From 1f1a3f19d50405f418398b70d36443fdb7654064 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 12 Jul 2024 15:57:41 -0400 Subject: [PATCH] Fix multi-daemon debug test `break` signal.. It was expecting `AssertionError` as a proceed-in-test signal (by breaking from a continue loop), but `in_prompt_msg(raise_on_err=True)` was changed to raise `ValueError`; so instead just use as a predicate for the `break`. Also rework `in_prompt_msg()` to accept the `child: BaseSpawn` as input instead of `before: str` remove the casting boilerplate, and adjust all usage to match. --- examples/debugging/multi_daemon_subactors.py | 3 +- tests/devx/conftest.py | 17 +-- tests/devx/test_debugger.py | 118 +++++++++---------- tests/test_infected_asyncio.py | 6 +- 4 files changed, 71 insertions(+), 73 deletions(-) diff --git a/examples/debugging/multi_daemon_subactors.py b/examples/debugging/multi_daemon_subactors.py index 80ef933..4a46262 100644 --- a/examples/debugging/multi_daemon_subactors.py +++ b/examples/debugging/multi_daemon_subactors.py @@ -25,7 +25,8 @@ async def main(): """ async with tractor.open_nursery( debug_mode=True, - loglevel='cancel', + # loglevel='cancel', + # loglevel='devx', ) as n: p0 = await n.start_actor('bp_forever', enable_modules=[__name__]) diff --git a/tests/devx/conftest.py b/tests/devx/conftest.py index b739569..28a14cb 100644 --- a/tests/devx/conftest.py +++ b/tests/devx/conftest.py @@ -10,6 +10,7 @@ import pytest from pexpect.exceptions import ( TIMEOUT, ) +from pexpect.spawnbase import SpawnBase from tractor._testing import ( mk_cmd, ) @@ -107,7 +108,7 @@ def expect( def in_prompt_msg( - prompt: str, + child: SpawnBase, parts: list[str], pause_on_false: bool = False, @@ -125,18 +126,20 @@ def in_prompt_msg( ''' __tracebackhide__: bool = False + before: str = str(child.before.decode()) for part in parts: - if part not in prompt: + if part not in before: if pause_on_false: import pdbp pdbp.set_trace() if print_prompt_on_false: - print(prompt) + print(before) if err_on_false: raise ValueError( - f'Could not find pattern: {part!r} in `before` output?' + f'Could not find pattern in `before` output?\n' + f'part: {part!r}\n' ) return False @@ -147,7 +150,7 @@ def in_prompt_msg( # against call stack frame output from the the 'll' command the like! # -[ ] SO answer for stipping ANSI codes: https://stackoverflow.com/a/14693789 def assert_before( - child, + child: SpawnBase, patts: list[str], **kwargs, @@ -155,10 +158,8 @@ def assert_before( ) -> None: __tracebackhide__: bool = False - # as in before the prompt end - before: str = str(child.before.decode()) assert in_prompt_msg( - prompt=before, + child=child, parts=patts, # since this is an "assert" helper ;) diff --git a/tests/devx/test_debugger.py b/tests/devx/test_debugger.py index bb59045..ce4f1ed 100644 --- a/tests/devx/test_debugger.py +++ b/tests/devx/test_debugger.py @@ -96,14 +96,15 @@ def test_root_actor_error( # scan for the prompt expect(child, PROMPT) - before = str(child.before.decode()) - # make sure expected logging and error arrives assert in_prompt_msg( - before, - [_crash_msg, "('root'"] + child, + [ + _crash_msg, + "('root'", + 'AssertionError', + ] ) - assert 'AssertionError' in before # send user command child.sendline(user_input) @@ -243,10 +244,12 @@ def test_subactor_error( # scan for the prompt child.expect(PROMPT) - before = str(child.before.decode()) assert in_prompt_msg( - before, - [_crash_msg, "('name_error'"] + child, + [ + _crash_msg, + "('name_error'", + ] ) if do_next: @@ -265,17 +268,15 @@ def test_subactor_error( child.sendline('continue') child.expect(PROMPT) - before = str(child.before.decode()) - - # root actor gets debugger engaged assert in_prompt_msg( - before, - [_crash_msg, "('root'"] - ) - # error is a remote error propagated from the subactor - assert in_prompt_msg( - before, - [_crash_msg, "('name_error'"] + child, + [ + _crash_msg, + # root actor gets debugger engaged + "('root'", + # error is a remote error propagated from the subactor + "('name_error'", + ] ) # another round @@ -296,14 +297,11 @@ def test_subactor_breakpoint( "Single subactor with an infinite breakpoint loop" child = spawn('subactor_breakpoint') - - # scan for the prompt child.expect(PROMPT) - - before = str(child.before.decode()) assert in_prompt_msg( - before, - [_pause_msg, "('breakpoint_forever'"] + child, + [_pause_msg, + "('breakpoint_forever'",] ) # do some "next" commands to demonstrate recurrent breakpoint @@ -319,9 +317,8 @@ def test_subactor_breakpoint( for _ in range(5): child.sendline('continue') child.expect(PROMPT) - before = str(child.before.decode()) assert in_prompt_msg( - before, + child, [_pause_msg, "('breakpoint_forever'"] ) @@ -334,9 +331,8 @@ def test_subactor_breakpoint( # child process should exit but parent will capture pdb.BdbQuit child.expect(PROMPT) - before = str(child.before.decode()) assert in_prompt_msg( - before, + child, ['RemoteActorError:', "('breakpoint_forever'", 'bdb.BdbQuit',] @@ -351,9 +347,8 @@ def test_subactor_breakpoint( # process should exit child.expect(EOF) - before = str(child.before.decode()) assert in_prompt_msg( - before, + child, ['RemoteActorError:', "('breakpoint_forever'", 'bdb.BdbQuit',] @@ -377,7 +372,7 @@ def test_multi_subactors( before = str(child.before.decode()) assert in_prompt_msg( - before, + child, [_pause_msg, "('breakpoint_forever'"] ) @@ -398,12 +393,14 @@ def test_multi_subactors( # first name_error failure child.expect(PROMPT) - before = str(child.before.decode()) assert in_prompt_msg( - before, - [_crash_msg, "('name_error'"] + child, + [ + _crash_msg, + "('name_error'", + "NameError", + ] ) - assert "NameError" in before if ctlc: do_ctlc(child) @@ -427,9 +424,8 @@ def test_multi_subactors( # breakpoint loop should re-engage child.sendline('c') child.expect(PROMPT) - before = str(child.before.decode()) assert in_prompt_msg( - before, + child, [_pause_msg, "('breakpoint_forever'"] ) @@ -522,25 +518,28 @@ def test_multi_daemon_subactors( # the root's tty lock first so anticipate either crash # message on the first entry. - bp_forev_parts = [_pause_msg, "('bp_forever'"] + bp_forev_parts = [ + _pause_msg, + "('bp_forever'", + ] bp_forev_in_msg = partial( in_prompt_msg, parts=bp_forev_parts, ) - name_error_msg = "NameError: name 'doggypants' is not defined" - name_error_parts = [name_error_msg] + name_error_msg: str = "NameError: name 'doggypants' is not defined" + name_error_parts: list[str] = [name_error_msg] before = str(child.before.decode()) - if bp_forev_in_msg(prompt=before): + if bp_forev_in_msg(child=child): next_parts = name_error_parts elif name_error_msg in before: next_parts = bp_forev_parts else: - raise ValueError("Neither log msg was found !?") + raise ValueError('Neither log msg was found !?') if ctlc: do_ctlc(child) @@ -609,14 +608,12 @@ def test_multi_daemon_subactors( # wait for final error in root # where it crashs with boxed error while True: - try: - child.sendline('c') - child.expect(PROMPT) - assert_before( - child, - bp_forev_parts - ) - except AssertionError: + child.sendline('c') + child.expect(PROMPT) + if not in_prompt_msg( + child, + bp_forev_parts + ): break assert_before( @@ -797,10 +794,13 @@ def test_root_nursery_cancels_before_child_releases_tty_lock( child = spawn('root_cancelled_but_child_is_in_tty_lock') child.expect(PROMPT) - - before = str(child.before.decode()) - assert "NameError: name 'doggypants' is not defined" in before - assert "tractor._exceptions.RemoteActorError: ('name_error'" not in before + assert_before( + child, + [ + "NameError: name 'doggypants' is not defined", + "tractor._exceptions.RemoteActorError: ('name_error'", + ], + ) time.sleep(0.5) if ctlc: @@ -891,9 +891,8 @@ def test_different_debug_mode_per_actor( child.expect(PROMPT) # only one actor should enter the debugger - before = str(child.before.decode()) assert in_prompt_msg( - before, + child, [_crash_msg, "('debugged_boi'", "RuntimeError"], ) @@ -903,8 +902,6 @@ def test_different_debug_mode_per_actor( child.sendline('c') child.expect(EOF) - before = str(child.before.decode()) - # NOTE: this debugged actor error currently WON'T show up since the # root will actually cancel and terminate the nursery before the error # msg reported back from the debug mode actor is processed. @@ -956,9 +953,8 @@ def test_pause_from_sync( child.expect(PROMPT) # XXX shouldn't see gb loaded message with PDB loglevel! - before = str(child.before.decode()) assert not in_prompt_msg( - before, + child, ['`greenback` portal opened!'], ) # should be same root task @@ -1039,7 +1035,7 @@ def test_pause_from_sync( # at the same time as the one that was detected above. for key, other_patts in attach_patts.copy().items(): assert not in_prompt_msg( - before, + child, other_patts, ) diff --git a/tests/test_infected_asyncio.py b/tests/test_infected_asyncio.py index fca971d..f5fa0aa 100644 --- a/tests/test_infected_asyncio.py +++ b/tests/test_infected_asyncio.py @@ -291,7 +291,7 @@ def test_context_spawns_aio_task_that_errors( err = excinfo.value assert isinstance(err, expect) - assert err.boxed_type == AssertionError + assert err.boxed_type is AssertionError async def aio_cancel(): @@ -497,7 +497,7 @@ def test_trio_error_cancels_intertask_chan(reg_addr): trio.run(main) # ensure boxed error type - excinfo.value.boxed_type == Exception + excinfo.value.boxed_type is Exception def test_trio_closes_early_and_channel_exits(reg_addr): @@ -533,7 +533,7 @@ def test_aio_errors_and_channel_propagates_and_closes(reg_addr): ) as excinfo: trio.run(main) - excinfo.value.boxed_type == Exception + excinfo.value.boxed_type is Exception @tractor.context