def test_previous_state_event_none(): m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s11 = State('s11') s12 = State('s12') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s11, initial=True) s1.add_state(s12) m.add_transition(s0, s12, events='a') m.initialize() assert list(m.leaf_state_stack.deque) == [] m.dispatch(_e('a')) assert list(m.leaf_state_stack.deque) == [s11] try: m.set_previous_leaf_state(event=None) except Exception as exc: assert not exc assert list(m.leaf_state_stack.deque) == [s11, s12]
def test_internal_transition(): class Foo(object): def __init__(self): self.value = False foo = Foo() def on_enter(state, event): foo.value = True def on_exit(state, event): foo.value = True idling = State('idling') idling.handlers = { 'enter': on_enter, 'exit': on_exit, } sm = StateMachine('sm') sm.add_state(idling, initial=True) sm.add_transition(idling, None, events=['internal_transition']) sm.add_transition(idling, idling, events=['external_transition']) sm.initialize() sm.dispatch(_e('internal_transition')) assert foo.value is False sm.dispatch(_e('external_transition')) assert foo.value is True
def test_hsm_simple_hsm_transition(): sm = StateMachine('sm') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s0.add_state(s1) s0.add_state(s2) s0.add_transition(s1, s2, events='a') s0.add_transition(s2, s1, events='a') s11 = State('s11') s12 = State('s12') sm.add_state(s0, initial=True) s0.add_state(s1, initial=True) s0.add_state(s2) s1.add_state(s11, initial=True) sm.initialize() assert sm.state == s0 assert s0.state == s1 assert s1.state == s11 assert sm.leaf_state == s11 sm.dispatch(_e('a')) assert sm.state == s0 assert s0.state == s2 assert sm.leaf_state == s2 sm.dispatch(_e('a')) assert sm.state == s0 assert s0.state == s1 assert s1.state == s11 assert sm.leaf_state == s11
def test_previous_state_with_source_event(): def do(state, event): event.cargo['source_event'].cargo['data'].append(state) def do_with_propagate(state, event): event.cargo['source_event'].cargo['data'].append(state) event.propagate = True m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s11 = State('s11') s12 = State('s12') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s11, initial=True) s1.add_state(s12) for state in s0, s1, s11, s12: state.handlers = {'enter': do, 'exit': do} m.add_transition(s0, s12, events='a') m.initialize() data = [] assert list(m.leaf_state_stack.deque) == [] m.dispatch(_e('a', data=data)) assert list(m.leaf_state_stack.deque) == [s11] data = [] m.set_previous_leaf_state(event=_e('a', data=data)) assert list(m.leaf_state_stack.deque) == [s11, s12] assert data == [s12, s11]
def _get_state_machine(self): oven = StateMachine('Oven') door_closed = StateMachine('Door closed') door_open = State('Door open') heating = HeatingState('Heating') toasting = State('Toasting') baking = State('Baking') off = State('Off') oven.add_state(door_closed, initial=True) oven.add_state(door_open) door_closed.add_state(off, initial=True) door_closed.add_state(heating) heating.add_state(baking, initial=True) heating.add_state(toasting) oven.add_transition(door_closed, toasting, events=['toast']) oven.add_transition(door_closed, baking, events=['bake']) oven.add_transition(door_closed, off, events=['off', 'timeout']) oven.add_transition(door_closed, door_open, events=['open']) # This time, a state behaviour is handled by Oven's methods. door_open.handlers = { 'enter': self.on_open_enter, 'exit': self.on_open_exit, 'close': self.on_door_close } oven.initialize() return oven
def test_state_stack_high_tree(): sm = StateMachine('sm') s0 = StateMachine('s0') s01 = StateMachine('s01') s011 = StateMachine('s011') s0111 = State('s0111') s0112 = State('s0112') s0113 = StateMachine('s0113') s01131 = State('s01131') sm.add_state(s0, initial=True) s0.add_state(s01, initial=True) s01.add_state(s011, initial=True) s011.add_state(s0111, initial=True) s011.add_state(s0112) s011.add_state(s0113) s0113.add_state(s01131, initial=True) s011.add_transition(s0111, s0113, events=['a']) sm.initialize() assert sm.leaf_state == s0111 sm.dispatch(_e('a')) assert list(sm.state_stack.deque) == [] assert list(s0.state_stack.deque) == [] assert list(s01.state_stack.deque) == [] assert list(s011.state_stack.deque) == [s0111] assert list(s011.leaf_state_stack.deque) == [] assert list(s0113.state_stack.deque) == [] assert list(sm.leaf_state_stack.deque) == [s0111]
def test_transition_from_and_to_machine_itself(): sm = StateMachine('sm') s1 = State('s1') s2 = State('s2') sm.add_state(s1, initial=True) sm.add_state(s2) with pytest.raises(StateMachineException) as exc: sm.add_transition(sm, s1, events=['sm->s1']) expected = ( 'Machine "sm" error: Unable to add transition from unknown state "sm"') assert expected in str(exc.value) sm.add_transition(s1, sm, events=['s1->sm']) sm.initialize() assert sm.state == s1 sm.dispatch(_e('sm->s1')) assert sm.state == s1 assert list(sm.state_stack.deque) == [] sm.dispatch(_e('s1->sm')) assert sm.state == s1 assert list(sm.state_stack.deque) == [s1] sm.dispatch(_e('sm->s1')) assert sm.state == s1 assert list(sm.state_stack.deque) == [s1] sm.dispatch(_e('s1->sm')) assert sm.state == s1 assert list(sm.state_stack.deque) == [s1, s1]
def test_revert_to_previous_state(): m = StateMachine('m') off = State('Off') on = State('On') m.add_state(off, initial=True) m.add_state(on) m.add_transition(on, off, events=['off']) m.add_transition(off, on, events=['on']) m.initialize() off.handlers = { 'test_no_history': lambda s, e: m.revert_to_previous_leaf_state() } assert m.leaf_state == off try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == [] m.dispatch(_e('on')) assert m.leaf_state == on assert list(m.leaf_state_stack.deque) == [off] m.dispatch(_e('off')) assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == [off, on] try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == on assert list(m.leaf_state_stack.deque) == [off, on]
def test_state_can_handle_hashable_types(): my_mock = mock.Mock() test_set = frozenset([1, 2]) m = StateMachine('m') s0 = State('s0') s1 = State('s1') m.add_state(s0, initial=True) m.add_state(s1) def do(state, event): my_mock(state, event.name) assert event.state_machine == m for state in s0, s1: state.handlers = {'&': do, frozenset([1, 2]): do} m.add_transition(s0, s1, events=['&', test_set]) m.add_transition(s1, s0, events=['&', test_set]) m.initialize() assert m.leaf_state == s0 m.dispatch(_e('&')) assert m.leaf_state == s1 assert my_mock.call_args[0] == (s0, '&') m.dispatch(_e(test_set)) assert m.leaf_state == s0 assert my_mock.call_args[0] == (s1, test_set) m.dispatch(_e(test_set)) assert m.leaf_state == s1 m.dispatch(_e('&')) assert m.leaf_state == s0 assert my_mock.call_count == 4
def _get_statemachine(self): dlg = StateMachine('memlog dialog') self.at_top = State('at top') self.in_middle = State('in middle') self.at_bottom = State('at bottom') dlg.add_state(self.at_top, initial=True) self.window_cursor_pos = 0 self.total_cursor_pos = 0 self.total_at_top_pos = 0 dlg.add_state(self.in_middle) dlg.add_state(self.at_bottom) dlg.add_transition(self.at_top, self.in_middle, events=['down_key'], action=self.move_down) dlg.add_transition(self.at_bottom, self.in_middle, events=['up_key'], action=self.move_up) # higher specificity: external transition, logically overwrites external transition (because it comes first) # IMPORTANT: add first, otherwise the up_key/down_key events are always handled by the 2 internal transitions below dlg.add_transition(self.in_middle, self.at_top, events=['up_key'], condition=self.is_at_second, action=self.move_up) dlg.add_transition(self.in_middle, self.at_bottom, events=['down_key'], condition=self.is_at_second_last, action=self.move_down) # lower specificity: internal transition dlg.add_transition(self.in_middle, None, events=['up_key'], action=self.move_up) dlg.add_transition(self.in_middle, None, events=['down_key'], action=self.move_down) dlg.add_transition(self.at_top, None, events=['up_key'], condition=self.has_more_up, action=self.scroll_up) dlg.add_transition(self.at_bottom, None, events=['down_key'], condition=self.has_more_down, action=self.scroll_down) dlg.initialize() return dlg
def test_state_stack(): sm = StateMachine('sm') s1 = State('s1') s2 = State('s2') s3 = StateMachine('s3') s31 = State('s31') s32 = State('s32') sm.add_state(s1, initial=True) sm.add_state(s2) sm.add_state(s3) s3.add_state(s31, initial=True) s3.add_state(s32) sm.add_transition(s1, s2, events=['s1->s2']) sm.add_transition(s2, s1, events=['s2->s1']) sm.add_transition(s1, s3, events=['a']) s3.add_transition(s31, s32, events=['b']) sm.initialize() assert sm.state == s1 assert sm.leaf_state == s1 sm.dispatch(_e('s1->s2')) assert sm.state == s2 assert sm.leaf_state == s2 assert list(sm.state_stack.deque) == [s1] sm.dispatch(_e('s2->s1')) assert sm.state == s1 assert sm.leaf_state == s1 assert list(sm.state_stack.deque) == [s1, s2] sm.dispatch(_e('a')) assert sm.state == s3 assert sm.leaf_state == s31 assert s3.leaf_state == s31 assert list(sm.state_stack.deque) == [s1, s2, s1] assert list(s3.state_stack.deque) == [] sm.dispatch(_e('b')) assert sm.state == s3 assert sm.leaf_state == s32 assert s3.state == s32 assert s3.leaf_state == s32 assert list(sm.state_stack.deque) == [s1, s2, s1] assert list(s3.state_stack.deque) == [s31] # Brute force rollback of the previous state s3.state = s3.state_stack.pop() sm.state = sm.state_stack.pop() assert sm.state == s1 assert sm.leaf_state == s1 assert s3.state == s31 assert s3.leaf_state == s31 assert list(sm.state_stack.deque) == [s1, s2] assert list(s3.state_stack.deque) == []
def test_revert_to_previous_state_not_reverting_after_first_iteration(): m = StateMachine('m') one = State('One') two = State('Two') three = State('Three') four = State('Four') m.add_state(one, initial=True) m.add_state(two) m.add_state(three) m.add_state(four) m.add_transition(one, two, events=['switch_to_two']) m.add_transition(two, three, events=['switch_to_three']) m.add_transition(three, four, events=['switch_to_four']) m.initialize() for state in [one, two, three, four]: state.handlers = { 'test_no_history': lambda s, e: m.revert_to_previous_leaf_state() } assert m.leaf_state == one assert list(m.leaf_state_stack.deque) == [] m.dispatch(_e('switch_to_two')) m.dispatch(_e('switch_to_three')) m.dispatch(_e('switch_to_four')) assert m.leaf_state == four assert list(m.leaf_state_stack.deque) == [one, two, three] try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == three assert list(m.leaf_state_stack.deque) == [one, two] try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == two assert list(m.leaf_state_stack.deque) == [one] try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == one assert list(m.leaf_state_stack.deque) == [] try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == one assert list(m.leaf_state_stack.deque) == []
def test_many_initial_states(): sm = StateMachine('sm') s1 = State('s1') s2 = State('s2') sm.add_state(s1, initial=True) with pytest.raises(StateMachineException) as exc: sm.add_state(s2, initial=True) expected = ('Machine "sm" error: Unable to set initial state to "s2". ' 'Initial state is already set to "s1"') assert expected in str(exc.value)
def test_set_previous_state_no_history(): m = StateMachine('m') off = State('Off') m.add_state(off, initial=True) m.initialize() off.handlers = {'test_no_history': lambda s, e: m.set_previous_leaf_state()} assert m.leaf_state == off try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == []
def test_hsm_init(): sm = StateMachine('sm') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s11 = State('s11') s12 = State('s12') sm.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s11, initial=True) sm.initialize() assert sm.state == s0 assert s0.state == s1 assert s1.state == s11 assert sm.leaf_state == s11
def test_transition_on_any_event(): m = StateMachine('m') s0 = State('s0') s1 = State('s1') m.add_state(s0, initial=True) m.add_state(s1) m.add_transition(s0, s1, events=[any_event]) m.add_transition(s1, s0, events=[any_event]) m.initialize() assert m.leaf_state == s0 m.dispatch(_e('whatever')) assert m.leaf_state == s1 m.dispatch(_e('whatever')) assert m.leaf_state == s0
def test_no_initial_state(): sm = StateMachine('sm') s1 = State('s1') s2 = State('s2') try: sm.initialize() except StateMachineException as exc: assert not exc sm.add_state(s1) sm.add_state(s2) with pytest.raises(StateMachineException) as exc: sm.initialize() expected = ('Machine "sm" error: Machine "sm" has no initial state') assert expected in str(exc.value)
def test_set_previous_state_no_history(): m = StateMachine('m') off = State('Off') m.add_state(off, initial=True) m.initialize() off.handlers = { 'test_no_history': lambda s, e: m.set_previous_leaf_state() } assert m.leaf_state == off try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == []
def __init__(self, emulator): super().__init__('emulator') self.emulator = emulator self.apple2 = self.emulator.apple2 self.display = self.apple2.display self.screen = self.display.screen self.cpu = self.apple2.cpu # type: CPU self.mem = self.apple2.memory._mem # type: [int] self.map = self.emulator.map executing = EmulatorExecutingState(self) not_executing = EmulatorNotExecutingState(self) self.add_state(executing, initial=True) self.add_state(not_executing) halt = State('halt') self.add_state(halt) self.add_transition(executing, not_executing, events=['ctrlx']) self.add_transition(not_executing, executing, events=['ctrlx']) self.add_transition(executing, not_executing, events=['breakpoint']) # TODO: EmulatorStates sollte selbst keine StateMachine mehr sein, sondern eine enthalten self.add_transition(executing, halt, events=['halt']) self.add_transition(not_executing, halt, events=['halt']) self.initialize()
def _get_state_machine(self): oven = StateMachine( 'Oven' ) # composition: Oven not isa StateMachine but contains one, linked below ((KXOQEIF)) door_closed = StateMachine('Door closed') # a StateMachine isa State door_open = State('Door open') heating = HeatingState() off = State('Off') oven.add_state(door_closed, initial=True) oven.add_state(door_open) door_closed.add_state(off, initial=True) door_closed.add_state(heating) # https://pysm.readthedocs.io/en/latest/pysm_module.html # So the order of calls on an event is as follows: # State’s event handler # condition callback # # before callback # exit handlers # action callback # enter handlers # after callback oven.add_transition(door_closed, heating.toasting, events=['toast'], after=heating.action) oven.add_transition(door_closed, heating.baking, events=['bake'], after=heating.action) oven.add_transition(door_closed, off, events=['off', 'timeout']) oven.add_transition(door_closed, door_open, events=['open']) # This time, a state behaviour is handled by Oven's methods. # ((KXOQEIF)) door_open.handlers = { 'enter': self.on_open_enter, 'exit': self.on_open_exit, 'close': self.on_door_close } # https://pysm.readthedocs.io/en/latest/pysm_module.html # If using nested state machines (HSM), initialize() has to be called on a root state machine in the hierarchy. oven.initialize() return oven
def test_transition_to_history(): oven = StateMachine('Oven') door_closed = StateMachine('DoorClosed') door_open = State('DoorOpen') heating = StateMachine('Heating') toasting = State('Toasting') baking = State('Baking') off = State('Off') oven.add_state(door_closed, initial=True) oven.add_state(door_open) door_closed.add_state(off, initial=True) door_closed.add_state(heating) heating.add_state(baking, initial=True) heating.add_state(toasting) oven.add_transition(door_closed, toasting, events=['toast']) oven.add_transition(door_closed, baking, events=['bake']) oven.add_transition(door_closed, off, events=['off', 'timeout']) oven.add_transition(door_closed, door_open, events=['open']) door_open.handlers = {'close': lambda s, e: oven.set_previous_leaf_state()} oven.initialize() assert oven.leaf_state == off oven.dispatch(_e('open')) assert oven.leaf_state == door_open try: oven.dispatch(_e('close')) except Exception as exc: assert not exc assert oven.leaf_state == off assert list(oven.leaf_state_stack.deque) == [off, door_open] oven.dispatch(_e('bake')) assert oven.leaf_state == baking oven.dispatch(_e('open')) assert oven.leaf_state == door_open try: oven.dispatch(_e('close')) except Exception as exc: assert not exc assert oven.leaf_state == baking expected = [off, door_open, off, baking, door_open] assert list(oven.leaf_state_stack.deque) == expected
def test_hsm_get_transition(): sm = StateMachine('sm') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s0.add_state(s1) s0.add_state(s2) s0.add_transition(s1, s2, events='a') s11 = State('s11') s12 = State('s12') sm.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s11, initial=True) sm.initialize() transition = sm._get_transition(_e('a')) assert s1 == transition['from_state'] assert s2 == transition['to_state']
def test_event_propagate_enter_exit(): data = [] m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s3 = StateMachine('s3') s4 = State('s4') def do(state, event): event.cargo['source_event'].cargo['data'].append(state) assert state == m.leaf_state def do_with_propagate(state, event): event.cargo['source_event'].cargo['data'].append(state) event.propagate = True assert state == m.leaf_state m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s2, initial=True) s2.add_state(s3, initial=True) s3.add_state(s4, initial=True) m.add_transition(s0, s0, events='a') for state in s0, s1, s2, s3, s4: state.handlers = {'enter': do, 'exit': do} m.initialize() m.dispatch(_e('a', data=data)) assert data == [s4, s3, s2, s1, s0, s0, s1, s2, s3, s4] data = [] s1.handlers = {} s3.handlers = {} m.dispatch(_e('a', data=data)) assert data == [s4, s2, s0, s0, s2, s4] # Never propagate exit/enter events, even if propagate is set to True data = [] s4.handlers = {'enter': do_with_propagate, 'exit': do_with_propagate} m.dispatch(_e('a', data=data)) assert data == [s4, s2, s0, s0, s2, s4]
def test_event_propagate_enter_exit(): data = [] def do(state, event): event.cargo['source_event'].cargo['data'].append(state) def do_with_propagate(state, event): event.cargo['source_event'].cargo['data'].append(state) event.propagate = True m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s3 = StateMachine('s3') s4 = State('s4') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s2, initial=True) s2.add_state(s3, initial=True) s3.add_state(s4, initial=True) m.add_transition(s0, s0, events='a') for state in s0, s1, s2, s3, s4: state.handlers = {'enter': do, 'exit': do} m.initialize() m.dispatch(_e('a', data=data)) assert data == [s4, s3, s2, s1, s0, s0, s1, s2, s3, s4] data = [] s1.handlers = {} s3.handlers = {} m.dispatch(_e('a', data=data)) assert data == [s4, s2, s0, s0, s2, s4] # Never propagate exit/enter events, even if propagate is set to True data = [] s4.handlers = {'enter': do_with_propagate, 'exit': do_with_propagate} m.dispatch(_e('a', data=data)) assert data == [s4, s2, s0, s0, s2, s4]
def test_add_state_that_is_already_added_anywhere_in_the_hsm(): sm = StateMachine('sm') s1 = State('s1') s2 = State('s2') s3 = StateMachine('s3') s31 = State('s31') s32 = State('s32') sm.add_state(s1) sm.add_state(s2) sm.add_state(s3) s3.add_state(s31) s3.add_state(s32) with pytest.raises(StateMachineException) as exc: s3.add_state(s2) expected = ('Machine "s3" error: State "s2" is already added ' 'to machine "sm"') assert expected in str(exc.value)
def test_input_not_iterable(): sm = StateMachine('sm') s1 = State('s1') sm.add_state(s1) with pytest.raises(StateMachineException) as exc: sm.add_transition(s1, None, events=[1], input=2) expected = ('Machine "sm" error: Unable to add transition, ' 'input is not iterable: 2') assert expected in str(exc.value)
def test_revert_to_previous_state(): m = StateMachine('m') off = State('Off') on = State('On') m.add_state(off, initial=True) m.add_state(on) m.add_transition(on, off, events=['off']) m.add_transition(off, on, events=['on']) m.initialize() off.handlers = { 'test_no_history': lambda s, e: m.revert_to_previous_leaf_state() } assert m.leaf_state == off try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == [] m.dispatch(_e('on')) assert m.leaf_state == on assert list(m.leaf_state_stack.deque) == [off] m.dispatch(_e('off')) assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == [off, on] try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == on assert list(m.leaf_state_stack.deque) == [off] # Nothing should change now as the "on" state doesn't handle the # "test_no_history"" event. try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == on assert list(m.leaf_state_stack.deque) == [off]
def test_add_states_and_set_initial_state(): m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = State('s2') m.add_states(s0, s1, s2) assert all(state in m.states for state in (s0, s1, s2)) assert m.initial_state is None m.set_initial_state(s0) m.initialize() assert m.initial_state is s0
def get_state_machine(self): sm = StateMachine('sm') initial = State('Initial') number = State('BuildingNumber') sm.add_state(initial, initial=True) sm.add_state(number) sm.add_transition(initial, number, events=['parse'], input=py_string.digits, action=self.start_building_number) sm.add_transition(number, None, events=['parse'], input=py_string.digits, action=self.build_number) sm.add_transition(number, initial, events=['parse'], input=py_string.whitespace) sm.add_transition(initial, None, events=['parse'], input='+-*/', action=self.do_operation) sm.add_transition(initial, None, events=['parse'], input='=', action=self.do_equal) sm.initialize() return sm
def _get_state_machine(self): state_machine = StateMachine('Test') state_1 = State('One') state_2 = State('Two') self.state_1 = state_1 self.state_2 = state_2 state_machine.add_state(state_1, initial=True) state_machine.add_state(state_2) state_machine.add_transition(state_1, state_2, events=['change_state']) state_machine.add_transition(state_2, state_1, events=['change_state']) state_1.handlers = { 'enter': self.entry_func, 'exit': self.exit_func } state_2.handlers = { 'enter': self.entry_func, 'exit': self.exit_func } state_machine.initialize() return state_machine
def test_add_transition_unknown_state(): sm = StateMachine('sm') s1 = State('s1') s2 = State('s2') # This state isn't added to sm s3 = StateMachine('s3') s31 = State('s31') s32 = State('s32') # This state isn't added to s3 sm.add_state(s1) sm.add_state(s3) s3.add_state(s31) with pytest.raises(StateMachineException) as exc: sm.add_transition(s1, s2, events='a') expected = ( 'Machine "sm" error: Unable to add transition to unknown state "s2"') assert expected in str(exc.value) with pytest.raises(StateMachineException) as exc: sm.add_transition(s2, s1, events='a') expected = ( 'Machine "sm" error: Unable to add transition from unknown state "s2"') assert expected in str(exc.value) with pytest.raises(StateMachineException) as exc: sm.add_transition(s1, s32, events='a') expected = ( 'Machine "sm" error: Unable to add transition to unknown state "s32"') assert expected in str(exc.value) with pytest.raises(StateMachineException) as exc: sm.add_transition(s32, s1, events='a') expected = ( 'Machine "sm" error: Unable to add transition from unknown state "s32"' ) assert expected in str(exc.value)
def test_add_transition_event_with_input(): sm = StateMachine('sm') s1 = State('s1') s2 = State('s2') s3 = State('s3') sm.add_state(s1, initial=True) sm.add_state(s2) sm.add_state(s3) sm.add_transition(s1, s2, events=['a'], input=['go_to_s2']) sm.add_transition(s1, s3, events=['a'], input=['go_to_s3']) sm.add_transition(s2, s1, events=['a']) sm.add_transition(s3, s1, events=['a']) sm.initialize() assert sm.state == s1 sm.dispatch(_e('a', input='go_to_s2')) assert sm.state == s2 sm.dispatch(_e('a')) assert sm.state == s1 sm.dispatch(_e('a', input='go_to_s3')) assert sm.state == s3 sm.dispatch(_e('a')) assert sm.state == s1
def test_state_machine_reference_present_in_event_with_nested_machines(): m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = State('s2') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s2, initial=True) def do(state, event): assert event.state_machine == m for state in s0, s1, s2: state.handlers = {'enter': do, 'exit': do, 'do': do} m.add_transition(s0, s2, events=['do']) m.initialize() m.dispatch(_e('do'))
def __init__(self): super().__init__('Heating') self.toasting = State('Toasting') self.baking = State('Baking') self.add_state(self.baking, initial=True) self.add_state(self.toasting)
def test_new_sm(): run_call_mock = mock.Mock() stop_call_mock = mock.Mock() idling_mock = mock.Mock() running_mock = mock.Mock() action_mock = mock.Mock() class Idling(State): # @event('run') def run(self, state, event): run_call_mock(self, event.input, event.cargo) def do(self, state, event): entity = event.cargo['entity'] entity.do() def on_enter(self, state, event): idling_mock(self, 'on_enter') def on_exit(self, state, event): idling_mock(self, 'on_exit') def register_handlers(self): self.handlers = { 'run': self.run, 'do': self.do, 'enter': self.on_enter, 'exit': self.on_exit, } def stop(state, event): stop_call_mock('stopping...', event.cargo) def do(state, event): entity = event.cargo['entity'] entity.do() def enter(state, event): running_mock('running, enter') def exit(state, event): running_mock('running, exit') def update(state, event): print('update', event) def do_on_transition(state, event): action_mock('action on transition') class Entity(object): def do(self): print(self, self.do) idling = Idling('idling') running = State('running') running.handlers = { 'stop': stop, 'do': do, 'update': update, 'enter': enter, 'exit': exit, } entity = Entity() sm = StateMachine('sm') sm.add_state(idling, initial=True) sm.add_state(running) sm.add_transition(idling, running, events=['run'], action=do_on_transition) sm.add_transition(running, idling, events=['stop']) sm.initialize() assert sm.state == idling sm.dispatch(_e('run')) assert sm.state == running assert run_call_mock.call_count == 1 assert run_call_mock.call_args[0] == (idling, None, {}) assert idling_mock.call_count == 1 assert idling_mock.call_args[0] == (idling, 'on_exit') assert running_mock.call_count == 1 assert running_mock.call_args[0] == ('running, enter',) assert action_mock.call_count == 1 assert action_mock.call_args[0] == ('action on transition',) # Nothing should happen - running state has no 'run' handler sm.dispatch(_e('run')) assert sm.state == running assert run_call_mock.call_count == 1 assert run_call_mock.call_args[0] == (idling, None, {}) sm.dispatch(_e('stop')) assert sm.state == idling assert idling_mock.call_count == 2 assert idling_mock.call_args[0] == (idling, 'on_enter') assert running_mock.call_count == 2 assert running_mock.call_args[0] == ('running, exit',) assert stop_call_mock.call_count == 1 assert stop_call_mock.call_args[0] == ('stopping...', {}) # Unknown events must be ignored sm.dispatch(_e('blah')) sm.dispatch(_e('blah blah')) assert sm.state == idling
def test_event_propagate(): data = [] def do(state, event): event.cargo['data'].append(state) def do_with_propagate(state, event): event.cargo['data'].append(state) event.propagate = True m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s3 = StateMachine('s3') s4 = State('s4') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s2, initial=True) s2.add_state(s3, initial=True) s3.add_state(s4, initial=True) for state in s0, s1, s2, s3, s4: state.handlers = {'do': do} m.initialize() # No propagate by default m.dispatch(_e('do', data=data)) assert data == [s4] # Should propagate only to the next state that can handle the event data = [] s4.handlers = {'do': do_with_propagate} m.dispatch(_e('do', data=data)) assert data == [s4, s3] data = [] s4.handlers = {'do': do_with_propagate} s3.handlers = {} m.dispatch(_e('do', data=data)) assert data == [s4, s2] data = [] s4.handlers = {'do': do_with_propagate} s3.handlers = {'do': do} m.dispatch(_e('do', data=data)) assert data == [s4, s3] data = [] s4.handlers = {} s3.handlers = {} m.dispatch(_e('do', data=data)) assert data == [s2] data = [] s4.handlers = {} s3.handlers = {} s2.handlers = {'do': do_with_propagate} m.dispatch(_e('do', data=data)) assert data == [s2, s1]