Add a `tractor.post_mortem()` API test + example
Since turns out we didn't have a single example using that API Bo The test granular-ly checks all use cases: - `.post_mortem()` manual calls in both subactor and root. - ensuring built-in RPC crash handling activates after each manual one from ^. - drafted some call-stack frame checking that i commented out for now since we need to first do ANSI escape code removal due to the colorization that `pdbp` does by default. |_ added a TODO with SO link on `assert_before()`. Also todo-staged a shielded-pause test to match with the already existing-but-needs-refinement example B)runtime_to_msgspec
parent
cdb1311e40
commit
2f854a3e86
|
@ -0,0 +1,56 @@
|
|||
import trio
|
||||
import tractor
|
||||
|
||||
|
||||
@tractor.context
|
||||
async def name_error(
|
||||
ctx: tractor.Context,
|
||||
):
|
||||
'''
|
||||
Raise a `NameError`, catch it and enter `.post_mortem()`, then
|
||||
expect the `._rpc._invoke()` crash handler to also engage.
|
||||
|
||||
'''
|
||||
try:
|
||||
getattr(doggypants) # noqa (on purpose)
|
||||
except NameError:
|
||||
await tractor.post_mortem()
|
||||
raise
|
||||
|
||||
|
||||
async def main():
|
||||
'''
|
||||
Test 3 `PdbREPL` entries:
|
||||
- one in the child due to manual `.post_mortem()`,
|
||||
- another in the child due to runtime RPC crash handling.
|
||||
- final one here in parent from the RAE.
|
||||
|
||||
'''
|
||||
# XXX NOTE: ideally the REPL arrives at this frame in the parent
|
||||
# ONE UP FROM the inner ctx block below!
|
||||
async with tractor.open_nursery(
|
||||
debug_mode=True,
|
||||
# loglevel='cancel',
|
||||
) as an:
|
||||
p: tractor.Portal = await an.start_actor(
|
||||
'child',
|
||||
enable_modules=[__name__],
|
||||
)
|
||||
|
||||
# XXX should raise `RemoteActorError[NameError]`
|
||||
# AND be the active frame when REPL enters!
|
||||
try:
|
||||
async with p.open_context(name_error) as (ctx, first):
|
||||
assert first
|
||||
except tractor.RemoteActorError as rae:
|
||||
assert rae.boxed_type is NameError
|
||||
|
||||
# manually handle in root's parent task
|
||||
await tractor.post_mortem()
|
||||
raise
|
||||
else:
|
||||
raise RuntimeError('IPC ctx should have remote errored!?')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
trio.run(main)
|
|
@ -161,6 +161,10 @@ def in_prompt_msg(
|
|||
|
||||
return True
|
||||
|
||||
|
||||
# TODO: todo support terminal color-chars stripping so we can match
|
||||
# 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,
|
||||
patts: list[str],
|
||||
|
@ -1125,7 +1129,112 @@ def test_pause_from_sync(
|
|||
child.expect(pexpect.EOF)
|
||||
|
||||
|
||||
# TODO!
|
||||
def test_post_mortem_api(
|
||||
spawn,
|
||||
ctlc: bool,
|
||||
):
|
||||
'''
|
||||
Verify the `tractor.post_mortem()` API works in an exception
|
||||
handler block.
|
||||
|
||||
'''
|
||||
child = spawn('pm_in_subactor')
|
||||
|
||||
# First entry is via manual `.post_mortem()`
|
||||
child.expect(PROMPT)
|
||||
assert_before(
|
||||
child,
|
||||
[
|
||||
_crash_msg,
|
||||
"<Task 'name_error'",
|
||||
"NameError",
|
||||
"('child'",
|
||||
"tractor.post_mortem()",
|
||||
]
|
||||
)
|
||||
if ctlc:
|
||||
do_ctlc(child)
|
||||
child.sendline('c')
|
||||
|
||||
# 2nd is RPC crash handler
|
||||
child.expect(PROMPT)
|
||||
assert_before(
|
||||
child,
|
||||
[
|
||||
_crash_msg,
|
||||
"<Task 'name_error'",
|
||||
"NameError",
|
||||
"('child'",
|
||||
]
|
||||
)
|
||||
if ctlc:
|
||||
do_ctlc(child)
|
||||
child.sendline('c')
|
||||
|
||||
# 3rd is via RAE bubbled to root's parent ctx task and
|
||||
# crash-handled via another manual pm call.
|
||||
child.expect(PROMPT)
|
||||
assert_before(
|
||||
child,
|
||||
[
|
||||
_crash_msg,
|
||||
"<Task '__main__.main'",
|
||||
"('root'",
|
||||
"NameError",
|
||||
"tractor.post_mortem()",
|
||||
"src_uid=('child'",
|
||||
]
|
||||
)
|
||||
if ctlc:
|
||||
do_ctlc(child)
|
||||
child.sendline('c')
|
||||
|
||||
# 4th and FINAL is via RAE bubbled to root's parent ctx task and
|
||||
# crash-handled via another manual pm call.
|
||||
child.expect(PROMPT)
|
||||
assert_before(
|
||||
child,
|
||||
[
|
||||
_crash_msg,
|
||||
"<Task '__main__.main'",
|
||||
"('root'",
|
||||
"NameError",
|
||||
"src_uid=('child'",
|
||||
]
|
||||
)
|
||||
if ctlc:
|
||||
do_ctlc(child)
|
||||
|
||||
|
||||
# TODO: ensure we're stopped and showing the right call stack frame
|
||||
# -[ ] need a way to strip the terminal color chars in order to
|
||||
# pattern match... see TODO around `assert_before()` above!
|
||||
# child.sendline('w')
|
||||
# child.expect(PROMPT)
|
||||
# assert_before(
|
||||
# child,
|
||||
# [
|
||||
# # error src block annot at ctx open
|
||||
# '-> async with p.open_context(name_error) as (ctx, first):',
|
||||
# ]
|
||||
# )
|
||||
|
||||
# # step up a frame to ensure the it's the root's nursery
|
||||
# child.sendline('u')
|
||||
# child.expect(PROMPT)
|
||||
# assert_before(
|
||||
# child,
|
||||
# [
|
||||
# # handler block annotation
|
||||
# '-> async with tractor.open_nursery(',
|
||||
# ]
|
||||
# )
|
||||
|
||||
child.sendline('c')
|
||||
child.expect(pexpect.EOF)
|
||||
|
||||
|
||||
# TODO: needs ANSI code stripping tho, see `assert_before()` # above!
|
||||
def test_correct_frames_below_hidden():
|
||||
'''
|
||||
Ensure that once a `tractor.pause()` enages, when the user
|
||||
|
@ -1138,4 +1247,15 @@ def test_correct_frames_below_hidden():
|
|||
|
||||
|
||||
def test_cant_pause_from_paused_task():
|
||||
'''
|
||||
Pausing from with an already paused task should raise an error.
|
||||
|
||||
Normally this should only happen in practise while debugging the call stack of `tractor.pause()` itself, likely
|
||||
by a `.pause()` line somewhere inside our runtime.
|
||||
|
||||
'''
|
||||
...
|
||||
|
||||
|
||||
def test_shield_pause():
|
||||
...
|
||||
|
|
Loading…
Reference in New Issue