Skip to content

Commit 12ff4dd

Browse files
committed
Merge pull request #69 from python-effect/perform-sequence
perform_sequence: a higher-level, more debuggable way to perform with a SequenceDispatcher
2 parents 6e0ea18 + 180433a commit 12ff4dd

File tree

7 files changed

+166
-64
lines changed

7 files changed

+166
-64
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
lint:
2-
flake8 --ignore=E131,E731,W503 effect/
2+
flake8 --ignore=E131,E731,W503 --max-line-length=100 effect/
33

44
build-dist:
55
rm -rf dist

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414
copyright = u'2015, Christopher Armstrong'
1515
version = release = '0.9+'
1616

17-
17+
html_theme = 'sphinx_rtd_theme'

docs/source/testing.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Testing Effectful Code
22
----------------------
33

44
The most useful testing tool you'll want to familiarize yourself with is
5-
:obj:`effect.testing.SequenceDispatcher`. Using this with
6-
:func:`effect.sync_perform` in your unit tests will allow you to perform your
7-
effects while both ensuring that the expected intents are performed, as well as
8-
provide the results of those effects.
5+
:func:`effect.testing.perform_sequence`. Using this in your unit tests will
6+
allow you to perform your effects while ensuring that the expected intents are
7+
performed in the expected order, as well as provide the results of those
8+
effects.

effect/do.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ def doit():
6363
% (f, gen))
6464
return _do(None, gen, False)
6565

66+
fname = getattr(f, '__name__', None)
67+
if fname is not None:
68+
doit.__name__ = 'do_' + fname
69+
6670
return Effect(Func(doit))
6771
return do_wrapper
6872

effect/test_fold.py

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,9 @@
33

44
from pytest import raises
55

6-
from effect import (
7-
ComposedDispatcher, Effect, Error,
8-
base_dispatcher, sync_perform)
6+
from effect import Effect, Error, base_dispatcher, sync_perform
97
from effect.fold import FoldError, fold_effect, sequence
10-
from effect.testing import SequenceDispatcher
11-
12-
13-
def _base_and(dispatcher):
14-
"""Compose base_dispatcher onto the given dispatcher."""
15-
return ComposedDispatcher([dispatcher, base_dispatcher])
8+
from effect.testing import perform_sequence
169

1710

1811
def test_fold_effect():
@@ -22,15 +15,13 @@ def test_fold_effect():
2215
"""
2316
effs = [Effect('a'), Effect('b'), Effect('c')]
2417

25-
dispatcher = SequenceDispatcher([
18+
dispatcher = [
2619
('a', lambda i: 'Ei'),
2720
('b', lambda i: 'Bee'),
2821
('c', lambda i: 'Cee'),
29-
])
22+
]
3023
eff = fold_effect(operator.add, 'Nil', effs)
31-
32-
with dispatcher.consume():
33-
result = sync_perform(_base_and(dispatcher), eff)
24+
result = perform_sequence(dispatcher, eff)
3425
assert result == 'NilEiBeeCee'
3526

3627

@@ -50,15 +41,12 @@ def test_fold_effect_errors():
5041
"""
5142
effs = [Effect('a'), Effect(Error(ZeroDivisionError('foo'))), Effect('c')]
5243

53-
dispatcher = SequenceDispatcher([
54-
('a', lambda i: 'Ei'),
55-
])
44+
dispatcher = [('a', lambda i: 'Ei')]
5645

5746
eff = fold_effect(operator.add, 'Nil', effs)
5847

59-
with dispatcher.consume():
60-
with raises(FoldError) as excinfo:
61-
sync_perform(_base_and(dispatcher), eff)
48+
with raises(FoldError) as excinfo:
49+
perform_sequence(dispatcher, eff)
6250
assert excinfo.value.accumulator == 'NilEi'
6351
assert excinfo.value.wrapped_exception[0] is ZeroDivisionError
6452
assert str(excinfo.value.wrapped_exception[1]) == 'foo'
@@ -67,14 +55,11 @@ def test_fold_effect_errors():
6755
def test_fold_effect_str():
6856
"""str()ing a FoldError returns useful traceback/exception info."""
6957
effs = [Effect('a'), Effect(Error(ZeroDivisionError('foo'))), Effect('c')]
70-
dispatcher = SequenceDispatcher([
71-
('a', lambda i: 'Ei'),
72-
])
58+
dispatcher = [('a', lambda i: 'Ei')]
7359

7460
eff = fold_effect(operator.add, 'Nil', effs)
75-
with dispatcher.consume():
76-
with raises(FoldError) as excinfo:
77-
sync_perform(_base_and(dispatcher), eff)
61+
with raises(FoldError) as excinfo:
62+
perform_sequence(dispatcher, eff)
7863
assert str(excinfo.value).startswith(
7964
"<FoldError after accumulating 'NilEi'> Original traceback follows:\n")
8065
assert str(excinfo.value).endswith('ZeroDivisionError: foo')
@@ -83,15 +68,14 @@ def test_fold_effect_str():
8368
def test_sequence():
8469
"""Collects each Effectful result into a list."""
8570
effs = [Effect('a'), Effect('b'), Effect('c')]
86-
dispatcher = SequenceDispatcher([
71+
dispatcher = [
8772
('a', lambda i: 'Ei'),
8873
('b', lambda i: 'Bee'),
8974
('c', lambda i: 'Cee'),
90-
])
75+
]
9176
eff = sequence(effs)
9277

93-
with dispatcher.consume():
94-
result = sync_perform(_base_and(dispatcher), eff)
78+
result = perform_sequence(dispatcher, eff)
9579
assert result == ['Ei', 'Bee', 'Cee']
9680

9781

@@ -107,15 +91,12 @@ def test_sequence_error():
10791
"""
10892
effs = [Effect('a'), Effect(Error(ZeroDivisionError('foo'))), Effect('c')]
10993

110-
dispatcher = SequenceDispatcher([
111-
('a', lambda i: 'Ei'),
112-
])
94+
dispatcher = [('a', lambda i: 'Ei')]
11395

11496
eff = sequence(effs)
11597

116-
with dispatcher.consume():
117-
with raises(FoldError) as excinfo:
118-
sync_perform(_base_and(dispatcher), eff)
98+
with raises(FoldError) as excinfo:
99+
perform_sequence(dispatcher, eff)
119100
assert excinfo.value.accumulator == ['Ei']
120101
assert excinfo.value.wrapped_exception[0] is ZeroDivisionError
121102
assert str(excinfo.value.wrapped_exception[1]) == 'foo'

effect/test_testing.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
Tests for the effect.testing module.
33
"""
44

5+
import attr
6+
7+
import pytest
8+
59
from testtools import TestCase
610
from testtools.matchers import (MatchesListwise, Equals, MatchesException,
711
raises)
@@ -12,6 +16,7 @@
1216
base_dispatcher,
1317
parallel,
1418
sync_perform)
19+
from .do import do, do_return
1520
from .testing import (
1621
ESConstant,
1722
ESError,
@@ -20,6 +25,7 @@
2025
EQFDispatcher,
2126
SequenceDispatcher,
2227
fail_effect,
28+
perform_sequence,
2329
resolve_effect,
2430
resolve_stubs)
2531

@@ -324,7 +330,7 @@ def test_consumed(self):
324330
def test_consumed_honors_changes(self):
325331
"""
326332
`consumed` returns True if there are no more elements after performing
327-
some..
333+
some.
328334
"""
329335
d = SequenceDispatcher([('foo', lambda i: 'bar')])
330336
sync_perform(d, Effect('foo'))
@@ -352,3 +358,48 @@ def failer():
352358
pass
353359
e = self.assertRaises(AssertionError, failer)
354360
self.assertEqual(str(e), "Not all intents were performed: ['foo']")
361+
362+
363+
@attr.s
364+
class MyIntent(object):
365+
val = attr.ib()
366+
367+
368+
@attr.s
369+
class OtherIntent(object):
370+
val = attr.ib()
371+
372+
373+
def test_perform_sequence():
374+
"""perform_sequence pretty much acts like SequenceDispatcher by default."""
375+
376+
@do
377+
def code_under_test():
378+
r = yield Effect(MyIntent('a'))
379+
r2 = yield Effect(OtherIntent('b'))
380+
yield do_return((r, r2))
381+
382+
seq = [(MyIntent('a'), lambda i: 'result1'),
383+
(OtherIntent('b'), lambda i: 'result2')]
384+
eff = code_under_test()
385+
assert perform_sequence(seq, eff) == ('result1', 'result2')
386+
387+
388+
def test_perform_sequence_log():
389+
"""
390+
When an intent isn't found, a useful log of intents is included in the
391+
exception message.
392+
"""
393+
@do
394+
def code_under_test():
395+
r = yield Effect(MyIntent('a'))
396+
r2 = yield Effect(OtherIntent('b'))
397+
yield do_return((r, r2))
398+
399+
seq = [(MyIntent('a'), lambda i: 'result1')]
400+
with pytest.raises(AssertionError) as exc:
401+
perform_sequence(seq, code_under_test())
402+
403+
expected = ("sequence: MyIntent(val='a')\n"
404+
"NOT FOUND: OtherIntent(val='b')")
405+
assert expected in str(exc.value)

0 commit comments

Comments
 (0)