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 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_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 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_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_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_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 _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_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 _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_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 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_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_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_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_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 _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_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]
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 _get_state_machine(self): #define the states robot = StateMachine('Robot') init = State('Init') orient = State('Orient') error = State('Error') off = State('Off') #add states to state machine robot.add_state(init, initial = True) robot.add_state(orient) robot.add_state(error) robot.add_state(off) #external transitions robot.add_transition(init, orient, events = ['loop_timeout'],action=self.loop_timer) robot.add_transition(init,error, events=['timeout']) robot.add_transition(orient,off, events=['oriented']) robot.add_transition(orient,error, events=['timeout']) #attach event handlers to each state init.handlers = {'enter': self.on_enter, 'exit': self.on_exit} orient.handlers = {'enter': self.on_orient_enter, 'loop_timeout': self.compareHeading, 'exit': self.on_exit} off.handlers = {'enter': self.on_enter, 'loop_timeout': self.Off_wait, 'exit': self.on_exit} error.handlers = {'enter': self.on_enter, 'exit': self.on_exit} # # define the events for event checker TODO: do I need this? # robot.events.append('odoRx') # robot.events.append('oriented') # robot.events.append('loop_timeout') # robot.events.append('timeout') robot.initialize() return robot
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_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_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 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 assert state == m.leaf_state 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 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_previous_state_with_source_event(): 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 = 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 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 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
class Statemachine1: def __init__(self): self.foo = True self.init_statemachine() def init_statemachine(self): self.m = StateMachine('m') self.s0 = StateMachine('s0') self.s1 = StateMachine('s1') self.s2 = StateMachine('s2') self.s11 = State('s11') self.s21 = StateMachine('s21') self.s211 = State('s211') m = self.m s0 = self.s0 s1 = self.s1 s2 = self.s2 s11 = self.s11 s21 = self.s21 s211 = self.s211 m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s0.add_state(s2) s1.add_state(s11, initial=True) s2.add_state(s21, initial=True) s21.add_state(s211, initial=True) # Internal transitions # beziehen events und angehängte actions, die auf den state gehen m.add_transition(s0, None, events='i', action=self.action_i) s0.add_transition(s1, None, events='j', action=self.action_j) s0.add_transition(s2, None, events='k', action=self.action_k) s1.add_transition(s11, None, events='h', condition=self.is_foo, action=self.unset_foo) s1.add_transition(s11, None, events='n', action=self.action_n) s21.add_transition(s211, None, events='m', action=self.action_m) s2.add_transition(s21, None, events='l', condition=self.is_foo, action=self.action_l) # External transition m.add_transition(s0, s211, events='e') s0.add_transition(s1, s0, events='d') s0.add_transition(s1, s11, events='b') s0.add_transition(s1, s1, events='a') s0.add_transition(s1, s211, events='f') s0.add_transition(s1, s2, events='c') s0.add_transition(s2, s11, events='f') s0.add_transition(s2, s1, events='c') s1.add_transition(s11, s211, events='g') s21.add_transition(s211, s0, events='g') s21.add_transition(s211, s21, events='d') s2.add_transition(s21, s211, events='b') s2.add_transition(s21, s21, events='h', condition=self.is_not_foo, action=self.set_foo) # Attach enter/exit handlers states = [m, s0, s1, s11, s2, s21, s211] for state in states: state.handlers = {'enter': self.on_enter, 'exit': self.on_exit} self.m.initialize() def on_enter(self, state, event): print('enter state {0}'.format(state.name)) def on_exit(self, state, event): print('exit state {0}'.format(state.name)) def set_foo(self, state, event): global foo print('set foo') self.foo = True def unset_foo(self, state, event): global foo print('unset foo') self.foo = False def action_i(self, state, event): print('action_i') def action_j(self, state, event): print('action_j') def action_k(self, state, event): print('action_k') def action_l(self, state, event): print('action_l') def action_m(self, state, event): print('action_m') def action_n(self, state, event): print('action_n') def is_foo(self, state, event): return self.foo is True def is_not_foo(self, state, event): return self.foo is False def test(self): m = self.m assert m.leaf_state == self.s11 m.dispatch(Event('a')) assert m.leaf_state == self.s11 # This transition toggles state between s11 and s211 m.dispatch(Event('c')) assert m.leaf_state == self.s211 m.dispatch(Event('b')) assert m.leaf_state == self.s211 m.dispatch(Event('i')) assert m.leaf_state == self.s211 m.dispatch(Event('c')) assert m.leaf_state == self.s11 assert self.foo is True m.dispatch(Event('h')) assert self.foo is False assert m.leaf_state == self.s11 # Do nothing if foo is False m.dispatch(Event('h')) assert m.leaf_state == self.s11 # This transition toggles state between s11 and s211 m.dispatch(Event('c')) assert m.leaf_state == self.s211 assert self.foo is False m.dispatch(Event('h')) assert self.foo is True assert m.leaf_state == self.s211 m.dispatch(Event('h')) assert m.leaf_state == self.s211
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
s0.add_transition(s1, s211, events='f') s0.add_transition(s1, s2, events='c') s0.add_transition(s2, s11, events='f') s0.add_transition(s2, s1, events='c') s1.add_transition(s11, s211, events='g') s21.add_transition(s211, s0, events='g') s21.add_transition(s211, s21, events='d') s2.add_transition(s21, s211, events='b') s2.add_transition(s21, s21, events='h', condition=is_not_foo, action=set_foo) # Attach enter/exit handlers states = [m, s0, s1, s11, s2, s21, s211] for state in states: state.handlers = {'enter': on_enter, 'exit': on_exit} m.initialize() def test(): assert m.leaf_state == s11 m.dispatch(Event('a')) assert m.leaf_state == s11 # This transition toggles state between s11 and s211 m.dispatch(Event('c')) assert m.leaf_state == s211 m.dispatch(Event('b')) assert m.leaf_state == s211 m.dispatch(Event('i')) assert m.leaf_state == s211 m.dispatch(Event('c')) assert m.leaf_state == s11
def test_conditions(): class Bool(object): def __init__(self): self.value = True def get(self, state, event): return self.value bool_a = Bool() bool_b = Bool() def run(event): print('runninng...') idling = State('idling') running = State('running') stopped = State('stopped') broken = State('broken') sm = StateMachine('sm') sm.add_state(idling, initial=True) sm.add_state(running) sm.add_state(stopped) sm.add_state(broken) sm.add_transition(idling, running, events=['run'], condition=bool_a.get) sm.add_transition(idling, stopped, events=['run'], condition=bool_b.get) sm.add_transition(running, idling, events=['idle']) sm.add_transition(stopped, idling, events=['idle']) sm.add_transition(broken, idling, events=['idle']) sm.initialize() # Expect no change bool_a.value = False bool_b.value = False assert sm.state == idling sm.dispatch(_e('run')) assert sm.state == idling sm.dispatch(_e('idle')) assert sm.state == idling # Expect first transition bool_a.value = True bool_b.value = False assert sm.state == idling sm.dispatch(_e('run')) assert sm.state == running sm.dispatch(_e('idle')) assert sm.state == idling # Expect first transition bool_a.value = True bool_b.value = True assert sm.state == idling sm.dispatch(_e('run')) assert sm.state == running sm.dispatch(_e('idle')) assert sm.state == idling # Expect second transition bool_a.value = False bool_b.value = True assert sm.state == idling sm.dispatch(_e('run')) assert sm.state == stopped sm.dispatch(_e('idle')) assert sm.state == idling sm.add_transition(idling, broken, events=['run']) # Expect transition to state without condition bool_a.value = False bool_b.value = False assert sm.state == idling sm.dispatch(_e('run')) assert sm.state == broken sm.dispatch(_e('idle')) assert sm.state == idling
from pysm import State, StateMachine, Event on = State('on') off = State('off') sm = StateMachine('sm') sm.add_state(on, initial=True) sm.add_state(off) sm.add_transition(on, off, events=['off']) sm.add_transition(off, on, events=['on']) sm.initialize() def test(): assert sm.state == on sm.dispatch(Event('off')) assert sm.state == off sm.dispatch(Event('on')) assert sm.state == on if __name__ == '__main__': test()
def test_state_instance_passed_to_an_event_handler(): m = StateMachine('m') s0 = StateMachine('s0') s1 = State('s1') s2 = State('s2') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s0.add_state(s2) def on_enter(state, event): source_event = event.cargo['source_event'] source_event.cargo['test_list'].append(('enter', state)) assert state == m.leaf_state def on_exit(state, event): source_event = event.cargo['source_event'] source_event.cargo['test_list'].append(('exit', state)) assert state == m.leaf_state def before(state, event): assert state == s1 assert state == m.leaf_state def action(state, event): assert state == s1 assert state == m.leaf_state def after(state, event): assert state == s2 assert state == m.leaf_state def on_internal(state, event): assert state == s1 assert state == m.leaf_state def do(state, event): # It's an action on the top machine, the `leaf_state` is NOT `state` assert state != m.leaf_state assert state == m def condition(state, event): assert state == s1 assert state == m.leaf_state return True m.add_transition(s0, s0, events='a') s0.add_transition(s1, None, events=['internal'], before=on_internal, action=on_internal, after=on_internal) s0.add_transition(s1, s2, events='b', before=before, action=action, after=after, condition=condition) s0.add_transition(s2, s1, events='b') for state in [m, s0, s1, s2]: state.handlers = {'enter': on_enter, 'exit': on_exit} m.handlers = {'do': do} m.initialize() test_list = [] m.dispatch(_e('a', test_list=test_list)) assert test_list == [('exit', s1), ('exit', s0), ('enter', s0), ('enter', s1)] m.dispatch(_e('b', test_list=test_list)) m.dispatch(_e('do')) m.dispatch(_e('b', test_list=test_list)) m.dispatch(_e('internal'))
def test_enter_exit_on_transitions(): test_list = [] m = StateMachine('m') # exit = m.add_state('exit', terminal=True) s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') def on_enter(state, event): assert state == m.leaf_state test_list.append(('enter', state)) def on_exit(state, event): assert state == m.leaf_state test_list.append(('exit', state)) s11 = State('s11') s21 = StateMachine('s21') s211 = State('s211') s212 = State('s212') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s0.add_state(s2) s1.add_state(s11, initial=True) s2.add_state(s21, initial=True) s21.add_state(s211, initial=True) s21.add_state(s212) states = [m, s0, s1, s11, s2, s21, s211, s212] for state in states: state.handlers = {'enter': on_enter, 'exit': on_exit} s0.add_transition(s1, s1, events='a') s0.add_transition(s1, s11, events='b') s2.add_transition(s21, s211, events='b') s0.add_transition(s1, s2, events='c') s0.add_transition(s2, s1, events='c') s0.add_transition(s1, s0, events='d') s21.add_transition(s211, s21, events='d') m.add_transition(s0, s211, events='e') m.add_transition(s0, s212, events='z') s0.add_transition(s2, s11, events='f') s0.add_transition(s1, s211, events='f') s1.add_transition(s11, s211, events='g') s21.add_transition(s211, s0, events='g') m.initialize() test_list[:] = [] m.dispatch(_e('a')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s1), ('enter', s11)] test_list[:] = [] m.dispatch(_e('b')) assert test_list == [('exit', s11), ('enter', s11)] m.dispatch(_e('c')) test_list[:] = [] m.dispatch(_e('b')) assert test_list == [('exit', s211), ('enter', s211)] m.dispatch(_e('c')) test_list[:] = [] m.dispatch(_e('c')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s2), ('enter', s21), ('enter', s211)] test_list[:] = [] m.dispatch(_e('c')) assert test_list == [('exit', s211), ('exit', s21), ('exit', s2), ('enter', s1), ('enter', s11)] test_list[:] = [] m.dispatch(_e('d')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s1), ('enter', s11)] m.dispatch(_e('c')) test_list[:] = [] m.dispatch(_e('d')) assert test_list == [('exit', s211), ('enter', s211)] m.dispatch(_e('c')) test_list[:] = [] m.dispatch(_e('e')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s2), ('enter', s21), ('enter', s211)] test_list[:] = [] m.dispatch(_e('e')) assert test_list == [('exit', s211), ('exit', s21), ('exit', s2), ('enter', s2), ('enter', s21), ('enter', s211)] test_list[:] = [] m.dispatch(_e('f')) assert test_list == [('exit', s211), ('exit', s21), ('exit', s2), ('enter', s1), ('enter', s11)] test_list[:] = [] m.dispatch(_e('f')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s2), ('enter', s21), ('enter', s211)] test_list[:] = [] m.dispatch(_e('g')) assert test_list == [('exit', s211), ('exit', s21), ('exit', s2), ('enter', s1), ('enter', s11)] test_list[:] = [] m.dispatch(_e('g')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s2), ('enter', s21), ('enter', s211)] test_list[:] = [] m.dispatch(_e('z')) assert test_list == [('exit', s211), ('exit', s21), ('exit', s2), ('enter', s2), ('enter', s21), ('enter', s212)] assert m.leaf_state == s212 test_list[:] = [] m.dispatch(_e('c')) assert test_list == [('exit', s212), ('exit', s21), ('exit', s2), ('enter', s1), ('enter', s11)] assert m.leaf_state == s11 test_list[:] = [] m.dispatch(_e('g')) assert m.leaf_state == s211 assert test_list == [('exit', s11), ('exit', s1), ('enter', s2), ('enter', s21), ('enter', s211)] assert m.leaf_state == s211
def test_internal_vs_external_transitions(): test_list = [] class Foo(object): value = True def on_enter(state, event): test_list.append(('enter', state)) def on_exit(state, event): test_list.append(('exit', state)) def set_foo(state, event): Foo.value = True test_list.append('set_foo') def unset_foo(state, event): Foo.value = False test_list.append('unset_foo') def action_i(state, event): test_list.append('action_i') return True def action_j(state, event): test_list.append('action_j') return True def action_k(state, event): test_list.append('action_k') return True def action_l(state, event): test_list.append('action_l') def action_m(state, event): test_list.append('action_m') def action_n(state, event): test_list.append('action_n') return True m = StateMachine('m') # exit = m.add_state('exit', terminal=True) s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s11 = State('s11') s21 = StateMachine('s21') s211 = State('s211') s212 = State('s212') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s0.add_state(s2) s1.add_state(s11, initial=True) s2.add_state(s21, initial=True) s21.add_state(s211, initial=True) s21.add_state(s212) states = [m, s0, s1, s11, s2, s21, s211, s212] for state in states: state.handlers = {'enter': on_enter, 'exit': on_exit} s0.add_transition(s1, s1, events='a') s0.add_transition(s1, s11, events='b') s2.add_transition(s21, s211, events='b') s0.add_transition(s1, s2, events='c') s0.add_transition(s2, s1, events='c') s0.add_transition(s1, s0, events='d') s21.add_transition(s211, s21, events='d') m.add_transition(s0, s211, events='e') m.add_transition(s0, s212, events='z') s0.add_transition(s2, s11, events='f') s0.add_transition(s1, s211, events='f') s1.add_transition(s11, s211, events='g') s21.add_transition(s211, s0, events='g') m.initialize() # Internal transitions m.add_transition(s0, None, events='i', action=action_i) s0.add_transition(s1, None, events='j', action=action_j) s0.add_transition(s2, None, events='k', action=action_k) s1.add_transition(s11, None, events='n', action=action_n) s1.add_transition(s11, None, events='h', condition=lambda s, e: Foo.value is True, action=unset_foo) s2.add_transition(s21, None, events='l', condition=lambda s, e: Foo.value is True, action=action_l) s21.add_transition(s211, None, events='m', action=action_m) # External transition s2.add_transition(s21, s21, events='h', condition=lambda s, e: Foo.value is False, action=set_foo) m.initialize() test_list[:] = [] m.dispatch(_e('i')) assert test_list == ['action_i'] assert m.leaf_state == s11 test_list[:] = [] m.dispatch(_e('j')) assert test_list == ['action_j'] assert m.leaf_state == s11 test_list[:] = [] m.dispatch(_e('n')) assert test_list == ['action_n'] assert m.leaf_state == s11 # This transition toggles state between s11 and s211 m.dispatch(_e('c')) assert m.leaf_state == s211 test_list[:] = [] m.dispatch(_e('i')) assert test_list == ['action_i'] assert m.leaf_state == s211 test_list[:] = [] m.dispatch(_e('k')) assert test_list == ['action_k'] assert m.leaf_state == s211 test_list[:] = [] m.dispatch(_e('m')) assert test_list == ['action_m'] assert m.leaf_state == s211 test_list[:] = [] m.dispatch(_e('n')) assert test_list == [] assert m.leaf_state == s211 # This transition toggles state between s11 and s211 m.dispatch(_e('c')) assert m.leaf_state == s11 test_list[:] = [] assert Foo.value is True m.dispatch(_e('h')) assert Foo.value is False assert test_list == ['unset_foo'] assert m.leaf_state == s11 test_list[:] = [] m.dispatch(_e('h')) assert test_list == [] # Do nothing if foo is False assert m.leaf_state == s11 # This transition toggles state between s11 and s211 m.dispatch(_e('c')) assert m.leaf_state == s211 test_list[:] = [] assert Foo.value is False m.dispatch(_e('h')) assert test_list == [('exit', s211), ('exit', s21), 'set_foo', ('enter', s21), ('enter', s211)] assert Foo.value is True assert m.leaf_state == s211 test_list[:] = [] m.dispatch(_e('h')) assert test_list == [] assert m.leaf_state == s211
def test_state_instance_passed_to_an_event_handler(): def on_enter(state, event): source_event = event.cargo['source_event'] source_event.cargo['test_list'].append(('enter', state)) def on_exit(state, event): source_event = event.cargo['source_event'] source_event.cargo['test_list'].append(('exit', state)) def before(state, event): assert state == s1 def action(state, event): assert state == s1 def after(state, event): assert state == s2 def on_internal(state, event): assert state == s1 def do(state, event): assert state == m def condition(state, event): assert state == s1 return True m = StateMachine('m') s0 = StateMachine('s0') s1 = State('s1') s2 = State('s2') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s0.add_state(s2) m.add_transition(s0, s0, events='a') s0.add_transition(s1, None, events=['internal'], before=on_internal, action=on_internal, after=on_internal) s0.add_transition(s1, s2, events='b', before=before, action=action, after=after, condition=condition) s0.add_transition(s2, s1, events='b') for state in [m, s0, s1, s2]: state.handlers = {'enter': on_enter, 'exit': on_exit} m.handlers = {'do': do} m.initialize() test_list = [] m.dispatch(_e('a', test_list=test_list)) assert test_list == [('exit', s1), ('exit', s0), ('enter', s0), ('enter', s1)] m.dispatch(_e('b', test_list=test_list)) m.dispatch(_e('do')) m.dispatch(_e('b', test_list=test_list)) m.dispatch(_e('internal'))
def test_enter_exit_on_transitions(): test_list = [] def on_enter(state, event): test_list.append(('enter', state)) def on_exit(state, event): test_list.append(('exit', state)) m = StateMachine('m') # exit = m.add_state('exit', terminal=True) s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s11 = State('s11') s21 = StateMachine('s21') s211 = State('s211') s212 = State('s212') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s0.add_state(s2) s1.add_state(s11, initial=True) s2.add_state(s21, initial=True) s21.add_state(s211, initial=True) s21.add_state(s212) states = [m, s0, s1, s11, s2, s21, s211, s212] for state in states: state.handlers = {'enter': on_enter, 'exit': on_exit} s0.add_transition(s1, s1, events='a') s0.add_transition(s1, s11, events='b') s2.add_transition(s21, s211, events='b') s0.add_transition(s1, s2, events='c') s0.add_transition(s2, s1, events='c') s0.add_transition(s1, s0, events='d') s21.add_transition(s211, s21, events='d') m.add_transition(s0, s211, events='e') m.add_transition(s0, s212, events='z') s0.add_transition(s2, s11, events='f') s0.add_transition(s1, s211, events='f') s1.add_transition(s11, s211, events='g') s21.add_transition(s211, s0, events='g') m.initialize() test_list[:] = [] m.dispatch(_e('a')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s1), ('enter', s11)] test_list[:] = [] m.dispatch(_e('b')) assert test_list == [('exit', s11), ('enter', s11)] m.dispatch(_e('c')) test_list[:] = [] m.dispatch(_e('b')) assert test_list == [('exit', s211), ('enter', s211)] m.dispatch(_e('c')) test_list[:] = [] m.dispatch(_e('c')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s2), ('enter', s21), ('enter', s211)] test_list[:] = [] m.dispatch(_e('c')) assert test_list == [('exit', s211), ('exit', s21), ('exit', s2), ('enter', s1), ('enter', s11)] test_list[:] = [] m.dispatch(_e('d')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s1), ('enter', s11)] m.dispatch(_e('c')) test_list[:] = [] m.dispatch(_e('d')) assert test_list == [('exit', s211), ('enter', s211)] m.dispatch(_e('c')) test_list[:] = [] m.dispatch(_e('e')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s2), ('enter', s21), ('enter', s211)] test_list[:] = [] m.dispatch(_e('e')) assert test_list == [('exit', s211), ('exit', s21), ('exit', s2), ('enter', s2), ('enter', s21), ('enter', s211)] test_list[:] = [] m.dispatch(_e('f')) assert test_list == [('exit', s211), ('exit', s21), ('exit', s2), ('enter', s1), ('enter', s11)] test_list[:] = [] m.dispatch(_e('f')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s2), ('enter', s21), ('enter', s211)] test_list[:] = [] m.dispatch(_e('g')) assert test_list == [('exit', s211), ('exit', s21), ('exit', s2), ('enter', s1), ('enter', s11)] test_list[:] = [] m.dispatch(_e('g')) assert test_list == [('exit', s11), ('exit', s1), ('enter', s2), ('enter', s21), ('enter', s211)] test_list[:] = [] m.dispatch(_e('z')) assert test_list == [('exit', s211), ('exit', s21), ('exit', s2), ('enter', s2), ('enter', s21), ('enter', s212)] assert m.leaf_state == s212 test_list[:] = [] m.dispatch(_e('c')) assert test_list == [('exit', s212), ('exit', s21), ('exit', s2), ('enter', s1), ('enter', s11)] assert m.leaf_state == s11 test_list[:] = [] m.dispatch(_e('g')) assert m.leaf_state == s211 assert test_list == [('exit', s11), ('exit', s1), ('enter', s2), ('enter', s21), ('enter', s211)] assert m.leaf_state == s211