Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
lint:
flake8 --ignore=E131,E731,W503 effect/
flake8 --ignore=E131,E731,W503 --max-line-length=100 effect/

build-dist:
rm -rf dist
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
copyright = u'2015, Christopher Armstrong'
version = release = '0.9+'


html_theme = 'sphinx_rtd_theme'
8 changes: 4 additions & 4 deletions docs/source/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Testing Effectful Code
----------------------

The most useful testing tool you'll want to familiarize yourself with is
:obj:`effect.testing.SequenceDispatcher`. Using this with
:func:`effect.sync_perform` in your unit tests will allow you to perform your
effects while both ensuring that the expected intents are performed, as well as
provide the results of those effects.
:func:`effect.testing.perform_sequence`. Using this in your unit tests will
allow you to perform your effects while ensuring that the expected intents are
performed in the expected order, as well as provide the results of those
effects.
4 changes: 4 additions & 0 deletions effect/do.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ def doit():
% (f, gen))
return _do(None, gen, False)

fname = getattr(f, '__name__', None)
if fname is not None:
doit.__name__ = 'do_' + fname

return Effect(Func(doit))
return do_wrapper

Expand Down
53 changes: 17 additions & 36 deletions effect/test_fold.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,9 @@

from pytest import raises

from effect import (
ComposedDispatcher, Effect, Error,
base_dispatcher, sync_perform)
from effect import Effect, Error, base_dispatcher, sync_perform
from effect.fold import FoldError, fold_effect, sequence
from effect.testing import SequenceDispatcher


def _base_and(dispatcher):
"""Compose base_dispatcher onto the given dispatcher."""
return ComposedDispatcher([dispatcher, base_dispatcher])
from effect.testing import perform_sequence


def test_fold_effect():
Expand All @@ -22,15 +15,13 @@ def test_fold_effect():
"""
effs = [Effect('a'), Effect('b'), Effect('c')]

dispatcher = SequenceDispatcher([
dispatcher = [
('a', lambda i: 'Ei'),
('b', lambda i: 'Bee'),
('c', lambda i: 'Cee'),
])
]
eff = fold_effect(operator.add, 'Nil', effs)

with dispatcher.consume():
result = sync_perform(_base_and(dispatcher), eff)
result = perform_sequence(dispatcher, eff)
assert result == 'NilEiBeeCee'


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

dispatcher = SequenceDispatcher([
('a', lambda i: 'Ei'),
])
dispatcher = [('a', lambda i: 'Ei')]

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

with dispatcher.consume():
with raises(FoldError) as excinfo:
sync_perform(_base_and(dispatcher), eff)
with raises(FoldError) as excinfo:
perform_sequence(dispatcher, eff)
assert excinfo.value.accumulator == 'NilEi'
assert excinfo.value.wrapped_exception[0] is ZeroDivisionError
assert str(excinfo.value.wrapped_exception[1]) == 'foo'
Expand All @@ -67,14 +55,11 @@ def test_fold_effect_errors():
def test_fold_effect_str():
"""str()ing a FoldError returns useful traceback/exception info."""
effs = [Effect('a'), Effect(Error(ZeroDivisionError('foo'))), Effect('c')]
dispatcher = SequenceDispatcher([
('a', lambda i: 'Ei'),
])
dispatcher = [('a', lambda i: 'Ei')]

eff = fold_effect(operator.add, 'Nil', effs)
with dispatcher.consume():
with raises(FoldError) as excinfo:
sync_perform(_base_and(dispatcher), eff)
with raises(FoldError) as excinfo:
perform_sequence(dispatcher, eff)
assert str(excinfo.value).startswith(
"<FoldError after accumulating 'NilEi'> Original traceback follows:\n")
assert str(excinfo.value).endswith('ZeroDivisionError: foo')
Expand All @@ -83,15 +68,14 @@ def test_fold_effect_str():
def test_sequence():
"""Collects each Effectful result into a list."""
effs = [Effect('a'), Effect('b'), Effect('c')]
dispatcher = SequenceDispatcher([
dispatcher = [
('a', lambda i: 'Ei'),
('b', lambda i: 'Bee'),
('c', lambda i: 'Cee'),
])
]
eff = sequence(effs)

with dispatcher.consume():
result = sync_perform(_base_and(dispatcher), eff)
result = perform_sequence(dispatcher, eff)
assert result == ['Ei', 'Bee', 'Cee']


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

dispatcher = SequenceDispatcher([
('a', lambda i: 'Ei'),
])
dispatcher = [('a', lambda i: 'Ei')]

eff = sequence(effs)

with dispatcher.consume():
with raises(FoldError) as excinfo:
sync_perform(_base_and(dispatcher), eff)
with raises(FoldError) as excinfo:
perform_sequence(dispatcher, eff)
assert excinfo.value.accumulator == ['Ei']
assert excinfo.value.wrapped_exception[0] is ZeroDivisionError
assert str(excinfo.value.wrapped_exception[1]) == 'foo'
53 changes: 52 additions & 1 deletion effect/test_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
Tests for the effect.testing module.
"""

import attr

import pytest

from testtools import TestCase
from testtools.matchers import (MatchesListwise, Equals, MatchesException,
raises)
Expand All @@ -12,6 +16,7 @@
base_dispatcher,
parallel,
sync_perform)
from .do import do, do_return
from .testing import (
ESConstant,
ESError,
Expand All @@ -20,6 +25,7 @@
EQFDispatcher,
SequenceDispatcher,
fail_effect,
perform_sequence,
resolve_effect,
resolve_stubs)

Expand Down Expand Up @@ -324,7 +330,7 @@ def test_consumed(self):
def test_consumed_honors_changes(self):
"""
`consumed` returns True if there are no more elements after performing
some..
some.
"""
d = SequenceDispatcher([('foo', lambda i: 'bar')])
sync_perform(d, Effect('foo'))
Expand Down Expand Up @@ -352,3 +358,48 @@ def failer():
pass
e = self.assertRaises(AssertionError, failer)
self.assertEqual(str(e), "Not all intents were performed: ['foo']")


@attr.s
class MyIntent(object):
val = attr.ib()


@attr.s
class OtherIntent(object):
val = attr.ib()


def test_perform_sequence():
"""perform_sequence pretty much acts like SequenceDispatcher by default."""

@do
def code_under_test():
r = yield Effect(MyIntent('a'))
r2 = yield Effect(OtherIntent('b'))
yield do_return((r, r2))

seq = [(MyIntent('a'), lambda i: 'result1'),
(OtherIntent('b'), lambda i: 'result2')]
eff = code_under_test()
assert perform_sequence(seq, eff) == ('result1', 'result2')


def test_perform_sequence_log():
"""
When an intent isn't found, a useful log of intents is included in the
exception message.
"""
@do
def code_under_test():
r = yield Effect(MyIntent('a'))
r2 = yield Effect(OtherIntent('b'))
yield do_return((r, r2))

seq = [(MyIntent('a'), lambda i: 'result1')]
with pytest.raises(AssertionError) as exc:
perform_sequence(seq, code_under_test())

expected = ("sequence: MyIntent(val='a')\n"
"NOT FOUND: OtherIntent(val='b')")
assert expected in str(exc.value)
Loading