class TestFactory(TestCase): def setUp(self): self.factory = MachineFactory() def test_mixins(self): machine_cls = self.factory.get_predefined() self.assertFalse(hasattr(machine_cls, 'set_edge_state')) graph_cls = self.factory.get_predefined(graph=True) self.assertTrue(hasattr(graph_cls, 'set_edge_state')) nested_cls = self.factory.get_predefined(nested=True) self.assertFalse(hasattr(nested_cls, 'set_edge_state')) self.assertTrue(hasattr(nested_cls, 'traverse')) locked_cls = self.factory.get_predefined(locked=True) self.assertFalse(hasattr(locked_cls, 'set_edge_state')) self.assertFalse(hasattr(locked_cls, 'traverse')) self.assertTrue('__getattribute__' in locked_cls.__dict__) locked_nested_cls = self.factory.get_predefined(nested=True, locked=True) self.assertFalse(hasattr(locked_nested_cls, 'set_edge_state')) self.assertTrue(hasattr(locked_nested_cls, 'traverse')) self.assertEqual(locked_nested_cls.__getattribute__, locked_cls.__getattribute__) self.assertNotEqual(machine_cls.__getattribute__, locked_cls.__getattribute__) graph_locked_cls = self.factory.get_predefined(graph=True, locked=True) self.assertTrue(hasattr(graph_locked_cls, 'set_edge_state')) self.assertEqual(graph_locked_cls.__getattribute__, locked_cls.__getattribute__) graph_nested_cls = self.factory.get_predefined(graph=True, nested=True) self.assertNotEqual(nested_cls._create_transition, graph_nested_cls._create_transition) locked_nested_graph_cls = self.factory.get_predefined(nested=True, locked=True, graph=True) self.assertNotEqual(locked_nested_graph_cls._create_event, graph_cls._create_event)
def setUp(self): NestedState.separator = '_' states = ['A', 'B', {'name': 'C', 'children': ['1', '2', {'name': '3', 'children': ['a', 'b', 'c']}]}, 'D', 'E', 'F'] self.stuff = Stuff(states, machine_cls=MachineFactory.get_predefined(locked=True, graph=True, nested=True)) self.stuff.heavy_processing = heavy_processing self.stuff.machine.add_transition('forward', '*', 'B', before='heavy_processing')
def test_agraph_diagram(self): states = ['A', 'B', 'C', 'D'] transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'B'}, {'trigger': 'run', 'source': 'B', 'dest': 'C'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'D', 'conditions': 'is_fast'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'B'} ] machine_cls = MachineFactory.get_predefined(graph=True) m = machine_cls(states=states, transitions=transitions, initial='A', auto_transitions=False, title='a test') graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) # Test that graph properties match the Machine self.assertEqual( set(m.states.keys()), set([n.name for n in graph.nodes()])) triggers = set([n.attr['label'] for n in graph.edges()]) for t in triggers: self.assertIsNotNone(getattr(m, t)) self.assertEqual(len(graph.edges()), len(transitions)) # check for a valid pygraphviz diagram # 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() print(graph)
def setUp(self): State.separator = state_separator states = ['A', 'B', {'name': 'C', 'children': ['1', '2', {'name': '3', 'children': ['a', 'b', 'c']}]}, 'D', 'E', 'F'] machine_cls = MachineFactory.get_predefined(graph=True, nested=True) self.stuff = Stuff(states, machine_cls)
def setUp(self): self.machine_cls = MachineFactory.get_predefined(graph=True) self.states = ['A', 'B', 'C', 'D'] self.transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'B'}, {'trigger': 'run', 'source': 'B', 'dest': 'C'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'D', 'conditions': 'is_fast'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'B'} ]
def test_multiple_models(self): class Model(object): pass s1, s2 = Model(), Model() m = MachineFactory.get_predefined(nested=True)(model=[s1, s2], states=['A', 'B', 'C'], initial='A') self.assertEquals(len(m.models), 2) m.add_transition('advance', 'A', 'B') self.assertNotEqual(s1.advance, s2.advance) s1.advance() self.assertEquals(s1.state, 'B') self.assertEquals(s2.state, 'A')
def setUp(self): self.machine_cls = MachineFactory.get_predefined(graph=True, nested=True) self.states = ['A', 'B', {'name': 'C', 'children': [{'name': '1', 'children': ['a', 'b', 'c']}, '2', '3']}, 'D'] self.transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'B'}, # 1 edge {'trigger': 'run', 'source': 'B', 'dest': 'C'}, # + 1 edge {'trigger': 'sprint', 'source': 'C', 'dest': 'D', # + 1 edge 'conditions': 'is_fast'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'B'}, # + 1 edge {'trigger': 'reset', 'source': '*', 'dest': 'A'}] # + 8 edges = 12
def setUp(self): states = [ 'A', 'B', { 'name': 'C', 'children': ['1', '2', { 'name': '3', 'children': ['a', 'b', 'c'] }] }, 'D', 'E', 'F' ] machine_cls = MachineFactory.get_predefined(nested=True) self.stuff = Stuff(states, machine_cls)
def test_add_custom_state(self): states = ['A', 'B', 'C', 'D'] transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'B'}, {'trigger': 'run', 'source': 'B', 'dest': 'C'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'D', 'conditions': 'is_fast'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'B'} ] machine_cls = MachineFactory.get_predefined(graph=True) m = machine_cls(states=states, transitions=transitions, initial='A', auto_transitions=False, title='a test') m.add_state('X') m.add_transition('foo', '*', 'X') m.foo()
def test_agraph_diagram(self): states = ['A', 'B', 'C', 'D'] transitions = [{ 'trigger': 'walk', 'source': 'A', 'dest': 'B' }, { 'trigger': 'run', 'source': 'B', 'dest': 'C' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'D', 'conditions': 'is_fast' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'B' }] machine_cls = MachineFactory.get_predefined(graph=True) m = machine_cls(states=states, transitions=transitions, initial='A', auto_transitions=False, title='a test') graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) # Test that graph properties match the Machine self.assertEqual(set(m.states.keys()), set([n.name for n in graph.nodes()])) triggers = set([n.attr['label'] for n in graph.edges()]) for t in triggers: t = edge_label_from_transition_label(t) self.assertIsNotNone(getattr(m, t)) self.assertEqual(len(graph.edges()), len(transitions)) # check for a valid pygraphviz diagram # 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() print(graph)
def test_multiple_models(self): class Model(object): pass s1, s2 = Model(), Model() m = MachineFactory.get_predefined(nested=True)(model=[s1, s2], states=['A', 'B', 'C'], initial='A') self.assertEqual(len(m.models), 2) m.add_transition('advance', 'A', 'B') self.assertNotEqual(s1.advance, s2.advance) s1.advance() self.assertEqual(s1.state, 'B') self.assertEqual(s2.state, 'A')
def test_reuse_machine_config(self): simple_config = { "name": "Child", "states": ["1", "2"], "transitions": [['go', '1', '2']], "initial": "1" } simple_cls = MachineFactory.get_predefined() simple = simple_cls(**simple_config) self.assertTrue(simple.is_1()) self.assertTrue(simple.go()) self.assertTrue(simple.is_2()) machine = self.machine_cls(states=['A', simple_config], initial='A') machine.to_Child() machine.go() self.assertTrue(machine.is_Child_2())
def test_ordered_with_graph(self): GraphMachine = MachineFactory.get_predefined(graph=True, nested=True) states = ['A', 'B', {'name': 'C', 'children': ['1', '2', {'name': '3', 'children': ['a', 'b', 'c']}]}, 'D', 'E', 'F'] State.separator = '/' machine = GraphMachine('self', states, initial='A', auto_transitions=False, ignore_invalid_triggers=True) machine.add_ordered_transitions(trigger='next_state') machine.next_state() self.assertEqual(machine.state, 'B') target = tempfile.NamedTemporaryFile() machine.get_graph().draw(target.name, prog='dot') self.assertTrue(getsize(target.name) > 0) target.close()
def setUp(self): self.event_list = [] self.s1 = DummyModel() self.c1 = TestContext(event_list=self.event_list) self.c2 = TestContext(event_list=self.event_list) self.c3 = TestContext(event_list=self.event_list) self.c4 = TestContext(event_list=self.event_list) self.stuff = Stuff(machine_cls=MachineFactory.get_predefined(locked=True), extra_kwargs={ 'machine_context': [self.c1, self.c2] }) self.stuff.machine.add_model(self.s1, model_context=[self.c3, self.c4]) del self.event_list[:] self.stuff.machine.add_transition('forward', 'A', 'B')
def setUp(self): self.event_list = [] self.s1 = self.DummyModel() self.c1 = self.TestContext(event_list=self.event_list) self.c2 = self.TestContext(event_list=self.event_list) self.c3 = self.TestContext(event_list=self.event_list) self.c4 = self.TestContext(event_list=self.event_list) self.stuff = Stuff( machine_cls=MachineFactory.get_predefined(locked=True), extra_kwargs={'machine_context': [self.c1, self.c2]}) self.stuff.machine.add_model(self.s1, model_context=[self.c3, self.c4]) del self.event_list[:] self.stuff.machine.add_transition('forward', 'A', 'B')
def test_ordered_with_graph(self): GraphMachine = MachineFactory.get_predefined(graph=True, nested=True) states = ['A', 'B', {'name': 'C', 'children': ['1', '2', {'name': '3', 'children': ['a', 'b', 'c']}]}, 'D', 'E', 'F'] State.separator = '/' machine = GraphMachine('self', states, initial='A', auto_transitions=False, ignore_invalid_triggers=True) machine.add_ordered_transitions(trigger='next_state') machine.next_state() self.assertEqual(machine.state, 'B') target = tempfile.NamedTemporaryFile() machine.get_graph().draw(target.name, prog='dot') self.assertTrue(getsize(target.name) > 0) target.close()
class TestFactory(TestCase): def setUp(self): self.factory = MachineFactory() def test_mixins(self): machine_cls = self.factory.get_predefined() self.assertFalse(hasattr(machine_cls, 'set_edge_state')) graph_cls = self.factory.get_predefined(graph=True) self.assertTrue(hasattr(graph_cls, 'set_edge_state')) nested_cls = self.factory.get_predefined(nested=True) self.assertFalse(hasattr(nested_cls, 'set_edge_state')) self.assertTrue(hasattr(nested_cls, '_traverse')) locked_cls = self.factory.get_predefined(locked=True) self.assertFalse(hasattr(locked_cls, 'set_edge_state')) self.assertFalse(hasattr(locked_cls, '_traverse')) self.assertTrue('__getattribute__' in locked_cls.__dict__) locked_nested_cls = self.factory.get_predefined(nested=True, locked=True) self.assertFalse(hasattr(locked_nested_cls, 'set_edge_state')) self.assertTrue(hasattr(locked_nested_cls, '_traverse')) self.assertEqual(locked_nested_cls.__getattribute__, locked_cls.__getattribute__) self.assertNotEqual(machine_cls.__getattribute__, locked_cls.__getattribute__) graph_locked_cls = self.factory.get_predefined(graph=True, locked=True) self.assertTrue(hasattr(graph_locked_cls, 'set_edge_state')) self.assertEqual(graph_locked_cls.__getattribute__, locked_cls.__getattribute__) graph_nested_cls = self.factory.get_predefined(graph=True, nested=True) self.assertNotEqual(nested_cls._create_transition, graph_nested_cls._create_transition) locked_nested_graph_cls = self.factory.get_predefined(nested=True, locked=True, graph=True) self.assertNotEqual(locked_nested_graph_cls._create_event, graph_cls._create_event)
def test_store_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', # + 1 edges 'conditions': 'is_fast'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'B'} # + 1 edges = 4 edges ] hsm_graph_cls = MachineFactory.get_predefined(graph=True, nested=True) m = hsm_graph_cls(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: t = edge_label_from_transition_label(t) self.assertIsNotNone(getattr(m, t)) self.assertEqual(len(graph.edges()), 4) # see above # Force a new graph2 = m.get_graph(title="Second Graph", force_new=True) self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) self.assertFalse(graph == graph2) # 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 setUp(self): states = [ 'A', 'B', { 'name': 'C', 'children': ['1', '2', { 'name': '3', 'children': ['a', 'b', 'c'] }] }, 'D', 'E', 'F' ] self.stuff = Stuff(states, machine_cls=MachineFactory.get_predefined( locked=True, nested=True)) self.stuff.heavy_processing = heavy_processing self.stuff.machine.add_transition('process', '*', 'B', before='heavy_processing')
def test_store_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', # + 1 edges 'conditions': 'is_fast'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'B'} # + 1 edges = 4 edges ] hsm_graph_cls = MachineFactory.get_predefined(graph=True, nested=True) m = hsm_graph_cls(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: t = edge_label_from_transition_label(t) self.assertIsNotNone(getattr(m, t)) self.assertEqual(len(graph.edges()), 4) # see above # Force a new graph2 = m.get_graph(title="Second Graph", force_new=True) self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) self.assertFalse(graph == graph2) # 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_ordered_with_graph(self): GraphMachine = MachineFactory.get_predefined(graph=True, nested=True) class CustomHierarchicalGraphMachine(GraphMachine): state_cls = self.state_cls states = ['A', 'B', {'name': 'C', 'children': ['1', '2', {'name': '3', 'children': ['a', 'b', 'c']}]}, 'D', 'E', 'F'] machine = CustomHierarchicalGraphMachine('self', states, initial='A', auto_transitions=False, ignore_invalid_triggers=True, use_pygraphviz=False) machine.add_ordered_transitions(trigger='next_state') machine.next_state() self.assertEqual(machine.state, 'B') target = tempfile.NamedTemporaryFile(suffix='.png', delete=False) machine.get_graph().draw(target.name, prog='dot') self.assertTrue(getsize(target.name) > 0) target.close() unlink(target.name)
def setUp(self): self.machine_cls = MachineFactory.get_predefined(graph=True, nested=True) self.states = [ 'A', 'B', { 'name': 'C', 'children': [{ 'name': '1', 'children': ['a', 'b', 'c'] }, '2', '3'] }, 'D' ] self.transitions = [ { 'trigger': 'walk', 'source': 'A', 'dest': 'B' }, # 1 edge { 'trigger': 'run', 'source': 'B', 'dest': 'C' }, # + 1 edge { 'trigger': 'sprint', 'source': 'C', 'dest': 'D', # + 1 edge 'conditions': 'is_fast' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'B' }, # + 1 edge { 'trigger': 'reset', 'source': '*', 'dest': 'A' } ] # + 10 (8 nodes; 2 cluster) edges = 14
def setUp(self): states = [ 'A', 'B', { 'name': 'C', 'children': ['1', '2', { 'name': '3', 'children': ['a', 'b', 'c'] }] }, 'D', 'E', 'F' ] machine_cls = MachineFactory.get_predefined(nested=True, async=True) class AsyncCompatibilityEventClass(AsyncNestedEvent): def trigger(self, model, *args, **kwargs): loop = asyncio.get_event_loop() return loop.run_until_complete(super().trigger( model, *args, **kwargs)) machine_cls.event_cls = AsyncCompatibilityEventClass self.stuff = Stuff(states, machine_cls)
def setUp(self): self.machine_cls = MachineFactory.get_predefined(graph=True) self.states = ['A', 'B', 'C', 'D'] self.transitions = [{ 'trigger': 'walk', 'source': 'A', 'dest': 'B' }, { 'trigger': 'run', 'source': 'B', 'dest': 'C' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'D', 'conditions': 'is_fast' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'B' }]
def test_if_multiple_edges_are_supported(self): transitions = [ ['event_0', 'a', 'b'], ['event_1', 'a', 'b'], ['event_2', 'a', 'b'], ['event_3', 'a', 'b'], ] machine_cls = MachineFactory.get_predefined(graph=True) m = machine_cls( states=['a', 'b'], transitions=transitions, initial='a', auto_transitions=False, ) graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) triggers = [transition[0] for transition in transitions] for trigger in triggers: self.assertTrue(trigger in str(graph))
def test_context_managers(self): class CounterContext(object): def __init__(self): self.counter = 0 self.level = 0 self.max = 0 super(CounterContext, self).__init__() def __enter__(self): self.counter += 1 self.level += 1 self.max = max(self.level, self.max) def __exit__(self, *exc): self.level -= 1 M = MachineFactory.get_predefined(locked=True) c = CounterContext() m = M(states=['A', 'B', 'C', 'D'], transitions=[['reset', '*', 'A']], initial='A', machine_context=c) m.get_triggers('A') self.assertEqual(c.max, 1) # was 3 before self.assertEqual(c.counter, 4) # was 72 (!) before
def test_context_managers(self): class CounterContext(object): def __init__(self): self.counter = 0 self.level = 0 self.max = 0 super(CounterContext, self).__init__() def __enter__(self): self.counter += 1 self.level += 1 self.max = max(self.level, self.max) def __exit__(self, *exc): self.level -= 1 M = MachineFactory.get_predefined(locked=True) c = CounterContext() m = M(states=['A', 'B', 'C', 'D'], transitions=[['reset', '*', 'A']], initial='A', machine_context=c) m.get_triggers('A') self.assertEqual(c.max, 1) # was 3 before self.assertEqual(c.counter, 4) # was 72 (!) before
class TestNestedStateEnums(TestEnumsAsStates): machine_cls = MachineFactory.get_predefined(nested=True) def test_root_enums(self): states = [self.States.RED, self.States.YELLOW, {'name': self.States.GREEN, 'children': ['tick', 'tock'], 'initial': 'tick'}] m = self.machine_cls(states=states, initial=self.States.GREEN) self.assertTrue(m.is_GREEN(allow_substates=True)) self.assertTrue(m.is_GREEN_tick()) m.to_RED() self.assertTrue(m.state is self.States.RED) # self.assertEqual(m.state, self.States.GREEN) def test_nested_enums(self): # Nested enums are currently not support since model.state does not contain any information about parents # and nesting states = ['A', 'B', {'name': 'C', 'children': self.States, 'initial': self.States.GREEN}] with self.assertRaises(AttributeError): # NestedState will raise an error when parent is not None and state name is an enum # Initializing this would actually work but `m.to_A()` would raise an error in get_state(m.state) # as Machine is not aware of the location of States.GREEN m = self.machine_cls(states=states, initial='C')
class TestEnumsAsStates(TestCase): machine_cls = MachineFactory.get_predefined() def setUp(self): class States(enum.Enum): RED = 1 YELLOW = 2 GREEN = 3 self.States = States def test_pass_enums_as_states(self): m = self.machine_cls(states=self.States, initial=self.States.YELLOW) assert m.state == self.States.YELLOW assert m.is_RED() is False assert m.is_YELLOW() is True assert m.is_RED() is False m.to_RED() assert m.state == self.States.RED assert m.is_RED() is True assert m.is_YELLOW() is False assert m.is_GREEN() is False def test_transitions(self): m = self.machine_cls(states=self.States, initial=self.States.RED) m.add_transition('switch_to_yellow', self.States.RED, self.States.YELLOW) m.add_transition('switch_to_green', 'YELLOW', 'GREEN') m.switch_to_yellow() assert m.is_YELLOW() is True m.switch_to_green() assert m.is_YELLOW() is False assert m.is_GREEN() is True def test_if_enum_has_string_behavior(self): class States(str, enum.Enum): __metaclass__ = enum.EnumMeta RED = 'red' YELLOW = 'yellow' m = self.machine_cls(states=States, auto_transitions=False, initial=States.RED) m.add_transition('switch_to_yellow', States.RED, States.YELLOW) m.switch_to_yellow() assert m.is_YELLOW() is True def test_property_initial(self): transitions = [ { 'trigger': 'switch_to_yellow', 'source': self.States.RED, 'dest': self.States.YELLOW }, { 'trigger': 'switch_to_green', 'source': 'YELLOW', 'dest': 'GREEN' }, ] m = self.machine_cls(states=self.States, initial=self.States.RED, transitions=transitions) m.switch_to_yellow() assert m.is_YELLOW() m.switch_to_green() assert m.is_GREEN() def test_pass_state_instances_instead_of_names(self): state_A = self.machine_cls.state_cls(self.States.YELLOW) state_B = self.machine_cls.state_cls(self.States.GREEN) states = [state_A, state_B] m = self.machine_cls(states=states, initial=state_A) assert m.state == self.States.YELLOW m.add_transition('advance', state_A, state_B) m.advance() assert m.state == self.States.GREEN def test_state_change_listeners(self): class States(enum.Enum): ONE = 1 TWO = 2 class Stuff(object): def __init__(self, machine_cls): self.state = None self.machine = machine_cls(states=States, initial=States.ONE, model=self) self.machine.add_transition('advance', States.ONE, States.TWO) self.machine.add_transition('reverse', States.TWO, States.ONE) self.machine.on_enter_TWO('hello') self.machine.on_exit_TWO('goodbye') def hello(self): self.message = 'Hello' def goodbye(self): self.message = 'Goodbye' s = Stuff(self.machine_cls) s.advance() assert s.is_TWO() assert s.message == 'Hello' s.reverse() assert s.is_ONE() assert s.message == 'Goodbye'
def setUp(self): super(TestAsync, self).setUp() self.machine_cls = MachineFactory.get_predefined(asyncio=True) self.machine = self.machine_cls(states=['A', 'B', 'C'], transitions=[['go', 'A', 'B']], initial='A')
def setUp(self): self.stuff = Stuff(machine_cls=MachineFactory.get_predefined(locked=True)) self.stuff.heavy_processing = heavy_processing self.stuff.machine.add_transition('process', '*', 'B', before='heavy_processing')
def setUp(self): super(TestDiagramsLockedNested, self).setUp() self.machine_cls = MachineFactory.get_predefined(graph=True, nested=True, locked=True)
class TestDiagramsNested(TestDiagrams): machine_cls = MachineFactory.get_predefined(graph=True, nested=True) def setUp(self): super(TestDiagramsNested, self).setUp() self.states = [ 'A', 'B', { 'name': 'C', 'children': [{ 'name': '1', 'children': ['a', 'b', 'c'] }, '2', '3'] }, 'D' ] self.transitions = [ { 'trigger': 'walk', 'source': 'A', 'dest': 'B' }, # 1 edge { 'trigger': 'run', 'source': 'B', 'dest': 'C' }, # + 1 edge { 'trigger': 'sprint', 'source': 'C', 'dest': 'D', # + 1 edge 'conditions': 'is_fast' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'B' }, # + 1 edge { 'trigger': 'reset', 'source': '*', 'dest': 'A' } ] # + 4 edges (from base state) = 8 def test_diagram(self): m = self.machine_cls(states=self.states, transitions=self.transitions, initial='A', auto_transitions=False, title='A test', show_conditions=True, use_pygraphviz=self.use_pygraphviz) graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) _, nodes, edges = self.parse_dot(graph) self.assertEqual(len(edges), 8) # Test that graph properties match the Machine self.assertEqual( set(m.states.keys()) - set(['C', 'C%s1' % NestedState.separator]), set(nodes) - set(['C_anchor', 'C%s1_anchor' % NestedState.separator])) m.walk() m.run() # write diagram to temp file target = tempfile.NamedTemporaryFile(suffix='.png', delete=False) m.get_graph().draw(target.name, prog='dot') self.assertTrue(os.path.getsize(target.name) > 0) # backwards compatibility check m.get_graph().draw(target.name, prog='dot') self.assertTrue(os.path.getsize(target.name) > 0) # cleanup temp file target.close() os.unlink(target.name) def test_roi(self): class Model: def is_fast(self, *args, **kwargs): return True model = Model() m = self.machine_cls(model, states=self.states, transitions=self.transitions, initial='A', title='A test', use_pygraphviz=self.use_pygraphviz, show_conditions=True) model.walk() model.run() g1 = model.get_graph(show_roi=True) _, nodes, edges = self.parse_dot(g1) self.assertEqual(len(edges), 4) self.assertEqual(len(nodes), 4) model.sprint() g2 = model.get_graph(show_roi=True) dot, nodes, edges = self.parse_dot(g2) self.assertEqual(len(edges), 2) self.assertEqual(len(nodes), 3) def test_internal(self): states = ['A', 'B'] transitions = [['go', 'A', 'B'], dict(trigger='fail', source='A', dest=None, conditions=['failed']), dict(trigger='fail', source='A', dest='B', unless=['failed'])] m = self.machine_cls(states=states, transitions=transitions, initial='A', show_conditions=True, use_pygraphviz=self.use_pygraphviz) _, nodes, edges = self.parse_dot(m.get_graph()) self.assertEqual(len(nodes), 2) self.assertEqual(len([e for e in edges if '[internal]' in e]), 1) def test_internal_wildcards(self): internal_only_once = r'^(?:(?!\[internal\]).)*\[internal\](?!.*\[internal\]).*$' states = ["initial", "ready", "running"] transitions = [["booted", "initial", "ready"], { "trigger": "polled", "source": "ready", "dest": "running", "conditions": "door_closed" }, ["done", "running", "ready"], ["polled", "*", None]] m = self.machine_cls(states=states, transitions=transitions, show_conditions=True, use_pygraphviz=self.use_pygraphviz, initial='initial') _, nodes, edges = self.parse_dot(m.get_graph()) self.assertEqual(len(nodes), 3) self.assertEqual( len([e for e in edges if re.match(internal_only_once, e)]), 3) def test_nested_notebook(self): states = [{ 'name': 'caffeinated', 'on_enter': 'do_x', 'children': ['dithering', 'running'], 'transitions': [['walk', 'dithering', 'running'], ['drink', 'dithering', '=']], }, { 'name': 'standing', 'on_enter': ['do_x', 'do_y'], 'on_exit': 'do_z' }, { 'name': 'walking', 'tags': ['accepted', 'pending'], 'timeout': 5, 'on_timeout': 'do_z' }] transitions = [['walk', 'standing', 'walking'], ['go', 'standing', 'walking'], ['stop', 'walking', 'standing'], { 'trigger': 'drink', 'source': '*', 'dest': 'caffeinated{0}dithering'.format( self.machine_cls.state_cls.separator), 'conditions': 'is_hot', 'unless': 'is_too_hot' }, ['relax', 'caffeinated', 'standing'], ['sip', 'standing', 'caffeinated']] @add_state_features(Timeout, Tags) class CustomStateMachine(self.machine_cls): def is_hot(self): return True def is_too_hot(self): return False def do_x(self): pass def do_z(self): pass extra_args = dict(auto_transitions=False, initial='standing', title='Mood Matrix', show_conditions=True, show_state_attributes=True, use_pygraphviz=self.use_pygraphviz) machine = CustomStateMachine(states=states, transitions=transitions, **extra_args) g1 = machine.get_graph() # dithering should have 4 'drink' edges, a) from walking, b) from initial, c) from running and d) from itself if self.use_pygraphviz: dot_string = g1.string() else: dot_string = g1.source count = re.findall( '-> "?caffeinated{0}dithering"?'.format( machine.state_cls.separator), dot_string) self.assertEqual(4, len(count)) self.assertTrue(True) machine.drink() machine.drink() g1 = machine.get_graph() self.assertIsNotNone(g1)
class CustomMachine(MachineFactory.get_predefined(nested=True)): state_cls = CustomState
def setUp(self): self.machine_cls = MachineFactory.get_predefined(locked=True) self.stuff = Stuff(machine_cls=self.machine_cls) self.stuff.heavy_processing = heavy_processing self.stuff.machine.add_transition('forward', 'A', 'B', before='heavy_processing')
def setUp(self): super(TestEnumWithGraph, self).setUp() self.machine_cls = MachineFactory.get_predefined(graph=True)
class TestDiagramsLocked(TestDiagrams): machine_cls = MachineFactory.get_predefined(graph=True, locked=True)
class CustomHierarchicalMachine( MachineFactory.get_predefined(nested=True)): state_cls = CustomNestedState
class TestDiagramsNested(TestDiagrams): machine_cls = MachineFactory.get_predefined(graph=True, nested=True) def setUp(self): super(TestDiagramsNested, self).setUp() self.states = [ 'A', 'B', { 'name': 'C', 'children': [{ 'name': '1', 'children': ['a', 'b', 'c'] }, '2', '3'] }, 'D' ] self.transitions = [ { 'trigger': 'walk', 'source': 'A', 'dest': 'B' }, # 1 edge { 'trigger': 'run', 'source': 'B', 'dest': 'C' }, # + 1 edge { 'trigger': 'sprint', 'source': 'C', 'dest': 'D', # + 1 edge 'conditions': 'is_fast' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'B' }, # + 1 edge { 'trigger': 'reset', 'source': '*', 'dest': 'A' } ] # + 10 (8 nodes; 2 cluster) edges = 14 def test_diagram(self): m = self.machine_cls(states=self.states, transitions=self.transitions, initial='A', auto_transitions=False, title='A test', show_conditions=True, use_pygraphviz=self.use_pygraphviz) graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) _, nodes, edges = self.parse_dot(graph) self.assertEqual(len(edges), 14) # Test that graph properties match the Machine self.assertEqual( set(m.states.keys()) - set(['C', 'C%s1' % NestedState.separator]), set(nodes) - set(['C_anchor', 'C%s1_anchor' % NestedState.separator])) m.walk() m.run() # write diagram to temp file target = tempfile.NamedTemporaryFile(suffix='.png') m.get_graph().draw(target.name, prog='dot') self.assertTrue(os.path.getsize(target.name) > 0) # backwards compatibility check m.get_graph().draw(target.name, prog='dot') self.assertTrue(os.path.getsize(target.name) > 0) # cleanup temp file target.close() def test_roi(self): class Model: def is_fast(self, *args, **kwargs): return True model = Model() m = self.machine_cls(model, states=self.states, transitions=self.transitions, initial='A', title='A test', use_pygraphviz=self.use_pygraphviz, show_conditions=True) model.walk() model.run() g1 = model.get_graph(show_roi=True) _, nodes, edges = self.parse_dot(g1) self.assertEqual(len(edges), 4) self.assertEqual(len(nodes), 4) model.sprint() g2 = model.get_graph(show_roi=True) dot, nodes, edges = self.parse_dot(g2) self.assertEqual(len(edges), 2) self.assertEqual(len(nodes), 3) def test_internal(self): self.use_pygraphviz = True states = ['A', 'B'] transitions = [['go', 'A', 'B'], dict(trigger='fail', source='A', dest=None, conditions=['failed']), dict(trigger='fail', source='A', dest='B', unless=['failed'])] m = self.machine_cls(states=states, transitions=transitions, initial='A', show_conditions=True, use_pygraphviz=self.use_pygraphviz) _, nodes, edges = self.parse_dot(m.get_graph()) self.assertEqual(len(nodes), 2) self.assertEqual(len([e for e in edges if '[internal]' in e]), 1) def test_internal_wildcards(self): internal_only_once = r'^(?:(?!\[internal\]).)*\[internal\](?!.*\[internal\]).*$' states = ["initial", "ready", "running"] transitions = [["booted", "initial", "ready"], { "trigger": "polled", "source": "ready", "dest": "running", "conditions": "door_closed" }, ["done", "running", "ready"], ["polled", "*", None]] m = self.machine_cls(states=states, transitions=transitions, show_conditions=True, use_pygraphviz=self.use_pygraphviz, initial='initial') _, nodes, edges = self.parse_dot(m.get_graph()) self.assertEqual(len(nodes), 3) self.assertEqual( len([e for e in edges if re.match(internal_only_once, e)]), 3)
def setUp(self): self.states = test_states self.machine_cls = MachineFactory.get_predefined(nested=True) self.state_cls = self.machine_cls.state_cls self.stuff = Stuff(self.states, self.machine_cls)
the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Janitoo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Janitoo. If not, see <http://www.gnu.org/licenses/>. """ __author__ = 'Sébastien GALLET aka bibi21000' __email__ = '*****@*****.**' __copyright__ = "Copyright © 2013-2014-2015-2016 Sébastien GALLET aka bibi21000" import logging logger = logging.getLogger(__name__) from transitions import State from transitions.extensions import MachineFactory def show_graph(self,fname='state.png', prog='dot', title=None): self.graph.draw(fname, prog=prog) Machine = MachineFactory.get_predefined(graph=True) Machine.show_graph = show_graph HierarchicalMachine = MachineFactory.get_predefined(graph=True, nested=True) HierarchicalMachine.show_graph = show_graph
def setUp(self): super(TestNestedStateGraphEnums, self).setUp() self.machine_cls = MachineFactory.get_predefined(nested=True, graph=True)
class TestDiagrams(TestTransitions): machine_cls = MachineFactory.get_predefined(graph=True) use_pygraphviz = False def parse_dot(self, graph): if self.use_pygraphviz: dot = graph.string() else: dot = graph.source nodes = [] edges = [] for line in dot.split('\n'): if '->' in line: src, rest = line.split('->') dst, attr = rest.split(None, 1) nodes.append(src.strip().replace('"', '')) nodes.append(dst) edges.append(attr) return dot, set(nodes), edges def tearDown(self): pass # for m in ['pygraphviz', 'graphviz']: # if 'transitions.extensions.diagrams_' + m in sys.modules: # del sys.modules['transitions.extensions.diagrams_' + m] def setUp(self): self.stuff = Stuff( machine_cls=self.machine_cls, extra_kwargs={'use_pygraphviz': self.use_pygraphviz}) self.states = ['A', 'B', 'C', 'D'] self.transitions = [{ 'trigger': 'walk', 'source': 'A', 'dest': 'B' }, { 'trigger': 'run', 'source': 'B', 'dest': 'C' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'D', 'conditions': 'is_fast' }, { 'trigger': 'sprint', 'source': 'C', 'dest': 'B' }] def test_diagram(self): m = self.machine_cls(states=self.states, transitions=self.transitions, initial='A', auto_transitions=False, title='a test', use_pygraphviz=self.use_pygraphviz) graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue(graph.directed) _, nodes, edges = self.parse_dot(graph) # Test that graph properties match the Machine self.assertEqual(set(m.states.keys()), nodes) self.assertEqual(len(edges), len(self.transitions)) for e in edges: # label should be equivalent to the event name self.assertIsNotNone( getattr(m, re.match(r'\[label=([^\]]+)\]', e).group(1))) # write diagram to temp file target = tempfile.NamedTemporaryFile(suffix='.png') graph.draw(target.name, format='png', prog='dot') self.assertTrue(os.path.getsize(target.name) > 0) # backwards compatibility check m.get_graph().draw(target.name, format='png', prog='dot') self.assertTrue(os.path.getsize(target.name) > 0) # cleanup temp file target.close() def test_add_custom_state(self): m = self.machine_cls(states=self.states, transitions=self.transitions, initial='A', auto_transitions=False, title='a test', use_pygraphviz=self.use_pygraphviz) m.add_state('X') m.add_transition('foo', '*', 'X') m.foo() def test_if_multiple_edges_are_supported(self): transitions = [ ['event_0', 'a', 'b'], ['event_1', 'a', 'b'], ['event_2', 'a', 'b'], ['event_3', 'a', 'b'], ] m = self.machine_cls(states=['a', 'b'], transitions=transitions, initial='a', auto_transitions=False, use_pygraphviz=self.use_pygraphviz) graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) triggers = [transition[0] for transition in transitions] for trigger in triggers: self.assertTrue(trigger in str(graph)) def test_multi_model_state(self): m1 = Stuff(machine_cls=None, extra_kwargs={'use_pygraphviz': self.use_pygraphviz}) m2 = Stuff(machine_cls=None, extra_kwargs={'use_pygraphviz': self.use_pygraphviz}) m = self.machine_cls(model=[m1, m2], states=self.states, transitions=self.transitions, initial='A', use_pygraphviz=self.use_pygraphviz) m1.walk() self.assertEqual(m.model_graphs[m1].custom_styles['node'][m1.state], 'active') self.assertEqual(m.model_graphs[m2].custom_styles['node'][m1.state], '') # backwards compatibility test dot1, _, _ = self.parse_dot(m1.get_graph()) dot, _, _ = self.parse_dot(m.get_graph()) self.assertEqual(dot, dot1) def test_model_method_collision(self): class GraphModel: def get_graph(self): return "This method already exists" model = GraphModel() with self.assertRaises(AttributeError): m = self.machine_cls(model=model) self.assertEqual(model.get_graph(), "This method already exists") def test_to_method_filtering(self): m = self.machine_cls(states=['A', 'B', 'C'], initial='A', use_pygraphviz=self.use_pygraphviz) m.add_transition('to_state_A', 'B', 'A') m.add_transition('to_end', '*', 'C') _, _, edges = self.parse_dot(m.get_graph()) self.assertEqual(len([e for e in edges if e == '[label=to_state_A]']), 1) self.assertEqual(len([e for e in edges if e == '[label=to_end]']), 3) m2 = self.machine_cls(states=['A', 'B', 'C'], initial='A', show_auto_transitions=True, use_pygraphviz=self.use_pygraphviz) _, _, edges = self.parse_dot(m2.get_graph()) self.assertEqual(len(edges), 9) self.assertEqual(len([e for e in edges if e == '[label=to_A]']), 3) self.assertEqual(len([e for e in edges if e == '[label=to_C]']), 3) def test_loops(self): m = self.machine_cls(states=['A'], initial='A', use_pygraphviz=self.use_pygraphviz) m.add_transition('reflexive', 'A', '=') m.add_transition('fixed', 'A', None) g1 = m.get_graph() def test_roi(self): m = self.machine_cls(states=['A', 'B', 'C', 'D', 'E', 'F'], initial='A', use_pygraphviz=self.use_pygraphviz) m.add_transition('to_state_A', 'B', 'A') m.add_transition('to_state_C', 'B', 'C') m.add_transition('to_state_F', 'B', 'F') g1 = m.get_graph(show_roi=True) dot, nodes, edges = self.parse_dot(g1) self.assertEqual(len(edges), 0) self.assertIn("label=A", dot) # make sure that generating a graph without ROI has not influence on the later generated graph # this has to be checked since graph.custom_style is a class property and is persistent for multiple # calls of graph.generate() m.to_C() m.to_E() _ = m.get_graph() g2 = m.get_graph(show_roi=True) dot, _, _ = self.parse_dot(g2) self.assertNotIn("label=A", dot) m.to_B() g3 = m.get_graph(show_roi=True) _, nodes, edges = self.parse_dot(g3) self.assertEqual(len(edges), 3) self.assertEqual(len(nodes), 4) def test_state_tags(self): @add_state_features(Tags, Timeout) class CustomMachine(self.machine_cls): pass self.states[0] = { 'name': 'A', 'tags': ['new', 'polling'], 'timeout': 5, 'on_enter': 'say_hello', 'on_exit': 'say_goodbye', 'on_timeout': 'do_something' } m = CustomMachine(states=self.states, transitions=self.transitions, initial='A', show_state_attributes=True, use_pygraphviz=self.use_pygraphviz) g = m.get_graph(show_roi=True)