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

hansva 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 9980d18584 Add Filter Rows condition isolation regression tests, fixes 
#7734 (#7855)
9980d18584 is described below

commit 9980d18584ededb0e934a24da47c233d49d98bfe
Author: Matt Casters <[email protected]>
AuthorDate: Mon Aug 10 14:12:59 2026 +0200

    Add Filter Rows condition isolation regression tests, fixes #7734 (#7855)
    
    Lock in the runtime Condition clone behavior so GUI edits during an
    active Local Pipeline Engine run cannot change True/False routing.
---
 .../transforms/filterrows/FilterRowsMetaTest.java  | 45 ++++++++++++
 .../transforms/filterrows/FilterRowsTest.java      | 85 ++++++++++++++++++++++
 2 files changed, 130 insertions(+)

diff --git 
a/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsMetaTest.java
 
b/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsMetaTest.java
index 56ba07325c..ffbbf3191f 100644
--- 
a/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsMetaTest.java
+++ 
b/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsMetaTest.java
@@ -16,8 +16,10 @@
  */
 package org.apache.hop.pipeline.transforms.filterrows;
 
+import static org.apache.hop.core.Condition.Function.EQUAL;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
 
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
@@ -27,6 +29,7 @@ import java.util.Map;
 import org.apache.hop.core.Condition;
 import org.apache.hop.core.HopEnvironment;
 import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.row.ValueMetaAndData;
 import org.apache.hop.pipeline.transform.TransformMeta;
 import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
 import org.apache.hop.pipeline.transforms.dummy.DummyMeta;
@@ -94,6 +97,48 @@ class FilterRowsMetaTest {
     assertEquals("false", clone.getFalseTransformName());
   }
 
+  /**
+   * FilterRowsMeta.clone() must deep-copy the condition tree so copy/paste 
and undo do not share
+   * mutable Condition / CValue instances with the original meta.
+   */
+  @Test
+  void testCloneDeepCopiesCondition() throws Exception {
+    Condition nameCondition =
+        new Condition("name", EQUAL, null, new ValueMetaAndData("constant", 
"Alice"));
+    Condition codeCondition =
+        new Condition("code", EQUAL, null, new ValueMetaAndData("constant", 
"A-1"));
+    Condition condition = new Condition();
+    condition.addCondition(nameCondition);
+    condition.addCondition(codeCondition);
+
+    FilterRowsMeta filterRowsMeta = new FilterRowsMeta();
+    filterRowsMeta.setCondition(condition);
+    filterRowsMeta.setTrueTransformName("true");
+    filterRowsMeta.setFalseTransformName("false");
+
+    FilterRowsMeta clone = filterRowsMeta.clone();
+    assertNotSame(filterRowsMeta, clone);
+    assertNotSame(filterRowsMeta.getCondition(), clone.getCondition());
+    assertNotSame(
+        filterRowsMeta.getCondition().getCondition(0), 
clone.getCondition().getCondition(0));
+    assertNotSame(
+        filterRowsMeta.getCondition().getCondition(0).getRightValue(),
+        clone.getCondition().getCondition(0).getRightValue());
+    assertEquals("true", clone.getTrueTransformName());
+    assertEquals("false", clone.getFalseTransformName());
+    assertEquals(2, clone.getCondition().nrConditions());
+    assertEquals("Alice", 
clone.getCondition().getCondition(0).getRightValueString());
+    assertEquals("A-1", 
clone.getCondition().getCondition(1).getRightValueString());
+
+    clone.getCondition().getCondition(0).getRightValue().setText("Bob");
+    clone.getCondition().getCondition(0).setLeftValueName("other");
+
+    assertEquals("Alice", 
filterRowsMeta.getCondition().getCondition(0).getRightValueString());
+    assertEquals("name", 
filterRowsMeta.getCondition().getCondition(0).getLeftValueName());
+    assertEquals("Bob", 
clone.getCondition().getCondition(0).getRightValueString());
+    assertEquals("other", 
clone.getCondition().getCondition(0).getLeftValueName());
+  }
+
   @Test
   void modifiedTarget() {
     FilterRowsMeta filterRowsMeta = new FilterRowsMeta();
diff --git 
a/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsTest.java
 
b/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsTest.java
index 1c6a38ac13..a3cde5988d 100644
--- 
a/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsTest.java
+++ 
b/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsTest.java
@@ -21,6 +21,8 @@ import static org.apache.hop.core.Condition.Function.EQUAL;
 import static org.apache.hop.core.Condition.Function.REGEXP;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.math.BigDecimal;
@@ -115,6 +117,89 @@ class FilterRowsTest {
     assertFalse(runtimeCondition.evaluate(rowMeta, new Object[] {"Alice", 
"B-123"}));
   }
 
+  /**
+   * Regression for #7734: after init(), the engine must keep evaluating a 
private clone of the
+   * condition. Mutating shared transform metadata (as the GUI does when OK is 
pressed on a running
+   * pipeline) must not change routing for the active execution.
+   */
+  @Test
+  void runtimeConditionIsIsolatedFromMetadataMutationAfterInit() throws 
Exception {
+    Condition metadataCondition =
+        new Condition("name", EQUAL, null, new ValueMetaAndData("constant", 
"Alice"));
+    FilterRows transform = createTransform(metadataCondition);
+    FilterRowsMeta meta = transform.getMeta();
+
+    assertTrue(transform.init());
+    Condition runtimeCondition = transform.getData().condition;
+    assertNotNull(runtimeCondition);
+    assertNotSame(meta.getCondition(), runtimeCondition);
+
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("name"));
+    Object[] aliceRow = new Object[] {"Alice"};
+    Object[] bobRow = new Object[] {"Bob"};
+
+    assertTrue(runtimeCondition.evaluate(rowMeta, aliceRow));
+    assertFalse(runtimeCondition.evaluate(rowMeta, bobRow));
+
+    // Simulate a GUI edit while the pipeline is still running: replace the 
meta condition.
+    Condition editedCondition =
+        new Condition("name", EQUAL, null, new ValueMetaAndData("constant", 
"Bob"));
+    meta.setCondition(editedCondition);
+
+    assertNotSame(meta.getCondition(), runtimeCondition);
+    assertTrue(
+        runtimeCondition.evaluate(rowMeta, aliceRow),
+        "Runtime condition must keep the rule captured at init()");
+    assertFalse(runtimeCondition.evaluate(rowMeta, bobRow));
+    assertFalse(
+        meta.getCondition().evaluate(rowMeta, aliceRow),
+        "Metadata condition really changed to the edited rule");
+    assertTrue(meta.getCondition().evaluate(rowMeta, bobRow));
+  }
+
+  /**
+   * Same isolation guarantee for nested conditions: editing a child on the 
meta condition after
+   * init must not affect the runtime clone.
+   */
+  @Test
+  void nestedRuntimeConditionIsIsolatedFromMetadataMutationAfterInit() throws 
Exception {
+    Condition nameCondition =
+        new Condition("name", EQUAL, null, new ValueMetaAndData("constant", 
"Alice"));
+    Condition codeCondition =
+        new Condition("code", EQUAL, null, new ValueMetaAndData("constant", 
"A-1"));
+    Condition metadataCondition = new Condition();
+    metadataCondition.addCondition(nameCondition);
+    metadataCondition.addCondition(codeCondition);
+
+    FilterRows transform = createTransform(metadataCondition);
+    FilterRowsMeta meta = transform.getMeta();
+
+    assertTrue(transform.init());
+    Condition runtimeCondition = transform.getData().condition;
+    assertNotNull(runtimeCondition);
+    assertNotSame(meta.getCondition(), runtimeCondition);
+    assertNotSame(meta.getCondition().getCondition(0), 
runtimeCondition.getCondition(0));
+
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("name"));
+    rowMeta.addValueMeta(new ValueMetaString("code"));
+    Object[] matchingRow = new Object[] {"Alice", "A-1"};
+
+    assertTrue(runtimeCondition.evaluate(rowMeta, matchingRow));
+
+    // Mutate nested meta condition in place (also possible via shared 
Condition graphs).
+    meta.getCondition().getCondition(0).getRightValue().setText("Bob");
+    meta.getCondition().getCondition(0).clearFieldPositions();
+
+    assertTrue(
+        runtimeCondition.evaluate(rowMeta, matchingRow),
+        "Nested runtime condition must not observe meta mutations after 
init()");
+    assertEquals("Alice", 
runtimeCondition.getCondition(0).getRightValueString());
+    assertEquals("Bob", 
meta.getCondition().getCondition(0).getRightValueString());
+    assertFalse(meta.getCondition().evaluate(rowMeta, matchingRow));
+  }
+
   private static Stream<Arguments> variableValueTypes() throws Exception {
     ValueMetaDate dateMeta = new ValueMetaDate("value");
     dateMeta.setConversionMask("yyyy-MM-dd");

Reply via email to