Skip to content

Commit 3a17f8f

Browse files
committed
nested: consider scope tree instead of source_path for exit strategy;
bug discovered in #421
1 parent 6bcfd73 commit 3a17f8f

File tree

4 files changed

+52
-16
lines changed

4 files changed

+52
-16
lines changed

Changelog.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## 0.8.2 ()
3+
## 0.8.2 (June 2020)
44

55
Release 0.8.2 is a minor release and contains several bugfixes and improvements:
66

@@ -9,11 +9,13 @@ Release 0.8.2 is a minor release and contains several bugfixes and improvements:
99
- Bugfix #440: Only allow explicit `dest=None` in `Machine.add_transition` (not just falsy) for internal transitions (thanks @Pathfinder216)
1010
- Bugfix #419: Fix state creation of nested enums (thanks @thedrow)
1111
- Bugfix #428: HierarchicalGraphMachine did not find/apply styling for parallel states (thanks @xiaohuihui1024)
12+
- Bugfix: `Model.trigger` now considers the machine's and current state's `ignore_invalid_triggers` attribute and can be called with non-existing events (thanks @potens1)
13+
- Bugfix: Child states may not have been exited when the executed transition had been defined on a parent (thanks @thedrow)
1214
- Feature #429: Introduced `transitions.extensions.asyncio.AsyncTimeout` as a state decorator to avoid threads used in `transitions.extensions.state.Timeout` (thanks @potens1)
15+
- Feature #444: `transitions` can now be tested online at mybinder.org
1316
- PR #418: Use sets instead of lists to cache already covered transitions in nested state machines (thanks @thedrow)
1417
- PR #422: Improve handling of unresolved attributes for easier inheritance (thanks @thedrow)
15-
- `Model.trigger` now considers the machine's and current state's `ignore_invalid_triggers` attribute and can be called with non-existing events (thanks @potens1)
16-
- Feature #444: `transitions` can now be tested online at mybinder.org
18+
- PR #445: Refactored AsyncMachine to enable trio/anyio override
1719

1820
## 0.8.1 (April 2020)
1921

tests/test_nesting.py

+37-3
Original file line numberDiff line numberDiff line change
@@ -230,16 +230,23 @@ def test_enter_exit_nested_state(self):
230230
def callback():
231231
mock()
232232
states = ['A', 'B', {'name': 'C', 'on_enter': callback, 'on_exit': callback,
233-
'children': [{'name': '1', 'on_exit': callback}, '2', '3']}, 'D']
233+
'children': [{'name': '1', 'on_enter': callback, 'on_exit': callback}, '2', '3']},
234+
{'name': 'D', 'on_enter': callback, 'on_exit': callback}]
234235
transitions = [['go', 'A', 'C{0}1'.format(State.separator)],
235236
['go', 'C', 'D']]
236237
m = self.stuff.machine_cls(states=states, transitions=transitions, initial='A')
237238
m.go()
238239
self.assertTrue(mock.called)
239-
self.assertEqual(mock.call_count, 1)
240+
self.assertEqual(2, mock.call_count)
240241
m.go()
241242
self.assertTrue(m.is_D())
242-
self.assertEqual(mock.call_count, 3)
243+
self.assertEqual(5, mock.call_count)
244+
m.to_C()
245+
self.assertEqual(7, mock.call_count)
246+
m.to('C{0}1'.format(State.separator))
247+
self.assertEqual(8, mock.call_count)
248+
m.to('C{0}2'.format(State.separator))
249+
self.assertEqual(9, mock.call_count)
243250

244251
def test_ordered_transitions(self):
245252
State = self.state_cls
@@ -438,6 +445,33 @@ def to(self):
438445
self.assertTrue(mock.called)
439446
self.assertTrue(model2.is_B())
440447

448+
def test_trigger_parent(self):
449+
parent_mock = MagicMock()
450+
exit_mock = MagicMock()
451+
enter_mock = MagicMock()
452+
453+
class Model():
454+
455+
def on_exit_A(self):
456+
parent_mock()
457+
458+
def on_exit_A_1(self):
459+
exit_mock()
460+
461+
def on_enter_A_2(self):
462+
enter_mock()
463+
464+
model = Model()
465+
machine = self.machine_cls(model, states=[{'name': 'A', 'children': ['1', '2']}],
466+
transitions=[['go', 'A', 'A_2'], ['enter', 'A', 'A_1']], initial='A')
467+
468+
model.enter()
469+
self.assertFalse(parent_mock.called)
470+
model.go()
471+
self.assertTrue(exit_mock.called)
472+
self.assertTrue(enter_mock.called)
473+
self.assertFalse(parent_mock.called)
474+
441475

442476
class TestSeparatorsBase(TestCase):
443477

tests/test_parallel.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def setUp(self):
2525
{'name': '2', 'children': ['a', 'b'],
2626
'initial': 'a',
2727
'transitions': [['go', 'a', 'b']]}]}]
28-
self.transitions = [['reset', '*', 'A']]
28+
self.transitions = [['reset', 'C', 'A']]
2929

3030
def test_init(self):
3131
m = self.stuff.machine_cls(states=self.states)
@@ -56,6 +56,7 @@ def on_exit_C_2(self):
5656

5757
model1 = Model()
5858
m = self.stuff.machine_cls(model1, states=self.states, transitions=self.transitions, initial='A')
59+
m.add_transition('reinit', 'C', 'C')
5960
model1.to_C()
6061
self.assertEqual(['C{0}1{0}a'.format(State.separator), 'C{0}2{0}a'.format(State.separator)], model1.state)
6162
model1.reset()
@@ -64,15 +65,18 @@ def on_exit_C_2(self):
6465

6566
model2 = Model()
6667
m.add_model(model2, initial='C')
68+
model2.reinit()
69+
self.assertEqual(['C{0}1{0}a'.format(State.separator), 'C{0}2{0}a'.format(State.separator)], model2.state)
70+
self.assertEqual(3, model2.mock.call_count)
6771
model2.reset()
6872
self.assertTrue(model2.is_A())
69-
self.assertEqual(3, model2.mock.call_count)
73+
self.assertEqual(6, model2.mock.call_count)
7074
for mod in m.models:
7175
mod.trigger('to_C')
7276
for mod in m.models:
7377
mod.trigger('reset')
7478
self.assertEqual(6, model1.mock.call_count)
75-
self.assertEqual(6, model2.mock.call_count)
79+
self.assertEqual(9, model2.mock.call_count)
7680

7781
def test_parent_transition(self):
7882
m = self.stuff.machine_cls(states=self.states)

transitions/extensions/nesting.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -238,13 +238,9 @@ def _resolve_transition(self, event_data):
238238
dst_name_path.pop(0)
239239

240240
scoped_tree = reduce(dict.get, scope + root, state_tree)
241-
exit_partials = []
242-
if src_name_path:
243-
for state_name in _resolve_order(scoped_tree):
244-
cb = partial(machine.get_state(root + state_name).scoped_exit,
245-
event_data,
246-
scope + root + state_name[:-1])
247-
exit_partials.append(cb)
241+
exit_partials = [partial(machine.get_state(root + state_name).scoped_exit,
242+
event_data, scope + root + state_name[:-1])
243+
for state_name in _resolve_order(scoped_tree)]
248244
if dst_name_path:
249245
new_states, enter_partials = self._enter_nested(root, dst_name_path, scope + root, event_data)
250246
else:

0 commit comments

Comments
 (0)