dcmaf opened a new issue, #6410:
URL: https://github.com/apache/incubator-kie-drools/issues/6410

   This issue appears to be related to lazy activations and not adding a match 
to the list of the activation group if the activation group's 
triggered-for-recency value is >= the recency of the fact handle associated 
with the match (see addItemToActivationGroup in DefaultAgenda).
   
   In the test case (see below), the rule that should not fire (Rule A - 
GroupA) has two conditions, one is the first fact that is created and that 
never gets updated (StaticFact), the other matches facts that are updated by 
another rule (DynamicFact). Agenda groups are used to to force another rule 
(Rule B - GroupB) in the same activation group to fire when the DynamicFact is 
changed by the setup rule (Setup Rule - Group A).
   
   When the test executes, Rule A - GroupA doesn't fire for the first match 
(the fact handle associated with the match references DynamicFact). On the 
second and third matches, the rule is fired (the fact handle associated with 
the match references StaticFact).
   
   Swapping the order of the conditions for Rule A - GroupA (placing the 
StaticMatch first) will cause the test to pass.
   
   The test also fails on 8.44.2.Final. It passes on 7.74.1.Final.
   
   ```java
   package com.example;
   
   import static org.junit.jupiter.api.Assertions.assertEquals;
   import static org.junit.jupiter.api.Assertions.assertFalse;
   
   import java.util.ArrayList;
   import java.util.List;
   
   import org.junit.jupiter.api.Test;
   import org.kie.api.KieBase;
   import org.kie.api.builder.Message;
   import org.kie.api.builder.Results;
   import org.kie.api.event.rule.AfterMatchFiredEvent;
   import org.kie.api.event.rule.DefaultAgendaEventListener;
   import org.kie.api.runtime.KieSession;
   import org.kie.internal.utils.KieHelper;
   
   import lombok.Getter;
   import lombok.RequiredArgsConstructor;
   import lombok.Setter;
   import lombok.ToString;
   
   /**
    * Test class that demonstrates violation of Drools activation groups 
constraint that
    * only one rule per activation group.
    * 
    * This test specifically tests that activation groups work correctly to 
prevent
    * multiple rules from firing when they belong to the same activation group, 
even when
    * multiple rules have matching conditions. The test uses a complex scenario 
involving:
    * - Multiple agenda groups (GroupA and GroupB)
    * - Rules with the same activation group ("TestGroup")
    * - Cross-agenda group rule execution flow
    * 
    * Expected Behavior:
    * The test verifies that "Rule A" never fires despite having matching 
conditions,
    * because "Rule B" fires first in the same activation group, preventing 
"Rule A"
    * from executing due to activation group mutual exclusion.
    *
    * Actual Behavior:
    * "Rule B" fires for the second and third activations. "Rule A" fires for 
all three
    * activations.
    */
   public class ActivationGroupDualFactMatchTest
   {
       @Getter
       @Setter
       @ToString
       @RequiredArgsConstructor
       public static class FactWrapper
       {
           private final String id;
           private boolean flag;
       }
   
       /**
        * Drools Rule Language (DRL) definition containing three interconnected 
rules
        * that demonstrate activation group behavior with agenda group 
transitions.
        * 
        * Rule Definitions:
        * 
        * "Setup Rule - GroupA":
        * - Triggers on dynamic facts with flag=false
        * - Modifies the fact to set flag=true
        * - Switches agenda focus to GroupB
        * - Not in any activation group (can fire multiple times)
        * 
        * "Rule A - GroupA":
        * - Requires both static fact (flag=true) AND dynamic fact (flag=true)
        * - In activation-group "TestGroup"
        * - Should NEVER fire due to activation group mutual exclusion
        * - Also in GroupA agenda group
        * 
        * "Rule B - GroupB":
        * - Requires only dynamic fact (flag=true)
        * - In activation-group "TestGroup" (same as Rule A)
        * - Fires first, preventing Rule A from executing
        * - Executes in GroupB agenda group
        *
        * Execution Flow:
        * 1. Setup Rule fires, modifies dynamic fact, switches to GroupB
        * 2. Rule B fires in GroupB (satisfies activation group)
        * 3. Rule A cannot fire because activation group is already satisfied
        */
       private static final String DRL = """
                                         package com.example
                                         
                                         import %s.FactWrapper;
                                         
                                         global org.kie.api.runtime.KieSession 
ksession;
                                         
                                         rule "Setup Rule - GroupA"
                                             agenda-group "GroupA"
                                             when
                                                 $d: FactWrapper(id matches 
"dynamic-\\\\d+", flag == false)
                                             then
                                                 System.out.println(">>> Fired 
Setup Rule with fact: " + $d);
                                                 modify($d) { setFlag(true) };
                                                 
ksession.getAgenda().getAgendaGroup("GroupB").setFocus();
                                             end
                                         
                                         // This rule should never fire, but 
fires twice. Switching the order of the conditions
                                         // (putting StaticFact first) will 
make the test pass.
                                         rule "Rule A - GroupA"
                                             agenda-group "GroupA"
                                             activation-group "TestGroup"
                                             when
                                                 DynamicFact: FactWrapper(id 
matches "dynamic-.*", flag == true)
                                                 StaticFact: FactWrapper(id == 
"static", flag == true)
                                             then
                                                 System.out.println(">>> 
[ERROR] Rule A fired with facts: " + StaticFact + " and " + DynamicFact);
                                             end
                                         
                                         rule "Rule B - GroupB"
                                             agenda-group "GroupB"
                                             activation-group "TestGroup"
                                             when
                                                 DynamicFact: FactWrapper(id 
matches "dynamic-.*", flag == true)
                                             then
                                                 System.out.println(">>> Rule B 
fired in GroupB with fact: " + DynamicFact);
                                             end
                                         
""".formatted(ActivationGroupDualFactMatchTest.class.getName());
   
       /**
        * Tests that activation groups correctly prevent multiple rules from 
firing
        * even when multiple rules have matching conditions.
        *
        * Test Scenario:
        * This test creates a complex scenario where:
        * - One static fact is inserted as the first fact with flag=true
        * - Multiple dynamic facts are inserted with flag=false
        * - Setup rules modify dynamic facts to flag=true
        * - Both "Rule A" and "Rule B" have matching conditions
        * - Only "Rule B" should fire due to activation group mutual exclusion
        * 
        * Expected Execution Flow:
        * 1. Start with GroupA agenda focus
        * 2. Setup Rule fires 3 times (once per dynamic fact)
        * 3. Each Setup Rule execution switches focus to GroupB
        * 4. Rule B fires 3 times (once per modified dynamic fact)
        * 5. Rule A never fires despite having matching conditions
        * 
        * Validation Points:
        * - Setup Rule fires exactly 3 times
        * - Rule B fires exactly 3 times
        * - Rule A never fires (activation group prevents it)
        */
       @Test
       void testRuleAMatchingTwoFactsButNeverFires()
       {
           // === PHASE 1: Rule Compilation and Knowledge Base Setup ===
   
           // Create a KieHelper to build the knowledge base from our DRL string
           final KieHelper kieHelper = new KieHelper();
           kieHelper.addContent(DRL, "src/main/resources/rules.drl");
   
           // Verify that the rules compile without errors
           final Results results = kieHelper.verify();
           if (results.hasMessages(Message.Level.ERROR))
           {
               
results.getMessages(Message.Level.ERROR).forEach(System.err::println);
               throw new IllegalStateException("Rule compilation failed");
           }
   
           // Build the knowledge base and create a new session
           final KieBase kieBase = kieHelper.build();
           final KieSession kSession = kieBase.newKieSession();
   
           // === PHASE 2: Event Listener Setup for Rule Execution Tracking ===
   
           // Track which rules fire during execution for validation
           final List<String> firedRules = new ArrayList<>();
   
           // Add event listener to capture rule firing events
           kSession.addEventListener(new DefaultAgendaEventListener()
           {
               @Override
               public void afterMatchFired(final AfterMatchFiredEvent event)
               {
                   // Record the name of each rule that fires
                   firedRules.add(event.getMatch().getRule().getName());
               }
           });
   
           // Set the global variable that rules use to manipulate agenda focus
           kSession.setGlobal("ksession", kSession);
   
           // === PHASE 3: Fact Insertion - Setting Up the Test Scenario ===
   
           // Insert the static fact with flag=true (required for Rule A to 
match)
           // This fact will remain constant throughout the test
           final FactWrapper staticFact = new FactWrapper("static");
           staticFact.setFlag(true);  // Pre-set to true so Rule A can 
potentially match
           kSession.insert(staticFact);
   
           // Insert multiple dynamic facts with flag=false (triggers for Setup 
Rule)
           // These facts will be modified by the Setup Rule to flag=true
           for (int i = 1; i <= 3; i++)
           {
               // Each dynamic fact starts with flag=false, matching Setup Rule 
conditions
               kSession.insert(new FactWrapper("dynamic-%d".formatted(i)));
           }
   
           // === PHASE 4: Rule Execution - The Core Test Logic ===
   
           // Set initial agenda focus to GroupA where Setup Rule and Rule A 
reside
           kSession.getAgenda().getAgendaGroup("GroupA").setFocus();
   
           System.out.println(">>> Firing all rules...");
   
           // Execute all rules - this is where the complex interaction happens:
           // 1. Setup Rule fires for each dynamic fact (3 times)
           // 2. Each Setup Rule execution modifies a dynamic fact and switches 
to GroupB
           // 3. Rule B fires in GroupB for each modified dynamic fact (3 times)
           // 4. Rule A never fires despite having matching conditions 
(activation group prevents it)
           final int fired = kSession.fireAllRules();
   
           System.out.println(">>> Total rules fired: " + fired);
   
           // === PHASE 5: Validation - Verify Expected Behavior ===
   
           final boolean ruleAFired = firedRules.contains("Rule A - GroupA");
           final long ruleBFires = firedRules.stream().filter(r -> 
r.equals("Rule B - GroupB")).count();
           final long setupFires = firedRules.stream().filter(r -> 
r.equals("Setup Rule - GroupA")).count();
   
           // Validate the expected execution pattern
           assertEquals(3, setupFires, "Setup rule should fire 3 times (once 
per dynamic fact).");
           assertEquals(3, ruleBFires, "Rule B should fire 3 times (once per 
setup cycle).");
           assertFalse(ruleAFired, "Rule A should NOT have fired due to 
activation group mutual exclusion.");
   
           // Clean up resources
           kSession.dispose();
       }
   }
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to