def test_blueprint_simple(self): states = ['A', 'B', 'C', 'D'] # Define with list of dictionaries transitions = [{ 'trigger': 'walk', 'source': 'A', 'dest': 'B' }, { 'trigger': 'run', 'source': 'B', 'dest': 'C' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'D' }] m = Machine(states=states, transitions=transitions, before_state_change='before_state_change', after_state_change='after_state_change', initial='A') self.assertEqual(len(m.blueprints['states']), 4) self.assertEqual(m.blueprints['states'][3], 'D') self.assertEqual(len(m.blueprints['transitions']), 3) self.assertEqual(m.blueprints['transitions'][2]['trigger'], 'sprint') m.add_transition('fly', 'D', 'A') self.assertEqual(len(m.blueprints['transitions']), 4) self.assertEqual(m.blueprints['transitions'][3]['source'], 'D')
def test_nested_agraph_diagram(self): ''' Same as above, but with nested states. ''' states = ['A', 'B', {'name': 'C', 'children': ['1', '2', '3']}, 'D'] transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'B'}, # 1 edge {'trigger': 'run', 'source': 'B', 'dest': 'C'}, # + 1 edge {'trigger': 'sprint', 'source': 'C', 'dest': 'D', # + 3 edges 'conditions': 'is_fast'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'B'} # + 3 edges = 8 edges ] m = HierarchicalMachine(states=states, transitions=transitions, initial='A', auto_transitions=False) graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) # Test that graph properties match the Machine # print((set(m.states.keys()), ) node_names = set([n.name for n in graph.nodes()]) self.assertEqual(set(m.states.keys()) - set('C'), node_names) triggers = set([n.attr['label'] for n in graph.edges()]) for t in triggers: self.assertIsNotNone(getattr(m, t)) self.assertEqual(len(graph.edges()), 8) # see above # write diagram to temp file target = tempfile.NamedTemporaryFile() graph.draw(target.name, prog='dot') self.assertTrue(os.path.getsize(target.name) > 0) # cleanup temp file target.close()
def test_pickle(self): import sys if sys.version_info < (3, 4): import dill as pickle else: import pickle states = ['A', 'B', 'C', 'D'] # Define with list of dictionaries transitions = [{ 'trigger': 'walk', 'source': 'A', 'dest': 'B' }, { 'trigger': 'run', 'source': 'B', 'dest': 'C' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'D' }] m = Machine(states=states, transitions=transitions, initial='A') m.walk() dump = pickle.dumps(m) self.assertIsNotNone(dump) m2 = pickle.loads(dump) self.assertEqual(m.state, m2.state) m2.run()
def test_blueprint_nested(self): c1 = NestedState('C_1', parent='C') c2 = NestedState('C_2', parent='C') c3 = NestedState('C_3', parent='C') c = NestedState('C', children=[c1, c2, c3]) states = ['A', {'name': 'B', 'on_enter': 'chirp', 'children': ['1', '2', '3']}, c, 'D'] # Define with list of dictionaries transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'B','before': 'before_state_change', 'after': 'after_state_change' }, {'trigger': 'run', 'source': 'B', 'dest': 'C'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'D'} ] m = Machine(states=states, transitions=transitions, before_state_change='before_state_change', after_state_change='after_state_change', initial='A') m.before_state_change = MagicMock() m.after_state_change = MagicMock() self.assertEqual(len(m.blueprints['states']), 4) self.assertEqual(m.blueprints['states'][3], 'D') self.assertEqual(len(m.blueprints['transitions']), 3) # transition 'walk' before should contain two calls of the same method self.assertEqual(len(m.blueprints['transitions'][0]['before']), 2) self.assertEqual(len(m.blueprints['transitions'][0]['after']), 2) self.assertEqual(len(m.blueprints['transitions'][1]['before']), 1) self.assertEqual(m.blueprints['transitions'][2]['trigger'], 'sprint') m.add_transition('fly', 'D', 'A') self.assertEqual(len(m.blueprints['transitions']), 4) self.assertEqual(m.blueprints['transitions'][3]['source'], 'D')
def test_nested_agraph_diagram(self): """ Same as above, but with nested states. """ states = ["A", "B", {"name": "C", "children": ["1", "2", "3"]}, "D"] transitions = [ {"trigger": "walk", "source": "A", "dest": "B"}, # 1 edge {"trigger": "run", "source": "B", "dest": "C"}, # + 1 edge {"trigger": "sprint", "source": "C", "dest": "D", "conditions": "is_fast"}, # + 3 edges {"trigger": "sprint", "source": "C", "dest": "B"}, # + 3 edges = 8 edges ] m = HierarchicalMachine(states=states, transitions=transitions, initial="A", auto_transitions=False) graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) # Test that graph properties match the Machine # print((set(m.states.keys()), ) node_names = set([n.name for n in graph.nodes()]) self.assertEqual(set(m.states.keys()) - set("C"), node_names) triggers = set([n.attr["label"] for n in graph.edges()]) for t in triggers: self.assertIsNotNone(getattr(m, t)) self.assertEqual(len(graph.edges()), 8) # see above # write diagram to temp file target = tempfile.NamedTemporaryFile() graph.draw(target.name, prog="dot") self.assertTrue(os.path.getsize(target.name) > 0) # cleanup temp file target.close()
def test_nested_agraph_diagram(self): ''' Same as above, but with nested states. ''' states = ['A', 'B', {'name': 'C', 'children': ['1', '2', '3']}, 'D'] transitions = [ { 'trigger': 'walk', 'source': 'A', 'dest': 'B' }, # 1 edge { 'trigger': 'run', 'source': 'B', 'dest': 'C' }, # + 1 edge { 'trigger': 'sprint', 'source': 'C', 'dest': 'D', # + 3 edges 'conditions': 'is_fast' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'B' } # + 3 edges = 8 edges ] m = HierarchicalMachine(states=states, transitions=transitions, initial='A', auto_transitions=False) graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) # Test that graph properties match the Machine # print((set(m.states.keys()), ) node_names = set([n.name for n in graph.nodes()]) self.assertEqual(set(m.states.keys()) - set('C'), node_names) triggers = set([n.attr['label'] for n in graph.edges()]) for t in triggers: self.assertIsNotNone(getattr(m, t)) self.assertEqual(len(graph.edges()), 8) # see above # write diagram to temp file target = tempfile.NamedTemporaryFile() graph.draw(target.name, prog='dot') self.assertTrue(os.path.getsize(target.name) > 0) # cleanup temp file target.close()
def test_blueprint_nested(self): c1 = NestedState('C_1', parent='C') c2 = NestedState('C_2', parent='C') c3 = NestedState('C_3', parent='C') c = NestedState('C', children=[c1, c2, c3]) states = [ 'A', { 'name': 'B', 'on_enter': 'chirp', 'children': ['1', '2', '3'] }, c, 'D' ] # Define with list of dictionaries transitions = [{ 'trigger': 'walk', 'source': 'A', 'dest': 'B', 'before': 'before_state_change', 'after': 'after_state_change' }, { 'trigger': 'run', 'source': 'B', 'dest': 'C' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'D' }] m = Machine(states=states, transitions=transitions, before_state_change='before_state_change', after_state_change='after_state_change', initial='A') m.before_state_change = MagicMock() m.after_state_change = MagicMock() self.assertEqual(len(m.blueprints['states']), 4) self.assertEqual(m.blueprints['states'][3], 'D') self.assertEqual(len(m.blueprints['transitions']), 3) # transition 'walk' before should contain two calls of the same method self.assertEqual(len(m.blueprints['transitions'][0]['before']), 2) self.assertEqual(len(m.blueprints['transitions'][0]['after']), 2) self.assertEqual(len(m.blueprints['transitions'][1]['before']), 1) self.assertEqual(m.blueprints['transitions'][2]['trigger'], 'sprint') m.add_transition('fly', 'D', 'A') self.assertEqual(len(m.blueprints['transitions']), 4) self.assertEqual(m.blueprints['transitions'][3]['source'], 'D')
def test_use_machine_as_model(self): states = ['A', 'B', 'C', 'D'] m = Machine(states=states, initial='A') m.add_transition('move', 'A', 'B') m.add_transition('move_to_C', 'B', 'C') m.move() self.assertEquals(m.state, 'B')
def test_callbacks_duplicate(self): transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'C', 'before': 'before_state_change', 'after': 'after_state_change'}, {'trigger': 'run', 'source': 'B', 'dest': 'C'} ] m = Machine(None, states=['A', 'B', 'C'], transitions=transitions, before_state_change='before_state_change', after_state_change='after_state_change', send_event=True, initial='A', auto_transitions=True) m.before_state_change = MagicMock() m.after_state_change = MagicMock() m.walk() self.assertEqual(m.before_state_change.call_count, 2) self.assertEqual(m.after_state_change.call_count, 2)
def test_blueprint_nested(self): states = ['A', {'name': 'B', 'on_enter': 'chirp', 'children': ['1', '2', '3']}, 'C', 'D'] # Define with list of dictionaries transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'B'}, {'trigger': 'run', 'source': 'B', 'dest': 'C'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'D'} ] m = Machine(states=states, transitions=transitions, before_state_change='before_state_change', after_state_change='after_state_change', initial='A') self.assertEqual(len(m.blueprints['states']), 4) self.assertEqual(m.blueprints['states'][3], 'D') self.assertEqual(len(m.blueprints['transitions']), 3) self.assertEqual(m.blueprints['transitions'][2]['trigger'], 'sprint') m.add_transition('fly', 'D', 'A') self.assertEqual(len(m.blueprints['transitions']), 4) self.assertEqual(m.blueprints['transitions'][3]['source'], 'D')
def test_pickle(self): import sys if sys.version_info < (3, 4): import dill as pickle else: import pickle states = ['A', 'B', 'C', 'D'] # Define with list of dictionaries transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'B'}, {'trigger': 'run', 'source': 'B', 'dest': 'C'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'D'} ] m = Machine(states=states, transitions=transitions, initial='A') m.walk() dump = pickle.dumps(m) self.assertIsNotNone(dump) m2 = pickle.loads(dump) self.assertEqual(m.state, m2.state) m2.run()
def test_blueprint_remap(self): states = ['1', '2', '3', 'finished'] transitions = [{ 'trigger': 'increase', 'source': '1', 'dest': '2' }, { 'trigger': 'increase', 'source': '2', 'dest': '3' }, { 'trigger': 'decrease', 'source': '3', 'dest': '2' }, { 'trigger': 'decrease', 'source': '1', 'dest': '1' }, { 'trigger': 'reset', 'source': '*', 'dest': '1' }, { 'trigger': 'done', 'source': '3', 'dest': 'finished' }] counter = Machine(states=states, transitions=transitions, initial='1') new_states = [ 'A', 'B', { 'name': 'C', 'children': counter, 'remap': { 'finished': 'A' } } ] new_transitions = [ { 'trigger': 'forward', 'source': 'A', 'dest': 'B' }, { 'trigger': 'forward', 'source': 'B', 'dest': 'C' }, { 'trigger': 'backward', 'source': 'C', 'dest': 'B' }, { 'trigger': 'backward', 'source': 'B', 'dest': 'A' }, { 'trigger': 'calc', 'source': '*', 'dest': 'C' }, ] walker = Machine(states=new_states, transitions=new_transitions, before_state_change='watch', after_state_change='look_back', initial='A') walker.watch = lambda: 'walk' walker.look_back = lambda: 'look_back' counter.increase() counter.increase() counter.done() self.assertEqual(counter.state, 'finished') with self.assertRaises(MachineError): walker.increase() self.assertEqual(walker.state, 'A') walker.forward() walker.forward() self.assertEqual(walker.state, 'C_1') walker.increase() self.assertEqual(walker.state, 'C_2') walker.reset() self.assertEqual(walker.state, 'C_1') walker.to_A() self.assertEqual(walker.state, 'A') walker.calc() self.assertEqual(walker.state, 'C_1') walker.increase() walker.increase() walker.done() self.assertEqual(walker.state, 'A')
def test_transition_definitions(self): states = ['A', 'B', {'name': 'C', 'children': ['1', '2', '3']}, 'D'] # Define with list of dictionaries transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'B'}, {'trigger': 'run', 'source': 'B', 'dest': 'C'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'D'}, {'trigger': 'run', 'source': 'C_1', 'dest': 'C_2'} ] m = Machine(states=states, transitions=transitions, initial='A') m.walk() self.assertEquals(m.state, 'B') m.run() self.assertEquals(m.state, 'C_1') m.run() self.assertEquals(m.state, 'C_2') # Define with list of lists transitions = [ ['walk', 'A', 'B'], ['run', 'B', 'C'], ['sprint', 'C', 'D'] ] m = Machine(states=states, transitions=transitions, initial='A') m.to_C() m.sprint() self.assertEquals(m.state, 'D')
def test_ordered_transitions(self): states = ['beginning', 'middle', 'end'] m = Machine(None, states) m.add_ordered_transitions() self.assertEquals(m.state, 'initial') m.next_state() self.assertEquals(m.state, 'beginning') m.next_state() m.next_state() self.assertEquals(m.state, 'end') m.next_state() self.assertEquals(m.state, 'initial') # Include initial state in loop m = Machine(None, states) m.add_ordered_transitions(loop_includes_initial=False) m.to_end() m.next_state() self.assertEquals(m.state, 'beginning') # Test user-determined sequence and trigger name m = Machine(None, states, initial='beginning') m.add_ordered_transitions(['end', 'beginning'], trigger='advance') m.advance() self.assertEquals(m.state, 'end') m.advance() self.assertEquals(m.state, 'beginning') # Via init argument m = Machine( None, states, initial='beginning', ordered_transitions=True) m.next_state() self.assertEquals(m.state, 'middle')
def test_blueprint_reuse(self): states = ['1', '2', '3'] transitions = [ {'trigger': 'increase', 'source': '1', 'dest': '2'}, {'trigger': 'increase', 'source': '2', 'dest': '3'}, {'trigger': 'decrease', 'source': '3', 'dest': '2'}, {'trigger': 'decrease', 'source': '1', 'dest': '1'}, {'trigger': 'reset', 'source': '*', 'dest': '1'}, ] counter = Machine(states=states, transitions=transitions, before_state_change='check', after_state_change='clear', initial='1') new_states = ['A', 'B', {'name':'C', 'children': counter}] new_transitions = [ {'trigger': 'forward', 'source': 'A', 'dest': 'B'}, {'trigger': 'forward', 'source': 'B', 'dest': 'C'}, {'trigger': 'backward', 'source': 'C', 'dest': 'B'}, {'trigger': 'backward', 'source': 'B', 'dest': 'A'}, {'trigger': 'calc', 'source': '*', 'dest': 'C'}, ] walker = Machine(states=new_states, transitions=new_transitions, before_state_change='watch', after_state_change='look_back', initial='A') walker.watch = lambda: 'walk' walker.look_back = lambda: 'look_back' walker.check = lambda: 'check' walker.clear = lambda: 'clear' with self.assertRaises(MachineError): walker.increase() self.assertEqual(walker.state, 'A') walker.forward() walker.forward() self.assertEqual(walker.state, 'C_1') walker.increase() self.assertEqual(walker.state, 'C_2') walker.reset() self.assertEqual(walker.state, 'C_1') walker.to_A() self.assertEqual(walker.state, 'A') walker.calc() self.assertEqual(walker.state, 'C_1')