Skip to content

Address some review comments. #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
5 changes: 3 additions & 2 deletions effect/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ def perform(dispatcher, effect, recurse_effects=True):
you can ignore the box by using a decorator like :func:`sync_performer` or
:func:`effect.twisted.deferred_performer`.

Callbacks can return Effects, and those effects will immediately performed.
The result of the returned Effect will be passed to the next callback.
Unless ``recurse_effects`` is ``False``, Callbacks can return Effects, and
those effects will immediately performed. The result of the returned
Effect will be passed to the next callback.

Note that this function does _not_ return the final result of the effect.
You may instead want to use :func:`effect.sync_perform` or
Expand Down
146 changes: 128 additions & 18 deletions effect/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@
from ._base import Effect, NoPerformerFoundError, perform


class POPOIntent(object):
"""An example effect intent."""


def func_dispatcher(intent):
"""
Simple effect dispatcher that takes callables taking a box,
Expand Down Expand Up @@ -42,6 +38,11 @@ def test_no_performer(self):
]))

def test_dispatcher(self):
"""
``perform`` calls the provided dispatcher, with the intent of the
provided effect. The returned perform is called with the dispatcher and
intent.
"""
calls = []

def dispatcher(intent):
Expand Down Expand Up @@ -80,9 +81,81 @@ def test_error_with_callback(self):
MatchesListwise([
MatchesException(ValueError('dispatched'))]))

def test_success_propagates_effect_exception(self):
"""
If an succes callback is specified, but a exception result occurs,
the exception is passed to the next callback.
"""

calls = []
intent = lambda box: box.fail(
(ValueError, ValueError('dispatched'), None))
perform(func_dispatcher,
Effect(intent)
.on(success=lambda box: calls.append("foo"))
.on(error=calls.append))
self.assertThat(
calls,
MatchesListwise([
MatchesException(ValueError('dispatched'))]))

def test_error_propagates_effect_result(self):
"""
If an error callback is specified, but a succesful result occurs,
the success is passed to the next callback.
"""
calls = []
intent = lambda box: box.succeed("dispatched")
perform(func_dispatcher,
Effect(intent)
.on(error=lambda box: calls.append("foo"))
.on(success=calls.append))
self.assertEqual(calls, ["dispatched"])

def test_callback_sucecss_exception(self):
"""
If a success callback raises an error, the exception is passed to the
error callback.
"""
calls = []

def raise_(_):
raise ValueError("oh dear")

perform(func_dispatcher,
Effect(lambda box: box.succeed("foo"))
.on(success=lambda _: raise_(ValueError("oh dear")))
.on(error=calls.append))
self.assertThat(
calls,
MatchesListwise([
MatchesException(ValueError('oh dear'))]))

def test_callback_error_exception(self):
"""
If a error callback raises an error, the exception is passed to the
error callback.
"""
calls = []

def raise_(_):
raise ValueError("oh dear")

intent = lambda box: box.fail(
(ValueError, ValueError('dispatched'), None))

perform(func_dispatcher,
Effect(intent)
.on(error=lambda _: raise_(ValueError("oh dear")))
.on(error=calls.append))
self.assertThat(
calls,
MatchesListwise([
MatchesException(ValueError('oh dear'))]))

def test_effects_returning_effects(self):
"""
When the effect dispatcher returns another effect,
When the effect performer returns another effect,
- that effect is immediately performed with the same dispatcher,
- the result of that is returned.
"""
Expand All @@ -105,6 +178,34 @@ def test_effects_returning_effects_returning_effects(self):
Effect(lambda box: calls.append("foo")))))))
self.assertEqual(calls, ['foo'])

def test_nested_effect_exception_passes_to_outer_error_handler(self):
"""
If an inner effect raises an exception, it bubbles up and the
exc_info is passed to the outer effect's error handlers.
"""
calls = []
intent = lambda box: box.fail(
(ValueError, ValueError('oh dear'), None))
perform(func_dispatcher,
Effect(lambda box: box.succeed(Effect(intent)))
.on(error=calls.append))
self.assertThat(
calls,
MatchesListwise([
MatchesException(ValueError('oh dear'))]))

def test_recurse_effects(self):
"""
If ``recurse_effects`` is ``False``, and an effect returns another
effect, that effect returned.
"""
calls = []
effect = Effect(lambda box: calls.append("foo"))
perform(func_dispatcher,
Effect(lambda box: box.succeed(effect)).on(calls.append),
recurse_effects=False)
self.assertEqual(calls, [effect])

def test_bounced(self):
"""
The callbacks of a performer are called after the performer returns.
Expand Down Expand Up @@ -145,21 +246,30 @@ def get_stack(box):
Effect(get_stack).on(success=lambda _: Effect(get_stack)))
self.assertEqual(calls[0], calls[1])

def test_callback_error(self):
def test_asynchronous_callback_invocation(self):
"""
If a callback raises an error, the exception is passed to the error
callback.
When an Effect that is returned by a callback is resolved
*asynchronously*, the callbacks will run.
"""
results = []
boxes = []
eff = Effect(boxes.append).on(success=results.append)
perform(func_dispatcher, eff)
boxes[0].succeed('foo')
self.assertEqual(results, ['foo'])

def test_asynchronous_callback_bounced(self):
"""
When an Effect that is returned by a callback is resolved
*asynchronously*, the callbacks are performed at the same stack depth.
"""
calls = []

def raise_(_):
raise ValueError("oh dear")
def get_stack(_):
calls.append(traceback.extract_stack())

perform(func_dispatcher,
Effect(lambda box: box.succeed("foo"))
.on(success=lambda _: raise_(ValueError("oh dear")))
.on(error=calls.append))
self.assertThat(
calls,
MatchesListwise([
MatchesException(ValueError('oh dear'))]))
boxes = []
eff = Effect(boxes.append).on(success=get_stack).on(success=get_stack)
perform(func_dispatcher, eff)
boxes[0].succeed('foo')
self.assertEqual(calls[0], calls[1])
Loading