Esempio n. 1
0
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)
Esempio n. 2
0
 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')
Esempio n. 3
0
    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)
Esempio n. 4
0
    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)
Esempio n. 5
0
    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'}
        ]
Esempio n. 6
0
 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')
Esempio n. 7
0
 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
Esempio n. 8
0
 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)
Esempio n. 9
0
    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()
Esempio n. 10
0
    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)
Esempio n. 11
0
    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')
Esempio n. 12
0
 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())
Esempio n. 13
0
    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()
Esempio n. 14
0
    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')
Esempio n. 15
0
    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')
Esempio n. 16
0
    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()
Esempio n. 17
0
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)
Esempio n. 18
0
    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()
Esempio n. 19
0
 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')
Esempio n. 20
0
    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()
Esempio n. 21
0
    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)
Esempio n. 22
0
 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
Esempio n. 23
0
    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)
Esempio n. 24
0
    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'
        }]
Esempio n. 25
0
    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))
Esempio n. 26
0
    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
Esempio n. 27
0
    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
Esempio n. 28
0
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')
Esempio n. 29
0
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'
Esempio n. 30
0
 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')
Esempio n. 31
0
 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')
Esempio n. 32
0
 def setUp(self):
     super(TestDiagramsLockedNested, self).setUp()
     self.machine_cls = MachineFactory.get_predefined(graph=True,
                                                      nested=True,
                                                      locked=True)
Esempio n. 33
0
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)
Esempio n. 34
0
 class CustomMachine(MachineFactory.get_predefined(nested=True)):
     state_cls = CustomState
Esempio n. 35
0
 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')
Esempio n. 36
0
 def setUp(self):
     super(TestEnumWithGraph, self).setUp()
     self.machine_cls = MachineFactory.get_predefined(graph=True)
Esempio n. 37
0
class TestDiagramsLocked(TestDiagrams):

    machine_cls = MachineFactory.get_predefined(graph=True, locked=True)
Esempio n. 38
0
 class CustomHierarchicalMachine(
         MachineFactory.get_predefined(nested=True)):
     state_cls = CustomNestedState
Esempio n. 39
0
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)
Esempio n. 40
0
 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)
Esempio n. 41
0
    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
Esempio n. 42
0
 def setUp(self):
     super(TestNestedStateGraphEnums, self).setUp()
     self.machine_cls = MachineFactory.get_predefined(nested=True,
                                                      graph=True)
Esempio n. 43
0
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)