diff --git a/examples/debugging/multi_subactor_root_errors.py b/examples/debugging/multi_subactor_root_errors.py new file mode 100644 index 0000000..f7a4372 --- /dev/null +++ b/examples/debugging/multi_subactor_root_errors.py @@ -0,0 +1,43 @@ +import tractor + + +async def name_error(): + "Raise a ``NameError``" + getattr(doggypants) + + +async def spawn_error(): + """"A nested nursery that triggers another ``NameError``. + """ + async with tractor.open_nursery() as n: + portal = await n.run_in_actor('name_error_1', name_error) + return await portal.result() + + +async def main(): + """The main ``tractor`` routine. + + The process tree should look as approximately as follows: + + -python examples/debugging/multi_subactors.py + |-python -m tractor._child --uid ('name_error', 'a7caf490 ...) + `-python -m tractor._child --uid ('spawn_error', '52ee14a5 ...) + `-python -m tractor._child --uid ('name_error', '3391222c ...) + """ + async with tractor.open_nursery() as n: + + # spawn both actors + portal = await n.run_in_actor('name_error', name_error) + portal1 = await n.run_in_actor('spawn_error', spawn_error) + + # trigger a root actor error + assert 0 + + # attempt to collect results (which raises error in parent) + # still has some issues where the parent seems to get stuck + await portal.result() + await portal1.result() + + +if __name__ == '__main__': + tractor.run(main, debug_mode=True) diff --git a/examples/debugging/multi_subactors.py b/examples/debugging/multi_subactors.py index 97de906..16ff22d 100644 --- a/examples/debugging/multi_subactors.py +++ b/examples/debugging/multi_subactors.py @@ -35,16 +35,12 @@ async def main(): """ async with tractor.open_nursery() as n: - # spawn both actors - portal1 = await n.run_in_actor('bp_forever', breakpoint_forever) - portal = await n.run_in_actor('name_error', name_error) - portal2 = await n.run_in_actor('spawn_error', spawn_error) - - # attempt to collect results (which raises error in parent) - # still has some issues where the parent seems to get stuck - # await portal.result() - # await portal1.result() - # await portal2.result() + # Spawn both actors, don't bother with collecting results + # (would result in a different debugger outcome due to parent's + # cancellation). + await n.run_in_actor('bp_forever', breakpoint_forever) + await n.run_in_actor('name_error', name_error) + await n.run_in_actor('spawn_error', spawn_error) if __name__ == '__main__': diff --git a/tests/test_debugger.py b/tests/test_debugger.py index 0770a14..8c87791 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -1,5 +1,8 @@ """ That native debug better work! + +All these tests can be understood (somewhat) by running the equivalent +`examples/debugging/` scripts manually. """ from os import path @@ -10,9 +13,8 @@ from .test_docs_examples import repodir # TODO: -# - recurrent entry from single actor # - recurrent entry to breakpoint() from single actor *after* and an -# error +# error in another task? # - root error before child errors # - root error after child errors # - root error before child breakpoint @@ -68,14 +70,14 @@ def test_root_actor_error(spawn, user_in_out): # scan for the pdbpp prompt child.expect(r"\(Pdb\+\+\)") + before = str(child.before.decode()) + # make sure expected logging and error arrives - assert 'TTY lock acquired' in str(child.before) - assert 'AssertionError' in str(child.before) + assert "Attaching to pdb in crashed actor: ('arbiter'" in before + assert 'AssertionError' in before # send user command child.sendline(user_input) - child.expect('\r\n') - child.expect('TTY lock released') # process should exit child.expect(pexpect.EOF) @@ -276,3 +278,35 @@ def test_multi_subactors(spawn): before = str(child.before.decode()) assert "RemoteActorError: ('bp_forever'" in before assert 'bdb.BdbQuit' in before + + +def test_multi_subactors_root_errors(spawn): + """Multiple subactors, both erroring and breakpointing as well as + a nested subactor erroring. + """ + child = spawn('multi_subactor_root_errors') + + # scan for the pdbpp prompt + child.expect(r"\(Pdb\+\+\)") + + # at most one subactor should attach before the root is cancelled + before = str(child.before.decode()) + assert "NameError: name 'doggypants' is not defined" in before + + # continue again + child.sendline('c') + child.expect(r"\(Pdb\+\+\)") + + # should now get attached in root with assert error + before = str(child.before.decode()) + # should have come just after priot prompt + assert "Cancelling nursery in ('spawn_error'," in before + assert "Attaching to pdb in crashed actor: ('arbiter'" in before + assert "AssertionError" in before + + # continue again + child.sendline('c') + child.expect(pexpect.EOF) + + before = str(child.before.decode()) + assert "AssertionError" in before