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

tkobayas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie.git


The following commit(s) were added to refs/heads/main by this push:
     new 1117b36e801 [incubator-kie#7109] test(drools): cover the remove-rule 
and subnetwork accumulate paths (#7119)
1117b36e801 is described below

commit 1117b36e801a59c5dbb3b48729c4901f0f1bd119
Author: Daniel Clark <[email protected]>
AuthorDate: Tue Sep 29 03:14:25 2026 +0200

    [incubator-kie#7109] test(drools): cover the remove-rule and subnetwork 
accumulate paths (#7119)
    
    Follow-up to the review on #7110, which asked for the remove-rule path and
    for an accumulate over a subnetwork source.
    
    Removing a rule renumbers the position bits of the merged memory prototypes
    through SegmentPrototype.mergeProtos(), with the same
    MemoryPrototype.setNodePosMaskBit() calls that splitProtos() uses. Removing
    the rule that caused the split is not enough to reproduce the bug: the
    numbering is positional, so un-splitting a segment restores exactly the
    build-time bits that the stale wrapped BetaMemoryPrototype still holds. A
    live session does not reproduce it either, because
    EagerPhreakBuilder.mergeSegment() pushes the bits of the prototypes into the
    existing memories rather than populating them from the prototypes.
    
    The accumulate node therefore has to end up at a different position than the
    one its prototype was built with, which needs the splitting node and the
    merging node to be different ones, and the session has to be created after
    the removal. Added that case plus its live-session counterpart and the
    straightforward removals of the rule that split the segment.
    
    An accumulate whose source is a subnetwork always roots its own segment: the
    subnetwork branches off the left tuple source of the accumulate node, which
    gives that source more than one sink and makes it a segment tip, so neither 
a
    split nor a merge can renumber the node. The added tests pin that invariant
    down, since it is the reason the bug cannot reach such a node, and they 
cover
    the branch of BetaMemoryPrototype.populateMemory() that resolves the
    SubnetworkPathMemory of the wrapped TupleToObjectNode.
    
    Verified against drools-core with and without the fix: 34 runs pass with it,
    8 fail without it, including the new
    accumulateBehindAJoinFiresInASessionCreatedAfterTheSplittingRuleIsRemoved.
    
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 .../SegmentSplitAccumulateTest.java                | 392 ++++++++++++++++++++-
 1 file changed, 380 insertions(+), 12 deletions(-)

diff --git 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/SegmentSplitAccumulateTest.java
 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/SegmentSplitAccumulateTest.java
index 5a4588418ae..77846d51b7e 100644
--- 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/SegmentSplitAccumulateTest.java
+++ 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/SegmentSplitAccumulateTest.java
@@ -29,6 +29,7 @@ import org.drools.core.reteoo.AccumulateNode;
 import org.drools.core.reteoo.AccumulateNode.AccumulateMemory;
 import org.drools.core.reteoo.BetaMemory;
 import org.drools.core.reteoo.LeftInputAdapterNode;
+import org.drools.core.reteoo.LeftTupleNode;
 import org.drools.core.reteoo.LeftTupleSink;
 import org.drools.core.reteoo.ObjectTypeNode;
 import org.drools.core.reteoo.SegmentMemory;
@@ -59,6 +60,12 @@ import static org.assertj.core.api.Assertions.assertThat;
  * SegmentCursor.moveToNextAvailableSegment() skips nodes whose dirty bit is 
clear, so the staged right
  * tuples of the accumulate node are never processed and the rule never 
fires.</p>
  *
+ * <p>The same renumbering runs on the removal path: 
SegmentPrototype.mergeProtos() reassigns the position
+ * bits of the merged memory prototypes with the very same 
MemoryPrototype.setNodePosMaskBit() calls. A live
+ * session survives that even with the stale wrapped bit, because 
EagerPhreakBuilder.mergeSegment() pushes the
+ * bits of the prototypes into the existing memories, but a session created 
after the removal is populated
+ * from the prototypes and sees the stale bit.</p>
+ *
  * <p>Two packages are used because KnowledgeBaseImpl.addPackages() sorts 
packages by rule count descending
  * and adds them one at a time: the first package gets its segment prototypes 
created in bulk once all of
  * its rules are attached, while every later package is added rule by rule 
through
@@ -71,8 +78,10 @@ public class SegmentSplitAccumulateTest {
         return 
TestParametersUtil2.getKieBaseCloudConfigurations(true).stream();
     }
 
+    private static final String PACKAGE = 
"org.drools.mvel.integrationtests.segmentsplit";
+
     private static final String HEADER =
-            "package org.drools.mvel.integrationtests.segmentsplit;\n" +
+            "package " + PACKAGE + ";\n" +
             "import " + A.class.getCanonicalName() + ";\n" +
             "import " + B.class.getCanonicalName() + ";\n" +
             "import " + C.class.getCanonicalName() + ";\n" +
@@ -97,6 +106,21 @@ public class SegmentSplitAccumulateTest {
             "  results.add( \"R1:\" + $n );\n" +
             "end\n";
 
+    /**
+     * The accumulate source is a conjunction, so it is compiled into a 
subnetwork and the accumulate node
+     * reads its right input from a TupleToObjectNode. That node is held by 
the BetaMemoryPrototype wrapped
+     * in the AccumulateMemoryPrototype, so this rule covers the branch of 
BetaMemoryPrototype.populateMemory()
+     * that also has to resolve a SubnetworkPathMemory.
+     */
+    private static final String RULE_ACCUMULATE_SUBNETWORK =
+            "rule \"R1 accumulate over subnetwork\"\n" +
+            "when\n" +
+            "  A( $v : value )\n" +
+            "  accumulate( B( value == $v ) and C( value == $v ); $n : 
count(); $n > 0 )\n" +
+            "then\n" +
+            "  results.add( \"R1:\" + $n );\n" +
+            "end\n";
+
     private static final String RULE_JOIN =
             "rule \"R1 join\"\n" +
             "when\n" +
@@ -115,6 +139,41 @@ public class SegmentSplitAccumulateTest {
             "  results.add( \"R2:\" + $v );\n" +
             "end\n";
 
+    /**
+     * The accumulate node sits behind a join instead of directly behind the 
LeftInputAdapterNode, so that the
+     * node that splits its segment and the node whose segment it is merged 
into can be different ones.
+     */
+    private static final String RULE_ACCUMULATE_BEHIND_JOIN =
+            "rule \"R1 accumulate behind a join\"\n" +
+            "when\n" +
+            "  A( $v : value )\n" +
+            "  B( value == $v )\n" +
+            "  accumulate( C( value == $v ); $n : count(); $n > 0 )\n" +
+            "then\n" +
+            "  results.add( \"R1:\" + $n );\n" +
+            "end\n";
+
+    /** Splits the segment at the join, which makes the accumulate node a 
segment root. */
+    private static final String RULE_SPLITTING_THE_JOIN =
+            "rule \"R2 shares the A and B patterns\"\n" +
+            "when\n" +
+            "  A( $v : value )\n" +
+            "  B( value == $v )\n" +
+            "  C( value == $v )\n" +
+            "then\n" +
+            "  results.add( \"R2:\" + $v );\n" +
+            "end\n";
+
+    /** Splits the segment at the LeftInputAdapterNode, so the join is a 
segment of its own. */
+    private static final String RULE_SPLITTING_THE_LIA =
+            "rule \"R3 shares the A pattern\"\n" +
+            "when\n" +
+            "  A( $v : value )\n" +
+            "  C( value == $v )\n" +
+            "then\n" +
+            "  results.add( \"R3:\" + $v );\n" +
+            "end\n";
+
     /** A second package with more rules, so that it is built first and the 
package under test is split. */
     private static final String BIGGER_PACKAGE =
             "package org.drools.mvel.integrationtests.segmentsplit.other;\n" +
@@ -206,16 +265,252 @@ public class SegmentSplitAccumulateTest {
     @MethodSource("parameters")
     public void 
accumulateNodeKeepsItsPositionBitAfterSegmentSplit(KieBaseTestConfiguration 
kieBaseTestConfiguration) {
         KieBase kbase = buildKieBase(kieBaseTestConfiguration, HEADER + 
RULE_ACCUMULATE_FROM + RULE_SHARING_LIA);
+        AccumulateNode accNode = findAccumulateNodeUnderTheSharedLia(kbase);
 
-        ObjectTypeNode aotn = getObjectTypeNode(kbase, A.class);
-        LeftInputAdapterNode liaNode = (LeftInputAdapterNode) 
aotn.getObjectSinkPropagator().getSinks()[0];
-        AccumulateNode accNode = null;
-        for (LeftTupleSink sink : liaNode.getSinkPropagator().getSinks()) {
-            if (sink instanceof AccumulateNode) {
-                accNode = (AccumulateNode) sink;
-            }
+        KieSession ksession = kbase.newKieSession();
+        try {
+            ksession.setGlobal("results", new ArrayList<String>());
+            ksession.insert(new A(1));
+            ksession.fireAllRules();
+
+            BetaMemory bm = getBetaMemory(ksession, accNode);
+            SegmentMemory smem = bm.getSegmentMemory();
+
+            assertThat(smem.getRootNode()).as("the split made the accumulate 
node a segment root").isEqualTo(accNode);
+            assertThat(bm.getNodePosMaskBit()).as("position bit of the first 
node of the segment").isEqualTo(1L);
+        } finally {
+            ksession.dispose();
         }
-        assertThat(accNode).as("the accumulate node shares the 
LeftInputAdapterNode").isNotNull();
+    }
+
+    /**
+     * Removing the rule that caused the split merges the two segments back 
together through
+     * SegmentPrototype.mergeProtos(), which renumbers the position bits of 
the merged memory prototypes with
+     * the same MemoryPrototype.setNodePosMaskBit() calls that splitProtos() 
uses. EagerPhreakBuilder then
+     * pushes those bits into the memories of the already running session by 
reading them back through
+     * MemoryPrototype.getNodePosMaskBit(), so the accumulate node of a live 
session must end up with the bit
+     * it holds inside the merged segment and keep firing.
+     */
+    @ParameterizedTest(name = "KieBase type={0}")
+    @MethodSource("parameters")
+    public void 
accumulateRuleStillFiresAfterTheSharingRuleIsRemovedFromALiveSession(KieBaseTestConfiguration
 kieBaseTestConfiguration) {
+        KieBase kbase = buildKieBase(kieBaseTestConfiguration, HEADER + 
RULE_ACCUMULATE_FROM + RULE_SHARING_LIA);
+        AccumulateNode accNode = findAccumulateNodeUnderTheSharedLia(kbase);
+
+        KieSession ksession = kbase.newKieSession();
+        try {
+            List<String> results = new ArrayList<>();
+            ksession.setGlobal("results", results);
+
+            ksession.insert(new A(1));
+            ksession.fireAllRules();
+            assertThat(results).as("nothing may fire before B(1) 
exists").isEmpty();
+
+            kbase.removeRule(PACKAGE, "R2 shares the A pattern");
+
+            BetaMemory bm = getBetaMemory(ksession, accNode);
+            SegmentMemory smem = bm.getSegmentMemory();
+            assertThat(smem.getRootNode()).as("the merge put the accumulate 
node back behind the LeftInputAdapterNode")
+                    .isNotEqualTo(accNode);
+            assertThat(bm.getNodePosMaskBit()).as("position bit of the 
accumulate node within the merged segment")
+                    .isEqualTo(2L);
+
+            // B(1) arrives on the right input of the accumulate node after 
the segments were merged
+            ksession.insert(new B(1));
+            ksession.fireAllRules();
+            assertThat(results).as("R1 must fire once B(1) 
exists").containsExactly("R1:1");
+        } finally {
+            ksession.dispose();
+        }
+    }
+
+    /**
+     * The same removal, but the accumulate rule is the one that splits the 
segment of the already built
+     * sharing rule, so the split and the merge are both applied incrementally 
to a live session.
+     */
+    @ParameterizedTest(name = "KieBase type={0}")
+    @MethodSource("parameters")
+    public void 
accumulateRuleBuiltAfterSharingRuleStillFiresAfterTheSharingRuleIsRemoved(KieBaseTestConfiguration
 kieBaseTestConfiguration) {
+        KieBase kbase = buildKieBase(kieBaseTestConfiguration, HEADER + 
RULE_SHARING_LIA + RULE_ACCUMULATE_FROM);
+
+        KieSession ksession = kbase.newKieSession();
+        try {
+            List<String> results = new ArrayList<>();
+            ksession.setGlobal("results", results);
+
+            ksession.insert(new A(1));
+            ksession.fireAllRules();
+            assertThat(results).as("nothing may fire before B(1) 
exists").isEmpty();
+
+            kbase.removeRule(PACKAGE, "R2 shares the A pattern");
+
+            ksession.insert(new B(1));
+            ksession.fireAllRules();
+            assertThat(results).as("R1 must fire once B(1) 
exists").containsExactly("R1:1");
+        } finally {
+            ksession.dispose();
+        }
+    }
+
+    /**
+     * A session created after the removal takes its memories from the merged 
prototypes through
+     * MemoryPrototype.populateMemory(), which reads the bit of the 
BetaMemoryPrototype wrapped in the
+     * AccumulateMemoryPrototype.
+     */
+    @ParameterizedTest(name = "KieBase type={0}")
+    @MethodSource("parameters")
+    public void 
accumulateRuleFiresInASessionCreatedAfterTheSharingRuleIsRemoved(KieBaseTestConfiguration
 kieBaseTestConfiguration) {
+        KieBase kbase = buildKieBase(kieBaseTestConfiguration, HEADER + 
RULE_ACCUMULATE_FROM + RULE_SHARING_LIA);
+        kbase.removeRule(PACKAGE, "R2 shares the A pattern");
+
+        KieSession ksession = kbase.newKieSession();
+        try {
+            List<String> results = new ArrayList<>();
+            ksession.setGlobal("results", results);
+
+            ksession.insert(new A(1));
+            ksession.fireAllRules();
+            assertThat(results).as("nothing may fire before B(1) 
exists").isEmpty();
+
+            ksession.insert(new B(1));
+            ksession.fireAllRules();
+            assertThat(results).as("R1 must fire once B(1) 
exists").containsExactly("R1:1");
+        } finally {
+            ksession.dispose();
+        }
+    }
+
+    /**
+     * Removing the rule that split the segment at the join leaves the 
accumulate node one position further
+     * down the merged segment than it held when its prototype was built, so 
the bit that
+     * MemoryPrototype.populateMemory() copies into a session created 
afterwards has to be the merged one and
+     * not the one the wrapped BetaMemoryPrototype was constructed with.
+     */
+    @ParameterizedTest(name = "KieBase type={0}")
+    @MethodSource("parameters")
+    public void 
accumulateBehindAJoinFiresInASessionCreatedAfterTheSplittingRuleIsRemoved(KieBaseTestConfiguration
 kieBaseTestConfiguration) {
+        KieBase kbase = buildKieBase(kieBaseTestConfiguration, HEADER + 
RULE_ACCUMULATE_BEHIND_JOIN
+                                                                       + 
RULE_SPLITTING_THE_JOIN + RULE_SPLITTING_THE_LIA);
+        kbase.removeRule(PACKAGE, "R2 shares the A and B patterns");
+
+        KieSession ksession = kbase.newKieSession();
+        try {
+            List<String> results = new ArrayList<>();
+            ksession.setGlobal("results", results);
+
+            ksession.insert(new A(1));
+            ksession.insert(new B(1));
+            ksession.fireAllRules();
+            assertThat(results).as("nothing may fire before C(1) 
exists").isEmpty();
+
+            AccumulateNode accNode = findAccumulateNode(kbase);
+            BetaMemory bm = getBetaMemory(ksession, accNode);
+            assertThat(bm.getSegmentMemory().getRootNode())
+                    .as("the merge put the accumulate node behind the 
join").isNotEqualTo(accNode);
+            assertThat(bm.getNodePosMaskBit())
+                    .as("position bit of the accumulate node within the merged 
segment").isEqualTo(2L);
+
+            ksession.insert(new C(1));
+            ksession.fireAllRules();
+            assertThat(results).as("R1 must fire once C(1) 
exists").containsExactly("R1:1", "R3:1");
+        } finally {
+            ksession.dispose();
+        }
+    }
+
+    /**
+     * The same removal against a running session, which 
EagerPhreakBuilder.mergeSegment() updates by reading
+     * the merged bits back through MemoryPrototype.getNodePosMaskBit().
+     */
+    @ParameterizedTest(name = "KieBase type={0}")
+    @MethodSource("parameters")
+    public void 
accumulateBehindAJoinFiresInALiveSessionAfterTheSplittingRuleIsRemoved(KieBaseTestConfiguration
 kieBaseTestConfiguration) {
+        KieBase kbase = buildKieBase(kieBaseTestConfiguration, HEADER + 
RULE_ACCUMULATE_BEHIND_JOIN
+                                                                       + 
RULE_SPLITTING_THE_JOIN + RULE_SPLITTING_THE_LIA);
+
+        KieSession ksession = kbase.newKieSession();
+        try {
+            List<String> results = new ArrayList<>();
+            ksession.setGlobal("results", results);
+
+            ksession.insert(new A(1));
+            ksession.insert(new B(1));
+            ksession.fireAllRules();
+            assertThat(results).as("nothing may fire before C(1) 
exists").isEmpty();
+
+            kbase.removeRule(PACKAGE, "R2 shares the A and B patterns");
+
+            AccumulateNode accNode = findAccumulateNode(kbase);
+            BetaMemory bm = getBetaMemory(ksession, accNode);
+            assertThat(bm.getNodePosMaskBit())
+                    .as("position bit of the accumulate node within the merged 
segment").isEqualTo(2L);
+
+            ksession.insert(new C(1));
+            ksession.fireAllRules();
+            assertThat(results).as("R1 must fire once C(1) 
exists").containsExactly("R1:1", "R3:1");
+        } finally {
+            ksession.dispose();
+        }
+    }
+
+    @ParameterizedTest(name = "KieBase type={0}")
+    @MethodSource("parameters")
+    public void 
accumulateOverSubnetworkRuleBuiltBeforeSharingRule(KieBaseTestConfiguration 
kieBaseTestConfiguration) {
+        assertSubnetworkRuleFiresWhenTheSubnetworkIsCompletedLater(
+                kieBaseTestConfiguration, HEADER + RULE_ACCUMULATE_SUBNETWORK 
+ RULE_SHARING_LIA);
+    }
+
+    @ParameterizedTest(name = "KieBase type={0}")
+    @MethodSource("parameters")
+    public void 
accumulateOverSubnetworkRuleBuiltAfterSharingRule(KieBaseTestConfiguration 
kieBaseTestConfiguration) {
+        assertSubnetworkRuleFiresWhenTheSubnetworkIsCompletedLater(
+                kieBaseTestConfiguration, HEADER + RULE_SHARING_LIA + 
RULE_ACCUMULATE_SUBNETWORK);
+    }
+
+    @ParameterizedTest(name = "KieBase type={0}")
+    @MethodSource("parameters")
+    public void 
accumulateOverSubnetworkRuleStillFiresAfterTheSharingRuleIsRemoved(KieBaseTestConfiguration
 kieBaseTestConfiguration) {
+        KieBase kbase = buildKieBase(kieBaseTestConfiguration, HEADER + 
RULE_ACCUMULATE_SUBNETWORK + RULE_SHARING_LIA);
+
+        KieSession ksession = kbase.newKieSession();
+        try {
+            List<String> results = new ArrayList<>();
+            ksession.setGlobal("results", results);
+
+            ksession.insert(new A(1));
+            ksession.insert(new C(1));
+            ksession.fireAllRules();
+            assertThat(results).as("only the sharing rule may fire before B(1) 
exists").containsExactly("R2:1");
+
+            kbase.removeRule(PACKAGE, "R2 shares the A pattern");
+
+            ksession.insert(new B(1));
+            ksession.fireAllRules();
+            assertThat(results).as("R1 must fire once the subnetwork matches")
+                    .containsExactly("R2:1", "R1:1");
+        } finally {
+            ksession.dispose();
+        }
+    }
+
+    /**
+     * An accumulate whose source is a subnetwork always roots its own 
segment: the subnetwork branches off the
+     * left tuple source of the accumulate node, which gives that source more 
than one sink and therefore makes
+     * it a segment tip (BuildtimeSegmentUtilities.isNonTerminalTipNode()). 
Adding or removing a rule that
+     * shares the LeftInputAdapterNode can neither split nor merge that 
segment away, so the position bit of
+     * such an accumulate node is pinned to the first one whatever else 
happens to the network.
+     *
+     * <p>This test pins down that invariant, because it is the reason why the 
renumbering bug cannot reach an
+     * accumulate over a subnetwork: should the node ever stop being a segment 
root, its bit would start being
+     * renumbered through MemoryPrototype.setNodePosMaskBit() like any other. 
It also covers the branch of
+     * BetaMemoryPrototype.populateMemory() that resolves the 
SubnetworkPathMemory of the TupleToObjectNode
+     * held by the prototype wrapped in the AccumulateMemoryPrototype.</p>
+     */
+    @ParameterizedTest(name = "KieBase type={0}")
+    @MethodSource("parameters")
+    public void 
accumulateOverSubnetworkNodeIsAlwaysASegmentRoot(KieBaseTestConfiguration 
kieBaseTestConfiguration) {
+        KieBase kbase = buildKieBase(kieBaseTestConfiguration, HEADER + 
RULE_ACCUMULATE_SUBNETWORK + RULE_SHARING_LIA);
+        AccumulateNode accNode = findAccumulateNodeUnderTheSharedLia(kbase);
 
         KieSession ksession = kbase.newKieSession();
         try {
@@ -223,12 +518,43 @@ public class SegmentSplitAccumulateTest {
             ksession.insert(new A(1));
             ksession.fireAllRules();
 
-            InternalWorkingMemory wm = (InternalWorkingMemory) ksession;
-            BetaMemory bm = ((AccumulateMemory) 
wm.getNodeMemory(accNode)).getBetaMemory();
+            BetaMemory bm = getBetaMemory(ksession, accNode);
             SegmentMemory smem = bm.getSegmentMemory();
 
-            assertThat(smem.getRootNode()).as("the split made the accumulate 
node a segment root").isEqualTo(accNode);
+            assertThat(bm.getSubnetworkPathMemory()).as("the accumulate source 
is a subnetwork").isNotNull();
+            assertThat(smem.getRootNode()).as("the accumulate node is a 
segment root").isEqualTo(accNode);
             assertThat(bm.getNodePosMaskBit()).as("position bit of the first 
node of the segment").isEqualTo(1L);
+
+            kbase.removeRule(PACKAGE, "R2 shares the A pattern");
+
+            BetaMemory bmAfter = getBetaMemory(ksession, accNode);
+            assertThat(bmAfter.getSegmentMemory().getRootNode())
+                    .as("the subnetwork keeps the left tuple source a segment 
tip, so no merge happens")
+                    .isEqualTo(accNode);
+            assertThat(bmAfter.getNodePosMaskBit()).as("position bit is 
unchanged by the removal").isEqualTo(1L);
+        } finally {
+            ksession.dispose();
+        }
+    }
+
+    private void 
assertSubnetworkRuleFiresWhenTheSubnetworkIsCompletedLater(KieBaseTestConfiguration
 kieBaseTestConfiguration,
+                                                                            
String drl) {
+        KieSession ksession = buildKieBase(kieBaseTestConfiguration, 
drl).newKieSession();
+        try {
+            List<String> results = new ArrayList<>();
+            ksession.setGlobal("results", results);
+
+            // C(1) only matches the sharing rule, the subnetwork of the 
accumulate still misses its B
+            ksession.insert(new A(1));
+            ksession.insert(new C(1));
+            ksession.fireAllRules();
+            assertThat(results).as("only the sharing rule may fire before B(1) 
exists").containsExactly("R2:1");
+
+            // B(1) completes the subnetwork, which propagates through the 
TupleToObjectNode to the right
+            // input of the accumulate node
+            ksession.insert(new B(1));
+            ksession.fireAllRules();
+            assertThat(results).as("R1 must fire once the subnetwork 
matches").containsExactly("R2:1", "R1:1");
         } finally {
             ksession.dispose();
         }
@@ -260,6 +586,48 @@ public class SegmentSplitAccumulateTest {
                                                           BIGGER_PACKAGE, drl);
     }
 
+    private AccumulateNode findAccumulateNodeUnderTheSharedLia(KieBase kbase) {
+        ObjectTypeNode aotn = getObjectTypeNode(kbase, A.class);
+        LeftInputAdapterNode liaNode = (LeftInputAdapterNode) 
aotn.getObjectSinkPropagator().getSinks()[0];
+        AccumulateNode accNode = null;
+        for (LeftTupleSink sink : liaNode.getSinkPropagator().getSinks()) {
+            if (sink instanceof AccumulateNode) {
+                accNode = (AccumulateNode) sink;
+            }
+        }
+        assertThat(accNode).as("the accumulate node shares the 
LeftInputAdapterNode").isNotNull();
+        return accNode;
+    }
+
+    private AccumulateNode findAccumulateNode(KieBase kbase) {
+        ObjectTypeNode aotn = getObjectTypeNode(kbase, A.class);
+        LeftInputAdapterNode liaNode = (LeftInputAdapterNode) 
aotn.getObjectSinkPropagator().getSinks()[0];
+        AccumulateNode accNode = findAccumulateNode(liaNode);
+        assertThat(accNode).as("the accumulate node is reachable from the 
shared LeftInputAdapterNode").isNotNull();
+        return accNode;
+    }
+
+    private AccumulateNode findAccumulateNode(LeftTupleNode node) {
+        if (node.getSinkPropagator() == null) {
+            return null;
+        }
+        for (LeftTupleSink sink : node.getSinkPropagator().getSinks()) {
+            if (sink instanceof AccumulateNode) {
+                return (AccumulateNode) sink;
+            }
+            AccumulateNode found = findAccumulateNode((LeftTupleNode) sink);
+            if (found != null) {
+                return found;
+            }
+        }
+        return null;
+    }
+
+    private BetaMemory getBetaMemory(KieSession ksession, AccumulateNode 
accNode) {
+        InternalWorkingMemory wm = (InternalWorkingMemory) ksession;
+        return ((AccumulateMemory) wm.getNodeMemory(accNode)).getBetaMemory();
+    }
+
     private ObjectTypeNode getObjectTypeNode(KieBase kbase, Class<?> 
nodeClass) {
         for (ObjectTypeNode n : ((InternalRuleBase) 
kbase).getRete().getObjectTypeNodes()) {
             if (((ClassObjectType) n.getObjectType()).getClassType() == 
nodeClass) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to