This is an automated email from the ASF dual-hosted git repository.

bamaer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 810ed58ec5 processFiles performance regression, fixes #9162 (#9164)
810ed58ec5 is described below

commit 810ed58ec5bfc688511900f100b12f7b51a0fc96
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Sat Oct 31 08:41:42 2026 +0100

    processFiles performance regression, fixes #9162 (#9164)
    
    * processFiles performance regression, fixes #9162
    
    * fix logging remark
---
 .../transforms/processfiles/ProcessFiles.java      | 179 ++++++++++++++-------
 .../transforms/processfiles/ProcessFilesData.java  |   8 +
 .../messages/messages_en_US.properties             |   4 +
 .../transforms/processfiles/ProcessFilesTest.java  |  81 ++++++++++
 4 files changed, 212 insertions(+), 60 deletions(-)

diff --git 
a/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFiles.java
 
b/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFiles.java
index 96ad4a2090..eadabe942e 100644
--- 
a/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFiles.java
+++ 
b/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFiles.java
@@ -33,6 +33,7 @@ import org.apache.hop.core.util.Utils;
 import org.apache.hop.core.vfs.HopVfs;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.lineage.LineageFileIoEmitter;
+import org.apache.hop.lineage.hub.LineageHub;
 import org.apache.hop.lineage.model.FileIoOperation;
 import org.apache.hop.pipeline.Pipeline;
 import org.apache.hop.pipeline.PipelineMeta;
@@ -44,6 +45,12 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
 
   private static final int COPY_BUFFER_SIZE = 8192;
 
+  /**
+   * Number of files logged one by one at Basic level. Past this, per-file 
lines only appear at
+   * Detailed level and a summary is logged when the transform finishes.
+   */
+  static final int MAX_BASIC_FILE_LOG_LINES = 100;
+
   public ProcessFiles(
       TransformMeta transformMeta,
       ProcessFilesMeta meta,
@@ -113,13 +120,14 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
       }
       data.sourceFile = HopVfs.getFileObject(sourceFilename, variables);
 
-      if (!data.sourceFile.exists()) {
+      FileType sourceType = data.sourceFile.getType();
+      if (sourceType == FileType.IMAGINARY) {
         logError(
             BaseMessages.getString(PKG, 
"ProcessFiles.Error.SourceFileNotExist", sourceFilename));
         throw new HopException(
             BaseMessages.getString(PKG, 
"ProcessFiles.Error.SourceFileNotExist", sourceFilename));
       }
-      if (data.sourceFile.getType() != FileType.FILE) {
+      if (sourceType != FileType.FILE) {
         logError(
             BaseMessages.getString(PKG, 
"ProcessFiles.Error.SourceFileNotFile", sourceFilename));
         throw new HopException(
@@ -135,7 +143,10 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
           throw new HopException(BaseMessages.getString(PKG, 
"ProcessFiles.Error.TargetFileEmpty"));
         }
         data.targetFile = HopVfs.getFileObject(targetFilename, variables);
-        if (data.targetFile.exists()) {
+        // Look the target up once: the copy and move operations reuse this 
answer
+        FileType targetType = data.targetFile.getType();
+        data.targetFileExists = targetType != FileType.IMAGINARY;
+        if (data.targetFileExists) {
           if (isDetailed()) {
             logDetailed(
                 BaseMessages.getString(
@@ -143,7 +154,7 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
           }
           // check if target is really a file otherwise it could overwrite a 
complete folder by copy
           // or move operations
-          if (data.targetFile.getType() != FileType.FILE) {
+          if (targetType != FileType.FILE) {
             logError(
                 BaseMessages.getString(
                     PKG, "ProcessFiles.Error.TargetFileNotFile", 
targetFilename));
@@ -260,6 +271,27 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
     return true;
   }
 
+  @Override
+  public void dispose() {
+    // One line per transform copy, also when it stopped early; nothing ran if 
first is still set
+    if (!first && !meta.simulate && isBasic()) {
+      String summaryKey =
+          switch (meta.getOperationType()) {
+            case ProcessFilesMeta.OPERATION_TYPE_MOVE -> 
"ProcessFiles.Log.Basic.SummaryMove";
+            case ProcessFilesMeta.OPERATION_TYPE_DELETE -> 
"ProcessFiles.Log.Basic.SummaryDelete";
+            default -> "ProcessFiles.Log.Basic.SummaryCopy";
+          };
+      logBasic(
+          BaseMessages.getString(
+              PKG,
+              summaryKey,
+              Long.toString(data.filesProcessed),
+              Long.toString(data.filesSkipped),
+              Long.toString(data.filesOverwritten)));
+    }
+    super.dispose();
+  }
+
   private String operationTypeLabel() {
     switch (meta.getOperationType()) {
       case ProcessFilesMeta.OPERATION_TYPE_COPY:
@@ -285,11 +317,34 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
     }
   }
 
+  /**
+   * Logs one processed file at Basic level, up to {@link 
#MAX_BASIC_FILE_LOG_LINES} files. The
+   * Basic-level overwrite warnings share this limit. The Detailed-level line 
for the same file is
+   * logged separately and is not capped.
+   */
+  private void logFileProcessed(String messageKey, String... filenames) {
+    if (!isBasic()) {
+      return;
+    }
+    data.basicFileLogLines++;
+    if (data.basicFileLogLines <= MAX_BASIC_FILE_LOG_LINES) {
+      String[] friendlyNames = new String[filenames.length];
+      for (int i = 0; i < filenames.length; i++) {
+        friendlyNames[i] = friendlyPath(filenames[i]);
+      }
+      logBasic(BaseMessages.getString(PKG, messageKey, friendlyNames));
+    } else if (data.basicFileLogLines == MAX_BASIC_FILE_LOG_LINES + 1) {
+      logBasic(
+          BaseMessages.getString(
+              PKG,
+              "ProcessFiles.Log.Basic.FileLogLimitReached",
+              Integer.toString(MAX_BASIC_FILE_LOG_LINES)));
+    }
+  }
+
   private void processCopyOperation(String sourceFilename, String 
targetFilename) throws Exception {
-    boolean targetFileExists =
-        data.targetFile.exists() && data.targetFile.getType() == FileType.FILE;
-    boolean shouldPerform =
-        !data.targetFile.exists() || (meta.isOverwriteTargetFile() && 
targetFileExists);
+    // processRow() already refused a target that exists but is not a file
+    boolean shouldPerform = !data.targetFileExists || 
meta.isOverwriteTargetFile();
 
     if (meta.simulate) {
       if (shouldPerform && isDetailed()) {
@@ -312,10 +367,15 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
     }
 
     if (shouldPerform) {
-      if (meta.isOverwriteTargetFile() && data.targetFile.exists() && 
isBasic()) {
-        logBasic(
-            BaseMessages.getString(
-                PKG, "ProcessFiles.Log.Basic.OverwriteWarningCopy", 
friendlyPath(targetFilename)));
+      if (data.targetFileExists) {
+        data.filesOverwritten++;
+        if (isBasic() && data.basicFileLogLines < MAX_BASIC_FILE_LOG_LINES) {
+          logBasic(
+              BaseMessages.getString(
+                  PKG,
+                  "ProcessFiles.Log.Basic.OverwriteWarningCopy",
+                  friendlyPath(targetFilename)));
+        }
       }
       if (isDetailed()) {
         logDetailed(
@@ -326,44 +386,41 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
                 friendlyPath(targetFilename)));
       }
       // Delete target first; some providers do not truncate correctly on 
overwrite.
-      data.targetFile.delete();
+      if (data.targetFileExists) {
+        data.targetFile.delete();
+      }
       if (isDataVolumeMetricEnabled()) {
         copyFileTrackingVolume(data.sourceFile, data.targetFile);
       } else {
         data.targetFile.copyFrom(data.sourceFile, new 
TextOneToOneFileSelector());
       }
-      if (isBasic()) {
-        logBasic(
+      data.filesProcessed++;
+      logFileProcessed("ProcessFiles.Log.Basic.FileCopied", sourceFilename, 
targetFilename);
+      if (isDetailed()) {
+        logDetailed(
             BaseMessages.getString(
                 PKG,
-                "ProcessFiles.Log.Basic.FileCopied",
+                "ProcessFiles.Log.Detail.FileCopied",
                 friendlyPath(sourceFilename),
                 friendlyPath(targetFilename)));
       }
+      emitCopyLineage();
+    } else {
+      data.filesSkipped++;
       if (isDetailed()) {
         logDetailed(
             BaseMessages.getString(
                 PKG,
-                "ProcessFiles.Log.Detail.FileCopied",
-                friendlyPath(sourceFilename),
-                friendlyPath(targetFilename)));
+                "ProcessFiles.Log.Detail.SkippedCopyExistsNoOverwrite",
+                friendlyPath(targetFilename),
+                friendlyPath(sourceFilename)));
       }
-      emitCopyLineage();
-    } else if (isDetailed()) {
-      logDetailed(
-          BaseMessages.getString(
-              PKG,
-              "ProcessFiles.Log.Detail.SkippedCopyExistsNoOverwrite",
-              friendlyPath(targetFilename),
-              friendlyPath(sourceFilename)));
     }
   }
 
   private void processMoveOperation(String sourceFilename, String 
targetFilename) throws Exception {
-    boolean targetFileExists =
-        data.targetFile.exists() && data.targetFile.getType() == FileType.FILE;
-    boolean shouldPerform =
-        !data.targetFile.exists() || (meta.isOverwriteTargetFile() && 
targetFileExists);
+    // processRow() already refused a target that exists but is not a file
+    boolean shouldPerform = !data.targetFileExists || 
meta.isOverwriteTargetFile();
 
     if (meta.simulate) {
       if (shouldPerform && isDetailed()) {
@@ -386,10 +443,15 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
     }
 
     if (shouldPerform) {
-      if (meta.isOverwriteTargetFile() && data.targetFile.exists() && 
isBasic()) {
-        logBasic(
-            BaseMessages.getString(
-                PKG, "ProcessFiles.Log.Basic.OverwriteWarningMove", 
friendlyPath(targetFilename)));
+      if (data.targetFileExists) {
+        data.filesOverwritten++;
+        if (isBasic() && data.basicFileLogLines < MAX_BASIC_FILE_LOG_LINES) {
+          logBasic(
+              BaseMessages.getString(
+                  PKG,
+                  "ProcessFiles.Log.Basic.OverwriteWarningMove",
+                  friendlyPath(targetFilename)));
+        }
       }
       if (isDetailed()) {
         logDetailed(
@@ -399,9 +461,11 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
                 friendlyPath(sourceFilename),
                 friendlyPath(targetFilename)));
       }
-      String sourceUri = vfsUriString(data.sourceFile);
-      String targetUri = vfsUriString(data.targetFile);
-      Long lineageBytes = nullableContentSize(data.sourceFile);
+      // The source is gone after the move, so collect what lineage needs first
+      boolean lineageEnabled = LineageHub.getInstance().isEnabled();
+      String sourceUri = lineageEnabled ? vfsUriString(data.sourceFile) : null;
+      String targetUri = lineageEnabled ? vfsUriString(data.targetFile) : null;
+      Long lineageBytes = lineageEnabled ? 
nullableContentSize(data.sourceFile) : null;
       long movedBytes = 0;
       if (isDataVolumeMetricEnabled()) {
         movedBytes = safeContentSize(data.sourceFile);
@@ -411,30 +475,27 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
         dataVolumeIn = (dataVolumeIn != null ? dataVolumeIn : 0L) + movedBytes;
         dataVolumeOut = (dataVolumeOut != null ? dataVolumeOut : 0L) + 
movedBytes;
       }
-      if (isBasic()) {
-        logBasic(
+      data.filesProcessed++;
+      logFileProcessed("ProcessFiles.Log.Basic.FileMoved", sourceFilename, 
targetFilename);
+      if (isDetailed()) {
+        logDetailed(
             BaseMessages.getString(
                 PKG,
-                "ProcessFiles.Log.Basic.FileMoved",
+                "ProcessFiles.Log.Detail.FileMoved",
                 friendlyPath(sourceFilename),
                 friendlyPath(targetFilename)));
       }
+      emitMoveLineageAfterSuccess(sourceUri, targetUri, lineageBytes);
+    } else {
+      data.filesSkipped++;
       if (isDetailed()) {
         logDetailed(
             BaseMessages.getString(
                 PKG,
-                "ProcessFiles.Log.Detail.FileMoved",
-                friendlyPath(sourceFilename),
-                friendlyPath(targetFilename)));
+                "ProcessFiles.Log.Detail.SkippedMoveExistsNoOverwrite",
+                friendlyPath(targetFilename),
+                friendlyPath(sourceFilename)));
       }
-      emitMoveLineageAfterSuccess(sourceUri, targetUri, lineageBytes);
-    } else if (isDetailed()) {
-      logDetailed(
-          BaseMessages.getString(
-              PKG,
-              "ProcessFiles.Log.Detail.SkippedMoveExistsNoOverwrite",
-              friendlyPath(targetFilename),
-              friendlyPath(sourceFilename)));
     }
   }
 
@@ -448,19 +509,17 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
       }
       return;
     }
-    String sourceUri = vfsUriString(data.sourceFile);
-    Long deleteBytes = nullableContentSize(data.sourceFile);
+    boolean lineageEnabled = LineageHub.getInstance().isEnabled();
+    String sourceUri = lineageEnabled ? vfsUriString(data.sourceFile) : null;
+    Long deleteBytes = lineageEnabled ? nullableContentSize(data.sourceFile) : 
null;
     if (!data.sourceFile.delete()) {
       throw new HopException(
           BaseMessages.getString(
               PKG, "ProcessFiles.Error.CanNotDeleteFile", 
data.sourceFile.toString()));
     }
     emitDeleteLineage(sourceUri, deleteBytes);
-    if (isBasic()) {
-      logBasic(
-          BaseMessages.getString(
-              PKG, "ProcessFiles.Log.Basic.FileDeleted", 
friendlyPath(sourceFilename)));
-    }
+    data.filesProcessed++;
+    logFileProcessed("ProcessFiles.Log.Basic.FileDeleted", sourceFilename);
     if (isDetailed()) {
       logDetailed(
           BaseMessages.getString(
@@ -469,7 +528,7 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
   }
 
   private void emitCopyLineage() {
-    if (meta.simulate) {
+    if (meta.simulate || !LineageHub.getInstance().isEnabled()) {
       return;
     }
     Long bytes = nullableContentSize(data.targetFile);
diff --git 
a/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesData.java
 
b/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesData.java
index a624c06fa4..987ae1edba 100644
--- 
a/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesData.java
+++ 
b/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesData.java
@@ -28,6 +28,14 @@ public class ProcessFilesData extends BaseTransformData 
implements ITransformDat
   public FileObject sourceFile;
   public FileObject targetFile;
 
+  /** Whether the target of the current row existed before the operation. */
+  public boolean targetFileExists;
+
+  public long basicFileLogLines;
+  public long filesProcessed;
+  public long filesSkipped;
+  public long filesOverwritten;
+
   public ProcessFilesData() {
     super();
     indexOfSourceFilename = -1;
diff --git 
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_en_US.properties
 
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_en_US.properties
index 8b606addab..72df5bd194 100644
--- 
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_en_US.properties
+++ 
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_en_US.properties
@@ -37,9 +37,13 @@ ProcessFiles.Injection.TargetFilenameField=Target filename 
fieldname
 ProcessFiles.LineNumber=Process files: processed {0} input rows
 ProcessFiles.Log.Basic.FileCopied=Copied file {0} to {1}
 ProcessFiles.Log.Basic.FileDeleted=Deleted file {0}
+ProcessFiles.Log.Basic.FileLogLimitReached=More than {0} files processed: the 
remaining files are only logged one by one at Detailed log level
 ProcessFiles.Log.Basic.FileMoved=Moved file {0} to {1}
 ProcessFiles.Log.Basic.OverwriteWarningCopy=Copy will replace existing target 
file {0}
 ProcessFiles.Log.Basic.OverwriteWarningMove=Move will replace existing target 
file {0}
+ProcessFiles.Log.Basic.SummaryCopy=Copied {0} files ({1} skipped because the 
target exists, {2} replaced an existing target)
+ProcessFiles.Log.Basic.SummaryDelete=Deleted {0} files
+ProcessFiles.Log.Basic.SummaryMove=Moved {0} files ({1} skipped because the 
target exists, {2} replaced an existing target)
 ProcessFiles.Log.Detail.CopyStarted=Starting copy: {0} -> {1}
 ProcessFiles.Log.Detail.FileCopied=Finished copy: {0} -> {1}
 ProcessFiles.Log.Detail.FileDeleted=Finished delete: {0}
diff --git 
a/plugins/transforms/processfiles/src/test/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesTest.java
 
b/plugins/transforms/processfiles/src/test/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesTest.java
index 8c659f98c5..4be23947a0 100644
--- 
a/plugins/transforms/processfiles/src/test/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesTest.java
+++ 
b/plugins/transforms/processfiles/src/test/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesTest.java
@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -57,6 +58,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 import org.junit.jupiter.api.io.TempDir;
+import org.mockito.ArgumentCaptor;
 
 class ProcessFilesTest {
 
@@ -392,4 +394,83 @@ class ProcessFilesTest {
     assertEquals("1", Files.readString(t1, StandardCharsets.UTF_8));
     assertEquals("2", Files.readString(t2, StandardCharsets.UTF_8));
   }
+
+  @Test
+  void basicLog_capsPerFileLinesAndLogsSummary(@TempDir Path tempDir) throws 
Exception {
+    int fileCount = ProcessFiles.MAX_BASIC_FILE_LOG_LINES + 5;
+    Object[][] rows = new Object[fileCount][];
+    for (int i = 0; i < fileCount; i++) {
+      Path source = tempDir.resolve("in" + i + ".txt");
+      Files.writeString(source, Integer.toString(i), StandardCharsets.UTF_8);
+      rows[i] = new Object[] {vfsPath(source), vfsPath(tempDir.resolve("out" + 
i + ".txt"))};
+    }
+    when(helper.iLogChannel.isBasic()).thenReturn(true);
+
+    ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+    ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+    wireInput(transform, rowMeta(), rows);
+    PipelineTestingUtil.execute(transform, fileCount, false);
+    transform.dispose();
+
+    ArgumentCaptor<String> messages = ArgumentCaptor.forClass(String.class);
+    verify(helper.iLogChannel, atLeastOnce()).logBasic(messages.capture());
+    List<String> logged = messages.getAllValues();
+    assertEquals(
+        ProcessFiles.MAX_BASIC_FILE_LOG_LINES,
+        logged.stream().filter(m -> m.startsWith("Copied file ")).count());
+    assertEquals(1, logged.stream().filter(m -> m.startsWith("More than 100 
files")).count());
+    assertTrue(
+        logged.contains(
+            "Copied "
+                + fileCount
+                + " files (0 skipped because the target"
+                + " exists, 0 replaced an existing target)"));
+    assertTrue(Files.exists(tempDir.resolve("out" + (fileCount - 1) + 
".txt")));
+  }
+
+  @Test
+  void summary_countsSkippedAndOverwrittenTargets(@TempDir Path tempDir) 
throws Exception {
+    Path s1 = tempDir.resolve("a.txt");
+    Path t1 = tempDir.resolve("a.out");
+    Path s2 = tempDir.resolve("b.txt");
+    Path t2 = tempDir.resolve("b.out");
+    Files.writeString(s1, "1", StandardCharsets.UTF_8);
+    Files.writeString(s2, "2", StandardCharsets.UTF_8);
+    Files.writeString(t2, "old", StandardCharsets.UTF_8);
+    when(helper.iLogChannel.isBasic()).thenReturn(true);
+
+    ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+    meta.setOverwriteTargetFile(true);
+    ProcessFilesData data = new ProcessFilesData();
+    ProcessFiles transform = createTransform(meta, data);
+    wireInput(
+        transform,
+        rowMeta(),
+        new Object[] {vfsPath(s1), vfsPath(t1)},
+        new Object[] {vfsPath(s2), vfsPath(t2)});
+    PipelineTestingUtil.execute(transform, 2, false);
+
+    assertEquals(2, data.filesProcessed);
+    assertEquals(0, data.filesSkipped);
+    assertEquals(1, data.filesOverwritten);
+    assertEquals("2", Files.readString(t2, StandardCharsets.UTF_8));
+  }
+
+  @Test
+  void summary_countsSkippedTargetsWithoutOverwrite(@TempDir Path tempDir) 
throws Exception {
+    Path source = tempDir.resolve("in.txt");
+    Path target = tempDir.resolve("out.txt");
+    Files.writeString(source, "new", StandardCharsets.UTF_8);
+    Files.writeString(target, "old", StandardCharsets.UTF_8);
+
+    ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+    ProcessFilesData data = new ProcessFilesData();
+    ProcessFiles transform = createTransform(meta, data);
+    wireInput(transform, rowMeta(), new Object[] {vfsPath(source), 
vfsPath(target)});
+    PipelineTestingUtil.execute(transform, 1, false);
+
+    assertEquals(0, data.filesProcessed);
+    assertEquals(1, data.filesSkipped);
+    assertEquals("old", Files.readString(target, StandardCharsets.UTF_8));
+  }
 }

Reply via email to