diff --git a/tests/test_cancellation.py b/tests/test_cancellation.py index 01b3af2..0dd33c2 100644 --- a/tests/test_cancellation.py +++ b/tests/test_cancellation.py @@ -14,26 +14,65 @@ async def assert_err(): assert 0 -def test_remote_error(arb_addr): - """Verify an error raises in a subactor is propagated to the parent. +@pytest.mark.parametrize( + 'args_err', + [ + # expected to be thrown in assert_err + ({}, AssertionError), + # argument mismatch raised in _invoke() + ({'unexpected': 10}, TypeError) + ], + ids=['no_args', 'unexpected_args'], +) +def test_remote_error(arb_addr, args_err): + """Verify an error raised in a subactor that is propagated + to the parent nursery, contains underlying builtin erorr type + infot and causes cancellation and reraising. + """ + args, errtype = args_err + + async def main(): + async with tractor.open_nursery() as nursery: + + portal = await nursery.run_in_actor('errorer', assert_err, **args) + + # get result(s) from main task + try: + await portal.result() + except tractor.RemoteActorError as err: + assert err.type == errtype + print("Look Maa that actor failed hard, hehh") + raise + else: + assert 0, "Remote error was not raised?" + + with pytest.raises(tractor.RemoteActorError): + # also raises + tractor.run(main, arbiter_addr=arb_addr) + + +def test_multierror(arb_addr): + """Verify we raise a ``trio.MultiError`` out of a nursery where + more then one actor errors. """ async def main(): async with tractor.open_nursery() as nursery: - portal = await nursery.run_in_actor('errorer', assert_err) + await nursery.run_in_actor('errorer1', assert_err) + portal2 = await nursery.run_in_actor('errorer2', assert_err) # get result(s) from main task try: - return await portal.result() - except tractor.RemoteActorError: - print("Look Maa that actor failed hard, hehh") + await portal2.result() + except tractor.RemoteActorError as err: + assert err.type == AssertionError + print("Look Maa that first actor failed hard, hehh") raise - except Exception: - pass - assert 0, "Remote error was not raised?" - with pytest.raises(tractor.RemoteActorError): - # also raises + # here we should get a `trio.MultiError` containing exceptions + # from both subactors + + with pytest.raises(trio.MultiError): tractor.run(main, arbiter_addr=arb_addr) @@ -42,9 +81,12 @@ def do_nothing(): def test_cancel_single_subactor(arb_addr): - - async def main(): - + """Ensure a ``ActorNursery.start_actor()`` spawned subactor + cancels when the nursery is cancelled. + """ + async def spawn_actor(): + """Spawn an actor that blocks indefinitely. + """ async with tractor.open_nursery() as nursery: portal = await nursery.start_actor( @@ -55,7 +97,7 @@ def test_cancel_single_subactor(arb_addr): # would hang otherwise await nursery.cancel() - tractor.run(main, arbiter_addr=arb_addr) + tractor.run(spawn_actor, arbiter_addr=arb_addr) async def stream_forever(): @@ -87,13 +129,22 @@ async def test_cancel_infinite_streamer(): assert n.cancelled +@pytest.mark.parametrize( + 'num_actors_and_errs', + [ + (1, tractor.RemoteActorError, AssertionError), + (2, tractor.MultiError, AssertionError) + ], + ids=['one_actor', 'two_actors'], +) @tractor_test -async def test_one_cancels_all(): +async def test_some_cancels_all(num_actors_and_errs): """Verify one failed actor causes all others in the nursery to be cancelled just like in trio. This is the first and only supervisory strategy at the moment. """ + num, first_err, err_type = num_actors_and_errs try: async with tractor.open_nursery() as n: real_actors = [] @@ -103,13 +154,21 @@ async def test_one_cancels_all(): rpc_module_paths=[__name__], )) - # start one actor that will fail immediately - await n.run_in_actor('extra', assert_err) + for i in range(num): + # start one actor that will fail immediately + await n.run_in_actor(f'extra_{i}', assert_err) - # should error here with a ``RemoteActorError`` containing - # an ``AssertionError` + # should error here with a ``RemoteActorError`` or + # ``MultiError`` containing an ``AssertionError` + + except first_err as err: + if isinstance(err, trio.MultiError): + assert len(err.exceptions) == num + for exc in err.exceptions: + assert exc.type == err_type + else: + assert err.type == err_type - except tractor.RemoteActorError: assert n.cancelled is True assert not n._children else: