-
-
Notifications
You must be signed in to change notification settings - Fork 8
Complex event processing
Test for complexEventProcessing.drl
Here are requirements: when one person calls another they should be connected. Call starts. Any new calls would be awaiting in 'dialing' state until either running call drop, in this case new call would start at once, or await timeout reached and dialing drops. Running call has its own timeout.
First option is to model/test the behavior using time periods. Dialing should be automatically ended after 7 seconds, so after 5 seconds nothing should happen and after 10 seconds dialing event should be retracted.
@DroolsSession("org/droolsassert/complexEventProcessing.drl")
public class ComplexEventProcessingTest {
@RegisterExtension
public DroolsAssert drools = new DroolsAssert();
@Before
public void before() {
drools.setGlobal("stdout", System.out);
}
Second call dropped because callee is busy
@Test
public void testCallsConnectAndDisconnectLogic() {
Dialing caller1Dial = new Dialing("11111", "22222");
drools.insertAndFire(caller1Dial);
drools.assertRetracted(caller1Dial);
CallInProgress call = drools.getObject(CallInProgress.class);
assertEquals("11111", call.callerNumber);
drools.advanceTime(5, MINUTES);
Dialing caller3Dial = new Dialing("33333", "22222");
drools.insertAndFire(caller3Dial);
drools.assertExist(caller3Dial);
drools.advanceTime(5, SECONDS);
drools.assertExist(call, caller3Dial);
drools.advanceTime(5, SECONDS);
drools.assertExist(call);
drools.assertRetracted(caller3Dial);
drools.advanceTime(1, HOURS);
drools.assertRetracted(call);
drools.assertAllRetracted();
}
state transition graph
test output
00:00:00 ComplexEventProcessingTest#testCallsConnectAndDisconnectLogic
00:00:00 --> inserted #515036017: ComplexEventProcessingTest.Dialing[callerNumber=11111,calleeNumber=22222]
00:00:00 --> fireAllRules
00:00:00 <-- 'input call' activated by [Dialing#515036017]
00:00:00 --> inserted #1813531981: ComplexEventProcessingTest.CallInProgress[callerNumber=11111,calleeNumber=22222]
00:00:00 --> deleted #515036017: ComplexEventProcessingTest.Dialing[callerNumber=11111,calleeNumber=22222]
00:05:00 --> inserted #406511188: ComplexEventProcessingTest.Dialing[callerNumber=33333,calleeNumber=22222]
00:05:00 --> fireAllRules
00:05:08 <-- 'drop dial-up if callee is talking' activated by [Dialing#406511188, CallInProgress#1813531981]
00:05:08 --> inserted #1644155208: ComplexEventProcessingTest.CallDropped[pair=[22222, 33333],reason=callee is busy]
00:05:08 <-- 'input call dropped' activated by [CallDropped#1644155208, Dialing#406511188]
Dial-up 33333 dropped due to callee is busy
00:05:08 --> deleted #406511188: ComplexEventProcessingTest.Dialing[callerNumber=33333,calleeNumber=22222]
00:30:00 <-- 'drop the call if caller is talking more than permitted time' activated by [CallInProgress#1813531981]
00:30:00 --> inserted #491738374: ComplexEventProcessingTest.CallDropped[pair=[22222, 11111],reason=call timed out]
00:30:00 <-- 'call in progress dropped' activated by [CallDropped#491738374, CallInProgress#1813531981]
Call 11111 dropped due to call timed out
00:30:00 --> deleted #1813531981: ComplexEventProcessingTest.CallInProgress[callerNumber=11111,calleeNumber=22222]
First call dismissed in favor of the second call
@Test
public void testCallsConnectAndDisconnectLogic2() {
Dialing caller1Dial = new Dialing("11111", "22222");
drools.insertAndFire(caller1Dial);
drools.assertRetracted(caller1Dial);
CallInProgress call = drools.getObject(CallInProgress.class);
assertEquals("11111", call.callerNumber);
drools.advanceTime(5, MINUTES);
Dialing caller3Dial = new Dialing("33333", "22222");
drools.insertAndFire(caller3Dial);
drools.assertExist(caller3Dial);
drools.advanceTime(5, SECONDS);
drools.assertExist(call, caller3Dial);
CallDropped callDropped = new CallDropped("11111", "22222", "Dismissed");
drools.insertAndFire(callDropped);
drools.assertRetracted(call, caller3Dial, callDropped);
CallInProgress call2 = drools.getObject(CallInProgress.class);
drools.assertExist(call2);
drools.advanceTime(10, SECONDS);
drools.assertExist(call2);
drools.advanceTime(1, HOURS);
drools.assertRetracted(call2);
drools.assertAllRetracted();
}
state transition graph
test output
00:00:00 ComplexEventProcessingTest#testCallsConnectAndDisconnectLogic2
00:00:00 --> inserted #515036017: ComplexEventProcessingTest.Dialing[callerNumber=11111,calleeNumber=22222]
00:00:00 --> fireAllRules
00:00:00 <-- 'input call' activated by [Dialing#515036017]
00:00:00 --> inserted #1813531981: ComplexEventProcessingTest.CallInProgress[callerNumber=11111,calleeNumber=22222]
00:00:00 --> deleted #515036017: ComplexEventProcessingTest.Dialing[callerNumber=11111,calleeNumber=22222]
00:05:00 --> inserted #406511188: ComplexEventProcessingTest.Dialing[callerNumber=33333,calleeNumber=22222]
00:05:00 --> fireAllRules
00:05:05 --> inserted #145664926: ComplexEventProcessingTest.CallDropped[pair=[22222, 11111],reason=Dismissed]
00:05:05 --> fireAllRules
00:05:05 <-- 'call in progress dropped' activated by [CallDropped#145664926, CallInProgress#1813531981]
Call 11111 dropped due to Dismissed
00:05:05 --> deleted #1813531981: ComplexEventProcessingTest.CallInProgress[callerNumber=11111,calleeNumber=22222]
00:05:05 <-- 'input call' activated by [CallInProgress#1813531981, Dialing#406511188]
00:05:05 --> inserted #1885991030: ComplexEventProcessingTest.CallInProgress[callerNumber=33333,calleeNumber=22222]
00:05:05 --> deleted #406511188: ComplexEventProcessingTest.Dialing[callerNumber=33333,calleeNumber=22222]
00:35:05 <-- 'drop the call if caller is talking more than permitted time' activated by [CallInProgress#1885991030]
00:35:05 --> inserted #299161977: ComplexEventProcessingTest.CallDropped[pair=[22222, 33333],reason=call timed out]
00:35:05 <-- 'call in progress dropped' activated by [CallDropped#299161977, CallInProgress#1885991030]
Call 33333 dropped due to call timed out
00:35:05 --> deleted #1885991030: ComplexEventProcessingTest.CallInProgress[callerNumber=33333,calleeNumber=22222]
You can omit drools. boilerplate by for extending test class from the rule if you don't bother.
Second option is to stick to events. We can wait for the very rule to be activated and check whether the state of the session is expected after that. Then we are waiting for another scheduled activation to be executed and check the state and so on. Test would fail in case expected rule is not activated or the state fails the assertion. This way you free your test code from constants which are always subject to be changed and imply support efforts.
@Test
public void testCallsConnectAndDisconnectLogicStickToEvents() {
Dialing caller1Dial = new Dialing("11111", "22222");
drools.insertAndFire(caller1Dial);
drools.assertActivated("input call");
drools.assertRetracted(caller1Dial);
CallInProgress call = drools.getObject(CallInProgress.class);
assertEquals("11111", call.callerNumber);
Dialing caller3Dial = new Dialing("33333", "22222");
drools.insertAndFire(caller3Dial);
drools.assertActivated();
drools.assertExist(call, caller3Dial);
drools.awaitFor("drop dial-up if callee is talking");
drools.assertActivated("drop dial-up if callee is talking", "input call dropped");
drools.assertExist(call);
drools.assertRetracted(caller3Dial);
drools.awaitFor("drop the call if caller is talking more than permitted time");
drools.assertActivatedCount(
1, "drop the call if caller is talking more than permitted time",
1, "call in progress dropped");
drools.assertRetracted(call);
drools.assertNoScheduledActivations();
drools.assertAllRetracted();
}
Domain classes being used
public static class Dialing {
public String callerNumber;
public String calleeNumber;
public Dialing(String callerNumber, String calleeNumber) {
this.callerNumber = callerNumber;
this.calleeNumber = calleeNumber;
}
}
public static class CallInProgress {
public String callerNumber;
public String calleeNumber;
public CallInProgress(String callerNumber, String calleeNumber) {
this.callerNumber = callerNumber;
this.calleeNumber = calleeNumber;
}
}
public static class CallDropped {
public List<String> pair = new ArrayList<>();
public String reason;
public CallDropped(String callerNumber, String calleeNumber, String reason) {
pair.add(calleeNumber);
pair.add(callerNumber);
this.reason = reason;
}
}
Few more tests to mind
@Test
@TestRules(expected = "input call")
public void testAssertActivations() {
drools.insertAndFire(new Dialing("11111", "22222"));
}
@Test
@TestRules(expected = {
"input call",
"drop the call if caller is talking more than permitted time",
"call in progress dropped"
}, checkScheduled = true)
public void testAssertScheduledActivations() {
drools.insertAndFire(new Dialing("11111", "22222"));
}
@Test
@TestRules(expected = {
"input call",
"drop the call if caller is talking more than permitted time",
"call in progress dropped"
})
public void testAwaitForScheduledActivations() {
Dialing caller1Dial = new Dialing("11111", "22222");
drools.insertAndFire(caller1Dial);
drools.assertRetracted(caller1Dial);
CallInProgress call = drools.getObject(CallInProgress.class);
assertEquals("11111", call.callerNumber);
drools.awaitFor("drop the call if caller is talking more than permitted time");
drools.assertNoScheduledActivations();
drools.assertAllRetracted();
Dialing caller3Dial = new Dialing("33333", "22222");
drools.insertAndFire(caller3Dial);
drools.assertRetracted(caller3Dial);
call = drools.getObject(CallInProgress.class);
assertEquals("33333", call.callerNumber);
drools.assertFactsCount(1);
drools.printFacts();
}
Regular heartbeat test for temporalReasoning.drl
When heartbeats come regularly, no activations are expected.
@DroolsSession("classpath:/org/droolsassert/temporalReasoning.drl")
public class TemporalReasoningTest {
@RegisterExtension
public DroolsAssert drools = new DroolsAssert();
@Before
public void before() {
drools.setGlobal("stdout", System.out);
}
@Test
@TestRules(expected = {})
public void testRegularHeartbeat() {
Heartbeat heartbeat1 = new Heartbeat(1);
drools.insertAndFireAt("MonitoringStream", heartbeat1);
drools.advanceTime(5, SECONDS);
drools.assertExist(heartbeat1);
Heartbeat heartbeat2 = new Heartbeat(2);
drools.insertAndFireAt("MonitoringStream", heartbeat2);
drools.assertExist(heartbeat1, heartbeat2);
drools.advanceTime(5, SECONDS);
drools.assertRetracted(heartbeat1);
drools.assertExist(heartbeat2);
Heartbeat heartbeat3 = new Heartbeat(3);
drools.insertAndFireAt("MonitoringStream", heartbeat3);
drools.assertExist(heartbeat2, heartbeat3);
drools.advanceTime(5, SECONDS);
drools.assertRetracted(heartbeat2);
drools.assertExist(heartbeat3);
Heartbeat heartbeat4 = new Heartbeat(4);
drools.insertAndFireAt("MonitoringStream", heartbeat4);
drools.assertExist(heartbeat3, heartbeat4);
drools.advanceTime(5, SECONDS);
drools.assertRetracted(heartbeat3);
drools.assertExist(heartbeat4);
drools.assertFactsCount(1);
assertEquals(4, drools.getObject(Heartbeat.class).ordinal);
drools.printFacts();
}
public static class Heartbeat {
public int ordinal;
public Heartbeat(int ordinal) {
this.ordinal = ordinal;
}
}
}
Output
00:00:00 --> inserted: TemporalReasoningTest.Heartbeat[ordinal=1]
00:00:00 --> fireAllRules
00:00:05 --> inserted: TemporalReasoningTest.Heartbeat[ordinal=2]
00:00:05 --> fireAllRules
00:00:10 --> inserted: TemporalReasoningTest.Heartbeat[ordinal=3]
00:00:10 --> fireAllRules
00:00:15 --> inserted: TemporalReasoningTest.Heartbeat[ordinal=4]
00:00:15 --> fireAllRules
00:00:20 Facts (1):
TemporalReasoningTest.Heartbeat[ordinal=4]
Irregular heartbeat test for temporalReasoning.drl
When we observe gaps in heartbeat arrival then rule should be activated.
@DroolsSession("classpath:/org/droolsassert/temporalReasoning.drl")
public class TemporalReasoningTest {
@RegisterExtension
public DroolsAssert drools = new DroolsAssert();
@Before
public void before() {
drools.setGlobal("stdout", System.out);
}
@Test
@TestRules(expectedCount = { "1", "Sound the Alarm" })
public void testIrregularHeartbeat() {
drools.insertAndFireAt("MonitoringStream", new Heartbeat(1));
drools.advanceTime(5, SECONDS);
drools.advanceTime(5, SECONDS);
drools.insertAndFireAt("MonitoringStream", new Heartbeat(2), new Heartbeat(3));
drools.advanceTime(5, SECONDS);
drools.assertFactsCount(2);
drools.printFacts();
}
}
Output
00:00:00 --> inserted: TemporalReasoningTest.Heartbeat[ordinal=1]
00:00:00 --> fireAllRules
00:00:10 <-- 'Sound the Alarm' has been activated by the tuple [Heartbeat]
No heartbeat
00:00:10 --> inserted: TemporalReasoningTest.Heartbeat[ordinal=2]
00:00:10 --> fireAllRules
00:00:10 --> inserted: TemporalReasoningTest.Heartbeat[ordinal=3]
00:00:10 --> fireAllRules
00:00:15 Facts (2):
TemporalReasoningTest.Heartbeat[ordinal=2]
TemporalReasoningTest.Heartbeat[ordinal=3]
Test for slidingTimeWindow.drl
@DroolsSession("classpath:/org/droolsassert/slidingTimeWindow.drl")
public class SlidingWindowTest {
@RegisterExtension
public DroolsAssert drools = new DroolsAssert();
@Before
public void before() {
drools.setGlobal("stdout", System.out);
}
@Test
@TestRules(expectedCount = { "5", "Sound the alarm if temperature rises above threshold" })
public void testSlidingTimeWindow() {
drools.insert(new TemperatureThreshold(2));
for (int i = 0; i < 6; i++) {
drools.insertAndFire(new SensorReading(i));
drools.advanceTime(1, SECONDS);
}
drools.assertFactsCount(3);
drools.printFacts();
}
public static class SensorReading {
public double temperature;
public SensorReading(double temperature) {
this.temperature = temperature;
}
public double getTemperature() {
return temperature;
}
}
public static class TemperatureThreshold {
public double max;
public TemperatureThreshold(double max) {
this.max = max;
}
public double getMax() {
return max;
}
}
}
Output
00:00:00 --> inserted: SlidingWindowTest.TemperatureThreshold[max=2.0]
00:00:00 --> inserted: SlidingWindowTest.SensorReading[temperature=0.0]
00:00:00 --> fireAllRules
00:00:01 --> inserted: SlidingWindowTest.SensorReading[temperature=1.0]
00:00:01 --> fireAllRules
00:00:02 --> inserted: SlidingWindowTest.SensorReading[temperature=2.0]
00:00:02 --> fireAllRules
00:00:03 --> inserted: SlidingWindowTest.SensorReading[temperature=3.0]
00:00:03 --> fireAllRules
00:00:04 <-- 'Sound the alarm if temperature rises above threshold' has been activated by the tuple [TemperatureThreshold, Double]
Temperature Rises Above Threshold
00:00:04 --> inserted: SlidingWindowTest.SensorReading[temperature=4.0]
00:00:04 --> fireAllRules
00:00:04 <-- 'Sound the alarm if temperature rises above threshold' has been activated by the tuple [TemperatureThreshold, Double]
Temperature Rises Above Threshold
00:00:05 <-- 'Sound the alarm if temperature rises above threshold' has been activated by the tuple [TemperatureThreshold, Double]
Temperature Rises Above Threshold
00:00:05 --> inserted: SlidingWindowTest.SensorReading[temperature=5.0]
00:00:05 --> fireAllRules
00:00:05 <-- 'Sound the alarm if temperature rises above threshold' has been activated by the tuple [TemperatureThreshold, Double]
Temperature Rises Above Threshold
00:00:06 <-- 'Sound the alarm if temperature rises above threshold' has been activated by the tuple [TemperatureThreshold, Double]
Temperature Rises Above Threshold
00:00:06 Facts (3):
SlidingWindowTest.TemperatureThreshold[max=2.0]
SlidingWindowTest.SensorReading[temperature=4.0]
SlidingWindowTest.SensorReading[temperature=5.0]
Test for slidingLengthWindow.drl
@DroolsSession("classpath:/org/droolsassert/slidingLengthWindow.drl")
public class SlidingWindowTest2 {
@RegisterExtension
public DroolsAssert drools = new DroolsAssert();
@Before
public void before() {
drools.setGlobal("stdout", System.out);
}
@Test
@TestRules(expectedCount = { "2", "Sound the alarm if temperature rises above threshold" })
public void testSlidingLengthWindow() {
drools.insert(new TemperatureThreshold(2));
for (int i = 0; i < 6; i++) {
drools.insertAndFire(new SensorReading(i));
drools.advanceTime(1, SECONDS);
}
drools.assertFactsCount(7);
drools.printFacts();
}
}
Output
00:00:00 --> inserted: SlidingWindowTest.TemperatureThreshold[max=2.0]
00:00:00 --> inserted: SlidingWindowTest.SensorReading[temperature=0.0]
00:00:00 --> fireAllRules
00:00:01 --> inserted: SlidingWindowTest.SensorReading[temperature=1.0]
00:00:01 --> fireAllRules
00:00:02 --> inserted: SlidingWindowTest.SensorReading[temperature=2.0]
00:00:02 --> fireAllRules
00:00:03 --> inserted: SlidingWindowTest.SensorReading[temperature=3.0]
00:00:03 --> fireAllRules
00:00:04 --> inserted: SlidingWindowTest.SensorReading[temperature=4.0]
00:00:04 --> fireAllRules
00:00:04 <-- 'Sound the alarm if temperature rises above threshold' has been activated by the tuple [TemperatureThreshold, Double]
Temperature Rises Above Threshold
00:00:05 --> inserted: SlidingWindowTest.SensorReading[temperature=5.0]
00:00:05 --> fireAllRules
00:00:05 <-- 'Sound the alarm if temperature rises above threshold' has been activated by the tuple [TemperatureThreshold, Double]
Temperature Rises Above Threshold
00:00:06 Facts (7):
SlidingWindowTest.TemperatureThreshold[max=2.0]
SlidingWindowTest.SensorReading[temperature=0.0]
SlidingWindowTest.SensorReading[temperature=1.0]
SlidingWindowTest.SensorReading[temperature=2.0]
SlidingWindowTest.SensorReading[temperature=3.0]
SlidingWindowTest.SensorReading[temperature=4.0]
SlidingWindowTest.SensorReading[temperature=5.0]