def test_add_not_a_state_instance(): class NotState(object): pass sm = StateMachine('sm') s1 = NotState() with pytest.raises(StateMachineException) as exc: sm.add_state(s1) expected = ('Machine "sm" error: Unable to add state of type') assert expected in str(exc.value)
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_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_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 create_regular_users(n): for i in range(n): index = randint(0, len(valid_ips) - 1) ip = valid_ips.pop(index) Thread(target=regular_user, args=[ip, "generic_username", StateMachine("generic_username")]).start()
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_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_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_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 _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_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_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_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_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_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_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_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_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_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
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_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
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_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 _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_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_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(): 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]
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()
print('action_l') def action_m(state, event): print('action_m') def action_n(state, event): print('action_n') def is_foo(state, event): return foo is True def is_not_foo(state, event): return foo is False m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s11 = State('s11') s21 = StateMachine('s21') s211 = State('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
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 _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 __init__(self): super().__init__() self.sm = StateMachine("sm")
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_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 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 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
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_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]