def test_deterministic_strict_negated_success(self): pattern_strict_negated = BoboPattern() \ .next(LABEL_LAYER_A, BoboPredicateCallable(predicate_key_a_value_a)) \ .not_next(LABEL_LAYER_B, BoboPredicateCallable(predicate_key_a_value_b)) \ .next(LABEL_LAYER_C, BoboPredicateCallable(predicate_key_a_value_c)) \ .next(LABEL_LAYER_D, BoboPredicateCallable(predicate_key_a_value_d)) nfa, buffer, handler, handlersub = handler_setup( nfa_name=NFA_NAME_A, pattern=pattern_strict_negated) handler.process(event_a) handler.process(event_c) handler.process(event_d) self.assertEqual(len(handlersub.final_history), 1) self.assertDictEqual( handlersub.final_history[0].events, { LABEL_LAYER_A: [event_a], LABEL_LAYER_C: [event_c], LABEL_LAYER_D: [event_d] })
def test_sliding_last_window_to_halt(self): timestamp_lower = EpochNSClock.generate_timestamp() sleep(0.5) timestamp_upper = EpochNSClock.generate_timestamp() window_range_ns = timestamp_upper - timestamp_lower timestamp_a = timestamp_lower timestamp_b = timestamp_a + window_range_ns timestamp_c = timestamp_b + window_range_ns + 1 predicate_a = BoboPredicateCallable(predicate_key_a_value_a) predicate_b = BoboPredicateCallable(predicate_key_a_value_b) predicate_c = BoboPredicateCallable(predicate_key_a_value_c) predicate_first_window = WindowSlidingLast(window_range_ns) event_a = PrimitiveEvent(timestamp_a, {KEY: VAL_1}) event_b = PrimitiveEvent(timestamp_b, {KEY: VAL_2}) event_c = PrimitiveEvent(timestamp_c, {KEY: VAL_3}) pattern_a = BoboPattern() \ .followed_by(LABEL_LAYER_A, predicate_a) \ .followed_by(LABEL_LAYER_B, predicate_b) \ .followed_by(LABEL_LAYER_C, predicate_c) \ .precondition(predicate_first_window) handler = BoboNFAHandler(BoboRuleBuilder.nfa(NFA_NAME_A, pattern_a), SharedVersionedMatchBuffer()) handlersub = NFAHandlerSubscriber() handler.subscribe(handlersub) handler.process(event_a) handler.process(event_b) handler.process(event_c) self.assertEqual(len(handlersub.final), 1)
def test_sliding_first_window_to_halt(self): predicate_a = BoboPredicateCallable(predicate_key_a_value_a) predicate_b = BoboPredicateCallable(predicate_key_a_value_b) predicate_c = BoboPredicateCallable(predicate_key_a_value_c) predicate_first_window = WindowSlidingFirst( interval_sec=range_sec) event_a = PrimitiveEvent(timestamp_low, {KEY: VAL_1}) event_b = PrimitiveEvent(timestamp_mid, {KEY: VAL_2}) event_c = PrimitiveEvent(timestamp_upp, {KEY: VAL_3}) pattern_a = BoboPattern() \ .followed_by(LABEL_LAYER_A, predicate_a) \ .followed_by(LABEL_LAYER_B, predicate_b) \ .followed_by(LABEL_LAYER_C, predicate_c) \ .precondition(predicate_first_window) handler = BoboNFAHandler( BoboRuleBuilder.nfa(NFA_NAME_A, pattern_a), SharedVersionedMatchBuffer()) handlersub = NFAHandlerSubscriber() handler.subscribe(handlersub) handler.process(event_a) handler.process(event_b) handler.process(event_c) self.assertEqual(len(handlersub.final), 1)
def test_deterministic_relaxed_optional(self): pattern_optional = BoboPattern() \ .followed_by(LABEL_LAYER_A, BoboPredicateCallable(predicate_key_a_value_a)) \ .followed_by(LABEL_LAYER_B, BoboPredicateCallable(predicate_key_a_value_b), optional=True) \ .followed_by(LABEL_LAYER_C, BoboPredicateCallable(predicate_key_a_value_c)) \ .followed_by(LABEL_LAYER_D, BoboPredicateCallable(predicate_key_a_value_d)) nfa, buffer, handler, handlersub = handler_setup( nfa_name=NFA_NAME_A, pattern=pattern_optional) handler.process(event_a) handler.process(event_c) handler.process(event_d) self.assertEqual(len(handlersub.final_history), 1) self.assertDictEqual( handlersub.final_history[0].events, { LABEL_LAYER_A: [event_a], LABEL_LAYER_C: [event_c], LABEL_LAYER_D: [event_d] })
def test_generate_nfa_deterministic_negated(self): predicate_a = BoboPredicateCallable(predicate_key_a_value_a) predicate_b = BoboPredicateCallable(predicate_key_a_value_b) predicate_c = BoboPredicateCallable(predicate_key_a_value_c) pattern = BoboPattern() \ .followed_by(LABEL_LAYER_A, predicate_a) \ .not_followed_by(LABEL_LAYER_B, predicate_b) \ .followed_by(LABEL_LAYER_C, predicate_c) nfa = BoboRuleBuilder.nfa(NFA_NAME_A, pattern) state_name_a = "{}-{}-{}".format(LABEL_LAYER_A, 1, 1) state_name_b = "{}-{}-{}".format(LABEL_LAYER_B, 1, 1) state_name_c = "{}-{}-{}".format(LABEL_LAYER_C, 1, 1) # variables self.assertEqual(nfa.name, NFA_NAME_A) self.assertEqual(3, len(nfa.states)) self.assertEqual(3, len(nfa.transitions)) self.assertEqual(state_name_a, nfa.start_state.name) self.assertEqual(state_name_c, nfa.final_state.name) self.assertEqual(0, len(nfa.preconditions)) # states self.assertTrue(state_name_a in nfa.states) self.assertTrue(state_name_b in nfa.states) self.assertTrue(state_name_c in nfa.states) self.assertEqual(state_name_a, nfa.states[state_name_a].name) self.assertEqual(state_name_b, nfa.states[state_name_b].name) self.assertEqual(state_name_c, nfa.states[state_name_c].name) self.assertEqual(LABEL_LAYER_A, nfa.states[state_name_a].label) self.assertEqual(LABEL_LAYER_B, nfa.states[state_name_b].label) self.assertEqual(LABEL_LAYER_C, nfa.states[state_name_c].label) self.assertFalse(nfa.states[state_name_a].is_negated) self.assertTrue(nfa.states[state_name_b].is_negated) self.assertFalse(nfa.states[state_name_c].is_negated) self.assertFalse(nfa.states[state_name_a].is_optional) self.assertFalse(nfa.states[state_name_b].is_optional) self.assertFalse(nfa.states[state_name_c].is_optional) # transitions self.assertTrue(state_name_a in nfa.transitions) self.assertTrue(state_name_b in nfa.transitions) self.assertTrue(state_name_c in nfa.transitions) self.assertListEqual([state_name_b], nfa.transitions[state_name_a].state_names) self.assertListEqual([state_name_c], nfa.transitions[state_name_b].state_names) self.assertListEqual([], nfa.transitions[state_name_c].state_names) self.assertFalse(nfa.transitions[state_name_a].is_strict) self.assertFalse(nfa.transitions[state_name_b].is_strict) self.assertFalse(nfa.transitions[state_name_c].is_strict)
def test_invalid_duplicate_labels(self): predicate_a = BoboPredicateCallable(predicate_key_a_value_a) predicate_b = BoboPredicateCallable(predicate_key_a_value_b) pattern = BoboPattern() \ .followed_by(LABEL_LAYER_A, predicate_a) \ .followed_by(LABEL_LAYER_A, predicate_b) with self.assertRaises(RuntimeError): BoboRuleBuilder.nfa(NFA_NAME_A, pattern)
def test_invalid_first_state_is_optional(self): predicate_a = BoboPredicateCallable(predicate_key_a_value_a) predicate_b = BoboPredicateCallable(predicate_key_a_value_b) pattern = BoboPattern() \ .followed_by(LABEL_LAYER_A, predicate_a, optional=True) \ .followed_by(LABEL_LAYER_B, predicate_b) with self.assertRaises(RuntimeError): BoboRuleBuilder.nfa(NFA_NAME_A, pattern)
def test_constructor_actions_is_none(self): name = "evdef_name" pattern = BoboPattern() action = None evdef = BoboComplexEvent(name=name, pattern=pattern, action=action) self.assertEqual(name, evdef.name) self.assertEqual(pattern, evdef.pattern) self.assertIsNone(evdef.action)
def test_constructor(self): name = "evdef_name" pattern = BoboPattern() action = NoAction() evdef = BoboComplexEvent(name=name, pattern=pattern, action=action) self.assertEqual(name, evdef.name) self.assertEqual(pattern, evdef.pattern) self.assertEqual(action, evdef.action)
def test_nondeterministic_loop_success(self): pattern_loop = BoboPattern() \ .followed_by(LABEL_LAYER_A, BoboPredicateCallable(predicate_key_a_value_a)) \ .followed_by(LABEL_LAYER_B, BoboPredicateCallable(predicate_key_a_value_b), loop=True) \ .followed_by(LABEL_LAYER_C, BoboPredicateCallable(predicate_key_a_value_c)) \ .followed_by(LABEL_LAYER_D, BoboPredicateCallable(predicate_key_a_value_d)) nfa, buffer, handler, handlersub = handler_setup(nfa_name=NFA_NAME_A, pattern=pattern_loop) event_b1 = PrimitiveEvent(timestamp=EpochNSClock.generate_timestamp(), data=event_b.data) event_b2 = PrimitiveEvent(timestamp=EpochNSClock.generate_timestamp(), data=event_b.data) event_c1 = PrimitiveEvent(timestamp=EpochNSClock.generate_timestamp(), data=event_c.data) event_c2 = PrimitiveEvent(timestamp=EpochNSClock.generate_timestamp(), data=event_c.data) # first run cloned handler.process(event_a) handler.process(event_b) self.assertEqual(1, len(handler.runs.values())) run = list(handler.runs.values())[0] # first run loops handler.process(event_b1) self.assertEqual(1, len(handler.runs.values())) # second run cloned handler.process(event_c1) self.assertEqual(2, len(handler.runs.values())) # first run increments handler.process(event_b2) self.assertEqual(2, len(handler.runs.values())) # third run cloned handler.process(event_c2) self.assertEqual(3, len(handler.runs.values())) # second and third run reach final state handler.process(event_d) self.assertEqual(1, len(handler.runs.values())) # first run is the only run left self.assertEqual(run, list(handler.runs.values())[0])
def test_invalid_accepting_states_are_nondeterministic(self): predicate_a = BoboPredicateCallable(predicate_key_a_value_a) predicate_b = BoboPredicateCallable(predicate_key_a_value_b) predicate_c = BoboPredicateCallable(predicate_key_a_value_c) pattern = BoboPattern() \ .followed_by(LABEL_LAYER_A, predicate_a) \ .followed_by_any(LABEL_LAYER_B, [predicate_b, predicate_c]) with self.assertRaises(RuntimeError): BoboRuleBuilder.nfa(NFA_NAME_A, pattern)
def test_only_one_state_in_nfa(self): pattern_one = BoboPattern().followed_by( LABEL_LAYER_A, BoboPredicateCallable(predicate_key_a_value_a)) nfa, buffer, handler, handlersub = handler_setup(nfa_name=NFA_NAME_A, pattern=pattern_one) handler.process(event_a) self.assertEqual(len(handlersub.final_history), 1) self.assertDictEqual(handlersub.final_history[0].events, {LABEL_LAYER_A: [event_a]})
def test_deterministic_strict_failure(self): pattern_strict = BoboPattern() \ .next(LABEL_LAYER_A, BoboPredicateCallable(predicate_key_a_value_a)) \ .next(LABEL_LAYER_B, BoboPredicateCallable(predicate_key_a_value_b)) \ .next(LABEL_LAYER_C, BoboPredicateCallable(predicate_key_a_value_c)) \ .next(LABEL_LAYER_D, BoboPredicateCallable(predicate_key_a_value_d)) nfa, buffer, handler, handlersub = handler_setup( nfa_name=NFA_NAME_A, pattern=pattern_strict) handler.process(event_a) handler.process(event_e) # event not in pattern self.assertEqual(len(handler.runs.keys()), 0)
def test_deterministic_relaxed_negated_failure(self): pattern_negated = BoboPattern() \ .followed_by(LABEL_LAYER_A, BoboPredicateCallable(predicate_key_a_value_a)) \ .not_followed_by(LABEL_LAYER_B, BoboPredicateCallable(predicate_key_a_value_b)) \ .followed_by(LABEL_LAYER_C, BoboPredicateCallable(predicate_key_a_value_c)) \ .followed_by(LABEL_LAYER_D, BoboPredicateCallable(predicate_key_a_value_d)) nfa, buffer, handler, handlersub = handler_setup( nfa_name=NFA_NAME_A, pattern=pattern_negated) handler.process(event_a) handler.process(event_b) # should NOT be followed by this self.assertEqual(1, len(handlersub.halt))
def test_fixed_window_to_final(self): timestamp_lower = EpochNSClock.generate_timestamp() sleep(0.1) timestamp_a = EpochNSClock.generate_timestamp() sleep(0.1) timestamp_b = EpochNSClock.generate_timestamp() sleep(0.1) timestamp_c = EpochNSClock.generate_timestamp() sleep(0.1) timestamp_upper = EpochNSClock.generate_timestamp() sleep(0.1) predicate_a = BoboPredicateCallable(predicate_key_a_value_a) predicate_b = BoboPredicateCallable(predicate_key_a_value_b) predicate_c = BoboPredicateCallable(predicate_key_a_value_c) predicate_fixed_window = WindowFixed(timestamp_lower, timestamp_upper) event_a = PrimitiveEvent(timestamp_a, {KEY: VAL_1}) event_b = PrimitiveEvent(timestamp_b, {KEY: VAL_2}) event_c = PrimitiveEvent(timestamp_c, {KEY: VAL_3}) pattern_a = BoboPattern() \ .followed_by(LABEL_LAYER_A, predicate_a) \ .followed_by(LABEL_LAYER_B, predicate_b) \ .followed_by(LABEL_LAYER_C, predicate_c) \ .precondition(predicate_fixed_window) handler = BoboNFAHandler( BoboRuleBuilder.nfa(NFA_NAME_A, pattern_a), SharedVersionedMatchBuffer()) handlersub = NFAHandlerSubscriber() handler.subscribe(handlersub) handler.process(event_a) handler.process(event_b) handler.process(event_c) self.assertEqual(len(handlersub.final_history), 1) self.assertDictEqual(handlersub.final_history[0].events, {LABEL_LAYER_A: [event_a], LABEL_LAYER_B: [event_b], LABEL_LAYER_C: [event_c]})
def test_all_callables_method_same_object(self): obj = StubPredicateClass() pattern = BoboPattern() \ .followed_by(LABEL_LAYER_A, BoboPredicateCallable(obj.predicate_true_1)) \ .followed_by(LABEL_LAYER_B, BoboPredicateCallable(obj.predicate_true_2)) \ .followed_by(LABEL_LAYER_C, BoboPredicateCallable(obj.predicate_true_3)) nfa, buffer, handler, handlersub = handler_setup(nfa_name=NFA_NAME_A, pattern=pattern) handler.process(event_a) handler.process(event_b) handler.process(event_c) self.assertEqual(1, len(handlersub.final))
def test_generate_nfa_times(self): predicate_a = BoboPredicateCallable(predicate_key_a_value_a) pattern = BoboPattern() \ .followed_by(LABEL_LAYER_A, predicate_a, times=5) nfa = BoboRuleBuilder.nfa(NFA_NAME_A, pattern) state_name_a1 = "{}-{}-{}".format(LABEL_LAYER_A, 1, 1) state_name_a2 = "{}-{}-{}".format(LABEL_LAYER_A, 1, 2) state_name_a3 = "{}-{}-{}".format(LABEL_LAYER_A, 1, 3) state_name_a4 = "{}-{}-{}".format(LABEL_LAYER_A, 1, 4) state_name_a5 = "{}-{}-{}".format(LABEL_LAYER_A, 1, 5) # variables self.assertEqual(5, len(nfa.states)) self.assertEqual(5, len(nfa.transitions)) self.assertEqual(state_name_a1, nfa.start_state.name) self.assertEqual(state_name_a5, nfa.final_state.name) # states self.assertEqual(state_name_a1, nfa.states[state_name_a1].name) self.assertEqual(state_name_a2, nfa.states[state_name_a2].name) self.assertEqual(state_name_a3, nfa.states[state_name_a3].name) self.assertEqual(state_name_a4, nfa.states[state_name_a4].name) self.assertEqual(state_name_a5, nfa.states[state_name_a5].name) self.assertEqual(LABEL_LAYER_A, nfa.states[state_name_a1].label) self.assertEqual(LABEL_LAYER_A, nfa.states[state_name_a2].label) self.assertEqual(LABEL_LAYER_A, nfa.states[state_name_a3].label) self.assertEqual(LABEL_LAYER_A, nfa.states[state_name_a4].label) self.assertEqual(LABEL_LAYER_A, nfa.states[state_name_a5].label) # transitions self.assertListEqual([state_name_a2], nfa.transitions[state_name_a1].state_names) self.assertListEqual([state_name_a3], nfa.transitions[state_name_a2].state_names) self.assertListEqual([state_name_a4], nfa.transitions[state_name_a3].state_names) self.assertListEqual([state_name_a5], nfa.transitions[state_name_a4].state_names) self.assertListEqual([], nfa.transitions[state_name_a5].state_names)
def test_nondeterministic_success(self): pattern_nondet = BoboPattern() \ .followed_by(LABEL_LAYER_A, BoboPredicateCallable(predicate_key_a_value_a)) \ .followed_by_any(LABEL_LAYER_B_C, [BoboPredicateCallable(predicate_key_a_value_b), BoboPredicateCallable( predicate_key_a_value_c)]) \ .next(LABEL_LAYER_D, BoboPredicateCallable(predicate_key_a_value_d)) nfa, buffer, handler, handlersub = handler_setup( nfa_name=NFA_NAME_A, pattern=pattern_nondet) event_d1 = PrimitiveEvent(timestamp=EpochNSClock.generate_timestamp(), data=event_d.data) event_d2 = PrimitiveEvent(timestamp=EpochNSClock.generate_timestamp(), data=event_d.data) handler.process(event_a) handler.process(event_b) handler.process(event_d1) self.assertEqual(len(handlersub.final_history), 1) self.assertDictEqual( handlersub.final_history[0].events, { LABEL_LAYER_A: [event_a], LABEL_LAYER_B_C: [event_b], LABEL_LAYER_D: [event_d1] }) handler.process(event_c) handler.process(event_d2) self.assertEqual(len(handlersub.final_history), 2) self.assertDictEqual( handlersub.final_history[1].events, { LABEL_LAYER_A: [event_a], LABEL_LAYER_B_C: [event_c], LABEL_LAYER_D: [event_d2] })
def test_haltconditions(self): pattern_haltcond = BoboPattern() \ .followed_by(LABEL_LAYER_A, BoboPredicateCallable(predicate_key_a_value_a)) \ .followed_by(LABEL_LAYER_B, BoboPredicateCallable(predicate_key_a_value_b)) \ .followed_by(LABEL_LAYER_C, BoboPredicateCallable(predicate_key_a_value_c)) \ .haltcondition(BoboPredicateCallable(predicate_key_a_value_d)) nfa, buffer, handler, handlersub = handler_setup( nfa_name=NFA_NAME_A, pattern=pattern_haltcond) # haltcondition not triggered, run not halted handler.process(event_a) handler.process(event_b) self.assertEqual(0, len(handlersub.halt)) # haltcondition triggered, halts run handler.process(event_d) self.assertEqual(1, len(handlersub.halt))
def test_generate_nfa_loop(self): predicate_a = BoboPredicateCallable(predicate_key_a_value_a) predicate_b = BoboPredicateCallable(predicate_key_a_value_b) predicate_c = BoboPredicateCallable(predicate_key_a_value_c) pattern = BoboPattern() \ .followed_by(LABEL_LAYER_A, predicate_a) \ .followed_by(LABEL_LAYER_B, predicate_b, loop=True) \ .followed_by(LABEL_LAYER_C, predicate_c) nfa = BoboRuleBuilder.nfa(NFA_NAME_A, pattern) state_name_a = "{}-{}-{}".format(LABEL_LAYER_A, 1, 1) state_name_b = "{}-{}-{}".format(LABEL_LAYER_B, 1, 1) state_name_c = "{}-{}-{}".format(LABEL_LAYER_C, 1, 1) # variables self.assertEqual(3, len(nfa.states)) self.assertEqual(3, len(nfa.transitions)) self.assertEqual(state_name_a, nfa.start_state.name) self.assertEqual(state_name_c, nfa.final_state.name) # states self.assertEqual(state_name_a, nfa.states[state_name_a].name) self.assertEqual(state_name_b, nfa.states[state_name_b].name) self.assertEqual(state_name_c, nfa.states[state_name_c].name) self.assertEqual(LABEL_LAYER_A, nfa.states[state_name_a].label) self.assertEqual(LABEL_LAYER_B, nfa.states[state_name_b].label) self.assertEqual(LABEL_LAYER_C, nfa.states[state_name_c].label) # transitions self.assertListEqual([state_name_b], nfa.transitions[state_name_a].state_names) self.assertEqual({state_name_b, state_name_c}, set(nfa.transitions[state_name_b].state_names)) self.assertListEqual([], nfa.transitions[state_name_c].state_names)
from bobocep.receiver.clocks.epoch_ns_clock import EpochNSClock from bobocep.rules.events.primitive_event import PrimitiveEvent from bobocep.rules.nfas.patterns.bobo_pattern import BoboPattern from bobocep.rules.predicates.bobo_predicate_callable import \ BoboPredicateCallable NFA_NAME_A = "NFA_NAME_A" LABEL_LAYER_A = 'layer_a' LABEL_LAYER_B = 'layer_b' LABEL_LAYER_C = 'layer_c' stub_predicate = BoboPredicateCallable(lambda e, h, r: True) stub_pattern = BoboPattern() \ .followed_by(LABEL_LAYER_A, stub_predicate) \ .followed_by(LABEL_LAYER_B, stub_predicate) \ .followed_by(LABEL_LAYER_C, stub_predicate) class TestMatchEvent(unittest.TestCase): def test_match_event_points_to_itself(self): event_a = PrimitiveEvent(timestamp=EpochNSClock.generate_timestamp()) match_a = MatchEvent(nfa_name=NFA_NAME_A, label=LABEL_LAYER_A, event=event_a) version = RunVersion() version.add_level( BoboRun._generate_id(nfa_name=NFA_NAME_A, start_event_id=event_a.event_id))
LABEL_A = "label_a" LABEL_B = "label_b" LABEL_C = "label_c" LABEL_D = "label_d" KEY_A = "key_a" VALUE_A = "value_a" DATA_DICT_A = {KEY_A: VALUE_A} SLEEP_WAIT = 0.5 NULL_DATA_DELAY = 1 stub_predicate_true = BoboPredicateCallable(lambda e, h, r: True) stub_pattern_1 = BoboPattern().followed_by(label=LABEL_A, predicate=stub_predicate_true) stub_pattern_4 = BoboPattern() \ .followed_by( label=LABEL_A, predicate=stub_predicate_true ).followed_by( label=LABEL_B, predicate=stub_predicate_true ).followed_by( label=LABEL_C, predicate=stub_predicate_true ).followed_by( label=LABEL_D, predicate=stub_predicate_true )
def test_invalid_empty_pattern(self): with self.assertRaises(RuntimeError): BoboRuleBuilder.nfa(NFA_NAME_A, BoboPattern())
data={KEY_A: VALUE_B}) event_c = PrimitiveEvent(timestamp=EpochNSClock.generate_timestamp(), data={KEY_A: VALUE_C}) event_d = PrimitiveEvent(timestamp=EpochNSClock.generate_timestamp(), data={KEY_A: VALUE_D}) event_e = PrimitiveEvent(timestamp=EpochNSClock.generate_timestamp(), data={KEY_A: VALUE_E}) pattern_relaxed = BoboPattern() \ .followed_by(LABEL_LAYER_A, BoboPredicateCallable(predicate_key_a_value_a)) \ .followed_by(LABEL_LAYER_B, BoboPredicateCallable(predicate_key_a_value_b)) \ .followed_by(LABEL_LAYER_C, BoboPredicateCallable(predicate_key_a_value_c)) \ .followed_by(LABEL_LAYER_D, BoboPredicateCallable(predicate_key_a_value_d)) def handler_setup(nfa_name, pattern, max_recent: int = 1): buffer = SharedVersionedMatchBuffer() nfa = BoboRuleBuilder.nfa(name_nfa=nfa_name, pattern=pattern) handler = BoboNFAHandler(nfa=nfa, buffer=buffer, max_recent=max_recent) handlersub = NFAHandlerSubscriber() handler.subscribe(handlersub) return nfa, buffer, handler, handlersub