Skip to content

Commit c24e7b6

Browse files
committed
Merge pull request #47 from radix/sequence-context-manager
add a context manager to SequenceDispatcher that ensures all intents are performed
2 parents d058794 + a9d9f3c commit c24e7b6

2 files changed

Lines changed: 66 additions & 4 deletions

File tree

effect/test_testing.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,40 @@ def test_out_of_order(self):
315315
('foo', lambda i: ('performfoo', i)),
316316
])
317317
self.assertEqual(d('foo'), None)
318+
319+
def test_consumed(self):
320+
"""`consumed` returns True if there are no more elements."""
321+
d = SequenceDispatcher([])
322+
self.assertTrue(d.consumed())
323+
324+
def test_consumed_honors_changes(self):
325+
"""
326+
`consumed` returns True if there are no more elements after performing
327+
some..
328+
"""
329+
d = SequenceDispatcher([('foo', lambda i: 'bar')])
330+
sync_perform(d, Effect('foo'))
331+
self.assertTrue(d.consumed())
332+
333+
def test_not_consumed(self):
334+
"""
335+
`consumed` returns False if there are more elements.
336+
"""
337+
d = SequenceDispatcher([('foo', lambda i: 'bar')])
338+
self.assertFalse(d.consumed())
339+
340+
def test_consume_good(self):
341+
"""``consume`` doesn't raise an error if all elements are consumed."""
342+
d = SequenceDispatcher([('foo', lambda i: 'bar')])
343+
with d.consume():
344+
sync_perform(d, Effect('foo'))
345+
346+
def test_consume_raises(self):
347+
"""``consume`` raises an error if not all elements are consumed."""
348+
d = SequenceDispatcher([('foo', None)])
349+
350+
def failer():
351+
with d.consume():
352+
pass
353+
e = self.assertRaises(AssertionError, failer)
354+
self.assertEqual(str(e), "Not all intents were performed: ['foo']")

effect/testing.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from __future__ import print_function
1010

11+
from contextlib import contextmanager
1112
from functools import partial
1213
import sys
1314

@@ -251,17 +252,25 @@ class SequenceDispatcher(object):
251252
runs ``func`` to perform intents in strict sequence.
252253
253254
So, if you expect to first perform an intent like ``MyIntent('a')`` and
254-
then an intent like ``OtherIntent('b')``, you can create a dispatcher like
255-
this::
255+
then an intent like ``OtherIntent('b')``, you can create and use a
256+
dispatcher like this::
256257
257-
SequenceDispatcher([
258+
sequence = SequenceDispatcher([
258259
(MyIntent('a'), lambda i: 'my-intent-result'),
259260
(OtherIntent('b'), lambda i: 'other-intent-result')
260261
])
261262
263+
with sequence.consume():
264+
perform(sequence, eff)
265+
266+
It's important to use `with sequence.consume():` to ensure that all of the
267+
intents are performed. Otherwise, if your code has a bug that causes it to
268+
return before all effects are performed, your test may not fail.
269+
262270
:obj:`None` is returned if the next intent in the sequence is not equal to
263271
the intent being performed, or if there are no more items left in the
264-
sequence.
272+
sequence (this is standard behavior for dispatchers that don't handle an
273+
intent).
265274
"""
266275
def __init__(self, sequence):
267276
""":param list sequence: Sequence of (intent, fn)."""
@@ -274,3 +283,19 @@ def __call__(self, intent):
274283
if intent == exp_intent:
275284
self.sequence = self.sequence[1:]
276285
return sync_performer(lambda d, i: func(i))
286+
287+
def consumed(self):
288+
"""Return True if all of the steps were performed."""
289+
return len(self.sequence) == 0
290+
291+
@contextmanager
292+
def consume(self):
293+
"""
294+
Return a context manager that can be used with the `with` syntax to
295+
ensure that all steps are performed by the end.
296+
"""
297+
yield
298+
if not self.consumed():
299+
raise AssertionError(
300+
"Not all intents were performed: {0}".format(
301+
[x[0] for x in self.sequence]))

0 commit comments

Comments
 (0)