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");