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

mattcasters 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 a4a7499087 Fix#8866  Evaluate files metrics workflow Check validating 
wrong property and skipping rows (#8867)
a4a7499087 is described below

commit a4a74990875ed001f61964642f6b1584fa647200
Author: Lance <[email protected]>
AuthorDate: Tue Oct 13 02:37:09 2026 +0800

    Fix#8866  Evaluate files metrics workflow Check validating wrong property 
and skipping rows (#8867)
    
    * Fix Evaluate files metrics workflow Check validating wrong property and 
skipping rows
    
    Signed-off-by: leehaut <[email protected]>
    
    * Fix Evaluate files metrics workflow Check validating wrong property and 
skipping rows
    
    Signed-off-by: leehaut <[email protected]>
    
    ---------
    
    Signed-off-by: leehaut <[email protected]>
---
 .../evalfilesmetrics/ActionEvalFilesMetrics.java   |  30 +++-
 .../ActionEvalFilesMetricsCheckTest.java           | 190 +++++++++++++++++++++
 2 files changed, 216 insertions(+), 4 deletions(-)

diff --git 
a/plugins/actions/evalfilesmetrics/src/main/java/org/apache/hop/workflow/actions/evalfilesmetrics/ActionEvalFilesMetrics.java
 
b/plugins/actions/evalfilesmetrics/src/main/java/org/apache/hop/workflow/actions/evalfilesmetrics/ActionEvalFilesMetrics.java
index de34c40b42..404a863acc 100644
--- 
a/plugins/actions/evalfilesmetrics/src/main/java/org/apache/hop/workflow/actions/evalfilesmetrics/ActionEvalFilesMetrics.java
+++ 
b/plugins/actions/evalfilesmetrics/src/main/java/org/apache/hop/workflow/actions/evalfilesmetrics/ActionEvalFilesMetrics.java
@@ -50,6 +50,7 @@ import org.apache.hop.workflow.action.IAction;
 import org.apache.hop.workflow.action.validator.AbstractFileValidator;
 import org.apache.hop.workflow.action.validator.ActionValidatorUtils;
 import org.apache.hop.workflow.action.validator.AndValidator;
+import org.apache.hop.workflow.action.validator.FileExistsValidator;
 import org.apache.hop.workflow.action.validator.ValidatorContext;
 import org.apache.hop.workflow.engine.IWorkflowEngine;
 
@@ -688,7 +689,7 @@ public class ActionEvalFilesMetrics extends ActionBase 
implements Cloneable, IAc
         ActionValidatorUtils.andValidator()
             .validate(
                 this,
-                "arguments",
+                "sourceFiles",
                 remarks,
                 
AndValidator.putValidators(ActionValidatorUtils.notNullValidator()));
 
@@ -696,13 +697,34 @@ public class ActionEvalFilesMetrics extends ActionBase 
implements Cloneable, IAc
       return;
     }
 
+    // Only static file/folder rows are checkable here; previous/result 
filenames are runtime-only.
+    if (sourceFilesType != SourceFilesType.FILES || sourceFiles == null) {
+      return;
+    }
+
     ValidatorContext ctx = new ValidatorContext();
-    AbstractFileValidator.putVariableSpace(ctx, getVariables());
+    AbstractFileValidator.putVariableSpace(ctx, variables);
+    // notBlank rejects literal "" / whitespace; notNull would accept "".
     AndValidator.putValidators(
-        ctx, ActionValidatorUtils.notNullValidator(), 
ActionValidatorUtils.fileExistsValidator());
+        ctx, ActionValidatorUtils.notBlankValidator(), 
ActionValidatorUtils.fileExistsValidator());
+    // Default is false: missing files would pass Check silently while 
execute() fails.
+    FileExistsValidator.putFailIfDoesNotExist(ctx, true);
 
     for (int i = 0; i < sourceFiles.size(); i++) {
-      ActionValidatorUtils.andValidator().validate(this, "arguments[" + (i++) 
+ "]", remarks, ctx);
+      String propertyName = "sourceFiles[" + i + "].sourceFileFolder";
+      // Align with processFileFolder(): empty after variable resolve is an 
error. An empty
+      // resolved path must not reach FileExistsValidator — HopVfs treats "" 
as the CWD.
+      String resolved =
+          variables.resolve(
+              sourceFiles.get(i).getSourceFileFolder() == null
+                  ? ""
+                  : sourceFiles.get(i).getSourceFileFolder());
+      if (Utils.isEmpty(resolved)) {
+        ActionValidatorUtils.addFailureRemark(
+            this, propertyName, "notBlank", remarks, 
ICheckResult.TYPE_RESULT_ERROR);
+        continue;
+      }
+      ActionValidatorUtils.andValidator().validate(this, propertyName, 
remarks, ctx);
     }
   }
 
diff --git 
a/plugins/actions/evalfilesmetrics/src/test/java/org/apache/hop/workflow/actions/evalfilesmetrics/ActionEvalFilesMetricsCheckTest.java
 
b/plugins/actions/evalfilesmetrics/src/test/java/org/apache/hop/workflow/actions/evalfilesmetrics/ActionEvalFilesMetricsCheckTest.java
new file mode 100644
index 0000000000..47ad4ebe28
--- /dev/null
+++ 
b/plugins/actions/evalfilesmetrics/src/test/java/org/apache/hop/workflow/actions/evalfilesmetrics/ActionEvalFilesMetricsCheckTest.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.workflow.actions.evalfilesmetrics;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.ICheckResult;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Covers {@link ActionEvalFilesMetrics#check}: validates {@code sourceFiles} 
(not legacy {@code
+ * arguments}), checks every row (no {@code i++} skip), and fails when a 
configured file is missing.
+ */
+class ActionEvalFilesMetricsCheckTest {
+
+  @TempDir Path tempDir;
+
+  @BeforeAll
+  static void setUpClass() throws Exception {
+    HopClientEnvironment.init();
+  }
+
+  @Test
+  void nullSourceFilesReportsSourceFilesNotArguments() {
+    ActionEvalFilesMetrics action = new ActionEvalFilesMetrics("metrics");
+    action.setSourceFiles(null);
+
+    List<String> errors = errorTexts(action);
+
+    assertFalse(errors.isEmpty(), errors::toString);
+    assertTrue(
+        errors.stream().anyMatch(t -> t.contains("sourceFiles")),
+        "Expected failure on sourceFiles, got: " + errors);
+    assertTrue(
+        errors.stream().noneMatch(t -> t.contains("arguments")),
+        "Must not validate legacy arguments property, got: " + errors);
+  }
+
+  @Test
+  void missingSecondRowIsReported() throws Exception {
+    Path existing = tempDir.resolve("a.txt");
+    Files.writeString(existing, "ok", StandardCharsets.UTF_8);
+    Path missing = tempDir.resolve("b-missing.txt");
+
+    ActionEvalFilesMetrics action = actionWithFiles(existing, missing);
+
+    List<String> errors = errorTexts(action);
+
+    assertTrue(
+        errors.stream().anyMatch(t -> 
t.contains("sourceFiles[1].sourceFileFolder")),
+        "Second row must be checked (i++ bug would skip it): " + errors);
+    assertTrue(
+        errors.stream().noneMatch(t -> 
t.contains("sourceFiles[0].sourceFileFolder")),
+        "First existing row should not error: " + errors);
+    assertTrue(
+        errors.stream().noneMatch(t -> t.contains("arguments")),
+        "Must not mention arguments: " + errors);
+  }
+
+  @Test
+  void bothExistingRowsPass() throws Exception {
+    Path a = tempDir.resolve("a.txt");
+    Path b = tempDir.resolve("b.txt");
+    Files.writeString(a, "1", StandardCharsets.UTF_8);
+    Files.writeString(b, "2", StandardCharsets.UTF_8);
+
+    ActionEvalFilesMetrics action = actionWithFiles(a, b);
+
+    List<String> errors = errorTexts(action);
+    assertTrue(errors.isEmpty(), errors::toString);
+  }
+
+  @Test
+  void previousResultSourceSkipsStaticFileExistenceCheck() {
+    ActionEvalFilesMetrics action = new ActionEvalFilesMetrics("metrics");
+    
action.setSourceFilesType(ActionEvalFilesMetrics.SourceFilesType.PREVIOUS_RESULT);
+    action.setSourceFiles(
+        
List.of(sourceFile(tempDir.resolve("does-not-exist-at-check-time.txt").toString())));
+
+    List<String> errors = errorTexts(action);
+    assertTrue(
+        errors.isEmpty(),
+        "Previous-result mode has no static paths to verify at check time: " + 
errors);
+  }
+
+  @Test
+  void filenamesResultSourceSkipsStaticFileExistenceCheck() {
+    ActionEvalFilesMetrics action = new ActionEvalFilesMetrics("metrics");
+    
action.setSourceFilesType(ActionEvalFilesMetrics.SourceFilesType.FILENAMES_RESULT);
+    action.setSourceFiles(
+        
List.of(sourceFile(tempDir.resolve("does-not-exist-at-check-time.txt").toString())));
+
+    List<String> errors = errorTexts(action);
+    assertTrue(
+        errors.isEmpty(),
+        "Filenames-result mode has no static paths to verify at check time: " 
+ errors);
+  }
+
+  @Test
+  void blankSourceFileFolderIsReportedEvenWithWildcard() {
+    ActionEvalFilesMetrics action = new ActionEvalFilesMetrics("metrics");
+    action.setSourceFilesType(ActionEvalFilesMetrics.SourceFilesType.FILES);
+    ActionEvalFilesMetrics.SourceFile row = sourceFile("");
+    row.setSourceWildcard(".*\\.txt");
+    action.setSourceFiles(List.of(row));
+
+    List<String> errors = errorTexts(action);
+
+    assertTrue(
+        errors.stream().anyMatch(t -> 
t.contains("sourceFiles[0].sourceFileFolder")),
+        "Blank folder must fail Check (must not treat \"\" as CWD): " + 
errors);
+  }
+
+  @Test
+  void sourceFileFolderResolvingToBlankIsReported() {
+    ActionEvalFilesMetrics action = new ActionEvalFilesMetrics("metrics");
+    action.setSourceFilesType(ActionEvalFilesMetrics.SourceFilesType.FILES);
+    action.setSourceFiles(List.of(sourceFile("${EMPTY_FOLDER}")));
+
+    Variables variables = new Variables();
+    variables.setVariable("EMPTY_FOLDER", "");
+
+    List<String> errors = errorTexts(action, variables);
+
+    assertTrue(
+        errors.stream().anyMatch(t -> 
t.contains("sourceFiles[0].sourceFileFolder")),
+        "Variable resolving to blank must fail Check: " + errors);
+  }
+
+  private static ActionEvalFilesMetrics actionWithFiles(Path first, Path 
second) {
+    ActionEvalFilesMetrics action = new ActionEvalFilesMetrics("metrics");
+    action.setSourceFilesType(ActionEvalFilesMetrics.SourceFilesType.FILES);
+    action.setSourceFiles(
+        List.of(
+            sourceFile(first.toAbsolutePath().toString()),
+            sourceFile(second.toAbsolutePath().toString())));
+    return action;
+  }
+
+  private static ActionEvalFilesMetrics.SourceFile sourceFile(String folder) {
+    ActionEvalFilesMetrics.SourceFile file = new 
ActionEvalFilesMetrics.SourceFile();
+    file.setSourceFileFolder(folder);
+    file.setSourceWildcard("");
+    file.setSourceIncludeSubfolders("N");
+    return file;
+  }
+
+  private static List<String> errorTexts(ActionEvalFilesMetrics action) {
+    return errorTexts(action, new Variables());
+  }
+
+  private static List<String> errorTexts(ActionEvalFilesMetrics action, 
Variables variables) {
+    List<ICheckResult> remarks = new ArrayList<>();
+    action.check(remarks, new WorkflowMeta(), variables, new 
MemoryMetadataProvider());
+    List<String> errors = new ArrayList<>();
+    for (ICheckResult remark : remarks) {
+      if (remark.getType() == ICheckResult.TYPE_RESULT_ERROR) {
+        errors.add(remark.getText());
+      }
+    }
+    return errors;
+  }
+}

Reply via email to