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]