diff --git a/examples/debugging/mp_debug.py b/examples/debugging/mp_debug.py deleted file mode 100644 index fff92d3..0000000 --- a/examples/debugging/mp_debug.py +++ /dev/null @@ -1,39 +0,0 @@ -import tractor -import trio - - -async def bubble(): - print('IN BUBBLE') - while True: - await trio.sleep(.1) - await tractor.breakpoint() - - -async def name_error(): - getattr(doggy) - - -async def main(): - """The main ``tractor`` routine. - """ - async with tractor.open_nursery() as n: - - portal1 = await n.run_in_actor('bubble', bubble) - portal = await n.run_in_actor('name_error', name_error) - await portal1.result() - await portal.result() - - # The ``async with`` will unblock here since the 'some_linguist' - # actor has completed its main task ``cellar_door``. - - -# TODO: -# - recurrent entry from single actor -# - recurrent entry to breakpoint() from single actor *after* and an -# error -# - root error alongside child errors -# - recurrent root errors - - -if __name__ == '__main__': - tractor.run(main, loglevel='info', debug_mode=True) diff --git a/examples/debugging/multi_subactors.py b/examples/debugging/multi_subactors.py new file mode 100644 index 0000000..97de906 --- /dev/null +++ b/examples/debugging/multi_subactors.py @@ -0,0 +1,51 @@ +import tractor +import trio + + +async def breakpoint_forever(): + "Indefinitely re-enter debugger in child actor." + while True: + await trio.sleep(0.1) + await tractor.breakpoint() + + +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 ('bp_forever', '1f787a7e ...) + `-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 + 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() + + +if __name__ == '__main__': + tractor.run(main, debug_mode=True) diff --git a/tests/test_debugger.py b/tests/test_debugger.py index a26f9ce..de76247 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -9,6 +9,17 @@ import pexpect from .test_docs_examples import repodir +# TODO: +# - recurrent entry from single actor +# - recurrent entry to breakpoint() from single actor *after* and an +# error +# - root error before child errors +# - root error after child errors +# - root error before child breakpoint +# - root error after child breakpoint +# - recurrent root errors + + def examples_dir(): """Return the abspath to the examples directory. """ @@ -45,7 +56,7 @@ def spawn( ('c', 'AssertionError'), ('q', 'AssertionError'), ], - ids=lambda item: item[1], + ids=lambda item: f'{item[0]} -> {item[1]}', ) def test_root_actor_error(spawn, user_in_out): """Demonstrate crash handler entering pdbpp from basic error in root actor. @@ -55,7 +66,7 @@ def test_root_actor_error(spawn, user_in_out): child = spawn('root_actor_error') # scan for the pdbpp prompt - child.expect("\(Pdb\+\+\)") + child.expect(r"\(Pdb\+\+\)") # make sure expected logging and error arrives assert 'TTY lock acquired' in str(child.before) @@ -86,7 +97,7 @@ def test_root_actor_bp(spawn, user_in_out): child = spawn('root_actor_breakpoint') # scan for the pdbpp prompt - child.expect("\(Pdb\+\+\)") + child.expect(r"\(Pdb\+\+\)") assert 'Error' not in str(child.before) @@ -104,10 +115,12 @@ def test_root_actor_bp(spawn, user_in_out): def test_subactor_error(spawn): + "Single subactor raising an error" + child = spawn('subactor_error') # scan for the pdbpp prompt - child.expect("\(Pdb\+\+\)") + child.expect(r"\(Pdb\+\+\)") before = str(child.before.decode()) assert "Attaching to pdb in crashed actor: ('name_error'" in before @@ -120,7 +133,7 @@ def test_subactor_error(spawn): # the debugger should enter a second time in the nursery # creating actor - child.expect("\(Pdb\+\+\)") + child.expect(r"\(Pdb\+\+\)") before = str(child.before.decode()) @@ -138,10 +151,12 @@ def test_subactor_error(spawn): def test_subactor_breakpoint(spawn): + "Single subactor with an infinite breakpoint loop" + child = spawn('subactor_breakpoint') # scan for the pdbpp prompt - child.expect("\(Pdb\+\+\)") + child.expect(r"\(Pdb\+\+\)") before = str(child.before.decode()) assert "Attaching pdb to actor: ('breakpoint_forever'" in before @@ -150,12 +165,12 @@ def test_subactor_breakpoint(spawn): # entries for _ in range(10): child.sendline('next') - child.expect("\(Pdb\+\+\)") + child.expect(r"\(Pdb\+\+\)") # now run some "continues" to show re-entries for _ in range(5): child.sendline('continue') - child.expect("\(Pdb\+\+\)") + child.expect(r"\(Pdb\+\+\)") before = str(child.before.decode()) assert "Attaching pdb to actor: ('breakpoint_forever'" in before @@ -163,7 +178,7 @@ def test_subactor_breakpoint(spawn): child.sendline('q') # child process should exit but parent will capture pdb.BdbQuit - child.expect("\(Pdb\+\+\)") + child.expect(r"\(Pdb\+\+\)") before = str(child.before.decode()) assert "RemoteActorError: ('breakpoint_forever'" in before @@ -178,3 +193,65 @@ def test_subactor_breakpoint(spawn): before = str(child.before.decode()) assert "RemoteActorError: ('breakpoint_forever'" in before assert 'bdb.BdbQuit' in before + + +def test_multi_subactors(spawn): + """Multiple subactors, both erroring and breakpointing as well as + a nested subactor erroring. + """ + child = spawn(r'multi_subactors') + + # scan for the pdbpp prompt + child.expect(r"\(Pdb\+\+\)") + + before = str(child.before.decode()) + assert "Attaching pdb to actor: ('bp_forever'" in before + + # do some "next" commands to demonstrate recurrent breakpoint + # entries + for _ in range(10): + child.sendline('next') + child.expect(r"\(Pdb\+\+\)") + + # continue to next error + child.sendline('c') + + # first name_error failure + child.expect(r"\(Pdb\+\+\)") + before = str(child.before.decode()) + assert "NameError" in before + + # continue again + child.sendline('c') + + # 2nd name_error failure + child.expect(r"\(Pdb\+\+\)") + before = str(child.before.decode()) + assert "NameError" in before + + # breakpoint loop should re-engage + child.sendline('c') + child.expect(r"\(Pdb\+\+\)") + before = str(child.before.decode()) + assert "Attaching pdb to actor: ('bp_forever'" in before + + # now run some "continues" to show re-entries + for _ in range(5): + child.sendline('c') + child.expect(r"\(Pdb\+\+\)") + + # quit the loop and expect parent to attach + child.sendline('q') + child.expect(r"\(Pdb\+\+\)") + before = str(child.before.decode()) + assert "Attaching to pdb in crashed actor: ('arbiter'" in before + assert "RemoteActorError: ('bp_forever'" in before + assert 'bdb.BdbQuit' in before + + # process should exit + child.sendline('c') + child.expect(pexpect.EOF) + + before = str(child.before.decode()) + assert "RemoteActorError: ('bp_forever'" in before + assert 'bdb.BdbQuit' in before