2020-10-03 23:35:18 +00:00
|
|
|
"""
|
2022-06-27 20:22:45 +00:00
|
|
|
That "native" debug mode better work!
|
2020-10-05 15:38:39 +00:00
|
|
|
|
|
|
|
All these tests can be understood (somewhat) by running the equivalent
|
|
|
|
`examples/debugging/` scripts manually.
|
2020-10-14 13:06:40 +00:00
|
|
|
|
2021-12-09 22:51:53 +00:00
|
|
|
TODO:
|
|
|
|
- none of these tests have been run successfully on windows yet but
|
|
|
|
there's been manual testing that verified it works.
|
|
|
|
- wonder if any of it'll work on OS X?
|
|
|
|
|
2020-10-03 23:35:18 +00:00
|
|
|
"""
|
2024-03-05 16:43:23 +00:00
|
|
|
from functools import partial
|
2022-10-14 21:42:54 +00:00
|
|
|
import itertools
|
2022-07-28 18:04:30 +00:00
|
|
|
import platform
|
|
|
|
import time
|
2020-10-03 23:35:18 +00:00
|
|
|
|
|
|
|
import pytest
|
2022-07-29 17:50:53 +00:00
|
|
|
from pexpect.exceptions import (
|
|
|
|
TIMEOUT,
|
|
|
|
EOF,
|
|
|
|
)
|
2020-10-03 23:35:18 +00:00
|
|
|
|
2024-12-09 20:38:28 +00:00
|
|
|
from .conftest import (
|
|
|
|
do_ctlc,
|
|
|
|
PROMPT,
|
2024-03-05 16:43:23 +00:00
|
|
|
_pause_msg,
|
|
|
|
_crash_msg,
|
2024-06-10 21:57:43 +00:00
|
|
|
_repl_fail_msg,
|
2024-03-05 16:43:23 +00:00
|
|
|
)
|
2024-07-10 22:17:42 +00:00
|
|
|
from .conftest import (
|
|
|
|
expect,
|
|
|
|
in_prompt_msg,
|
|
|
|
assert_before,
|
|
|
|
)
|
2020-10-03 23:35:18 +00:00
|
|
|
|
2020-10-14 13:06:40 +00:00
|
|
|
# TODO: The next great debugger audit could be done by you!
|
2020-10-04 21:31:02 +00:00
|
|
|
# - recurrent entry to breakpoint() from single actor *after* and an
|
2020-10-05 15:38:39 +00:00
|
|
|
# error in another task?
|
2020-10-04 21:31:02 +00:00
|
|
|
# - root error before child errors
|
|
|
|
# - root error after child errors
|
|
|
|
# - root error before child breakpoint
|
|
|
|
# - root error after child breakpoint
|
|
|
|
# - recurrent root errors
|
|
|
|
|
|
|
|
|
2021-07-06 11:56:38 +00:00
|
|
|
if platform.system() == 'Windows':
|
|
|
|
pytest.skip(
|
|
|
|
'Debugger tests have no windows support (yet)',
|
|
|
|
allow_module_level=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-08-01 16:29:06 +00:00
|
|
|
# TODO: was trying to this xfail style but some weird bug i see in CI
|
|
|
|
# that's happening at collect time.. pretty soon gonna dump actions i'm
|
|
|
|
# thinkin...
|
2022-08-01 16:00:25 +00:00
|
|
|
# in CI we skip tests which >= depth 1 actor trees due to there
|
|
|
|
# still being an oustanding issue with relaying the debug-mode-state
|
|
|
|
# through intermediary parents.
|
2022-08-02 00:00:05 +00:00
|
|
|
has_nested_actors = pytest.mark.has_nested_actors
|
|
|
|
# .xfail(
|
|
|
|
# os.environ.get('CI', False),
|
|
|
|
# reason=(
|
|
|
|
# 'This test uses nested actors and fails in CI\n'
|
|
|
|
# 'The test seems to run fine locally but until we solve the '
|
|
|
|
# 'following issue this CI test will be xfail:\n'
|
|
|
|
# 'https://github.com/goodboy/tractor/issues/320'
|
|
|
|
# )
|
2022-08-01 16:29:06 +00:00
|
|
|
# )
|
2022-08-01 16:00:25 +00:00
|
|
|
|
|
|
|
|
2020-10-03 23:35:18 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'user_in_out',
|
|
|
|
[
|
|
|
|
('c', 'AssertionError'),
|
|
|
|
('q', 'AssertionError'),
|
|
|
|
],
|
2020-10-04 21:31:02 +00:00
|
|
|
ids=lambda item: f'{item[0]} -> {item[1]}',
|
2020-10-03 23:35:18 +00:00
|
|
|
)
|
2024-05-22 19:01:31 +00:00
|
|
|
def test_root_actor_error(
|
|
|
|
spawn,
|
|
|
|
user_in_out,
|
|
|
|
):
|
2023-05-15 13:12:13 +00:00
|
|
|
'''
|
|
|
|
Demonstrate crash handler entering pdb from basic error in root actor.
|
|
|
|
|
|
|
|
'''
|
2020-10-03 23:35:18 +00:00
|
|
|
user_input, expect_err_str = user_in_out
|
|
|
|
|
2020-10-04 13:54:36 +00:00
|
|
|
child = spawn('root_actor_error')
|
2020-10-03 23:35:18 +00:00
|
|
|
|
2023-05-15 13:12:13 +00:00
|
|
|
# scan for the prompt
|
2022-08-01 19:08:15 +00:00
|
|
|
expect(child, PROMPT)
|
2020-10-03 23:35:18 +00:00
|
|
|
|
|
|
|
# make sure expected logging and error arrives
|
2024-03-05 16:43:23 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
|
|
|
[
|
|
|
|
_crash_msg,
|
|
|
|
"('root'",
|
|
|
|
'AssertionError',
|
|
|
|
]
|
2024-03-05 16:43:23 +00:00
|
|
|
)
|
2020-10-03 23:35:18 +00:00
|
|
|
|
|
|
|
# send user command
|
|
|
|
child.sendline(user_input)
|
|
|
|
|
|
|
|
# process should exit
|
2022-08-01 19:08:15 +00:00
|
|
|
expect(child, EOF)
|
2020-10-03 23:35:18 +00:00
|
|
|
assert expect_err_str in str(child.before)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'user_in_out',
|
|
|
|
[
|
|
|
|
('c', None),
|
|
|
|
('q', 'bdb.BdbQuit'),
|
|
|
|
],
|
|
|
|
ids=lambda item: f'{item[0]} -> {item[1]}',
|
|
|
|
)
|
2020-10-04 13:54:36 +00:00
|
|
|
def test_root_actor_bp(spawn, user_in_out):
|
2024-12-09 20:38:28 +00:00
|
|
|
'''
|
|
|
|
Demonstrate breakpoint from in root actor.
|
|
|
|
|
|
|
|
'''
|
2020-10-03 23:35:18 +00:00
|
|
|
user_input, expect_err_str = user_in_out
|
2020-10-04 13:54:36 +00:00
|
|
|
child = spawn('root_actor_breakpoint')
|
2020-10-03 23:35:18 +00:00
|
|
|
|
2023-05-15 13:12:13 +00:00
|
|
|
# scan for the prompt
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-03 23:35:18 +00:00
|
|
|
|
|
|
|
assert 'Error' not in str(child.before)
|
|
|
|
|
|
|
|
# send user command
|
|
|
|
child.sendline(user_input)
|
|
|
|
child.expect('\r\n')
|
|
|
|
|
|
|
|
# process should exit
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2020-10-03 23:35:18 +00:00
|
|
|
|
|
|
|
if expect_err_str is None:
|
|
|
|
assert 'Error' not in str(child.before)
|
|
|
|
else:
|
|
|
|
assert expect_err_str in str(child.before)
|
|
|
|
|
|
|
|
|
2022-06-27 20:22:45 +00:00
|
|
|
def test_root_actor_bp_forever(
|
|
|
|
spawn,
|
|
|
|
ctlc: bool,
|
|
|
|
):
|
2020-10-04 23:39:00 +00:00
|
|
|
"Re-enter a breakpoint from the root actor-task."
|
|
|
|
child = spawn('root_actor_breakpoint_forever')
|
|
|
|
|
|
|
|
# do some "next" commands to demonstrate recurrent breakpoint
|
|
|
|
# entries
|
|
|
|
for _ in range(10):
|
2022-06-27 20:22:45 +00:00
|
|
|
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 23:39:00 +00:00
|
|
|
|
2022-06-27 20:22:45 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
|
|
|
child.sendline('next')
|
|
|
|
|
|
|
|
# do one continue which should trigger a
|
|
|
|
# new task to lock the tty
|
2020-10-04 23:39:00 +00:00
|
|
|
child.sendline('continue')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 23:39:00 +00:00
|
|
|
|
2022-07-11 00:46:16 +00:00
|
|
|
# seems that if we hit ctrl-c too fast the
|
|
|
|
# sigint guard machinery might not kick in..
|
|
|
|
time.sleep(0.001)
|
|
|
|
|
2022-06-27 20:22:45 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-04 23:39:00 +00:00
|
|
|
# XXX: this previously caused a bug!
|
|
|
|
child.sendline('n')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 23:39:00 +00:00
|
|
|
|
|
|
|
child.sendline('n')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 23:39:00 +00:00
|
|
|
|
2022-06-27 20:22:45 +00:00
|
|
|
# quit out of the loop
|
|
|
|
child.sendline('q')
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2022-06-27 20:22:45 +00:00
|
|
|
|
2020-10-04 23:39:00 +00:00
|
|
|
|
2022-07-28 18:04:30 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'do_next',
|
|
|
|
(True, False),
|
|
|
|
ids='do_next={}'.format,
|
|
|
|
)
|
2022-06-27 20:22:45 +00:00
|
|
|
def test_subactor_error(
|
|
|
|
spawn,
|
|
|
|
ctlc: bool,
|
2022-07-28 18:04:30 +00:00
|
|
|
do_next: bool,
|
2022-06-27 20:22:45 +00:00
|
|
|
):
|
2022-08-01 15:02:31 +00:00
|
|
|
'''
|
|
|
|
Single subactor raising an error
|
2020-10-04 21:31:02 +00:00
|
|
|
|
2022-08-01 15:02:31 +00:00
|
|
|
'''
|
2020-10-04 13:54:36 +00:00
|
|
|
child = spawn('subactor_error')
|
|
|
|
|
2023-05-15 13:12:13 +00:00
|
|
|
# scan for the prompt
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 13:54:36 +00:00
|
|
|
|
2024-03-05 16:43:23 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
|
|
|
[
|
|
|
|
_crash_msg,
|
|
|
|
"('name_error'",
|
|
|
|
]
|
2024-03-05 16:43:23 +00:00
|
|
|
)
|
2020-10-04 13:54:36 +00:00
|
|
|
|
2022-07-28 18:04:30 +00:00
|
|
|
if do_next:
|
|
|
|
child.sendline('n')
|
|
|
|
|
|
|
|
else:
|
|
|
|
# make sure ctl-c sends don't do anything but repeat output
|
|
|
|
if ctlc:
|
|
|
|
do_ctlc(
|
|
|
|
child,
|
|
|
|
)
|
|
|
|
|
|
|
|
# send user command and (in this case it's the same for 'continue'
|
|
|
|
# vs. 'quit') the debugger should enter a second time in the nursery
|
|
|
|
# creating actor
|
|
|
|
child.sendline('continue')
|
2020-10-04 13:54:36 +00:00
|
|
|
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2024-03-05 16:43:23 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
|
|
|
[
|
|
|
|
_crash_msg,
|
|
|
|
# root actor gets debugger engaged
|
|
|
|
"('root'",
|
|
|
|
# error is a remote error propagated from the subactor
|
|
|
|
"('name_error'",
|
|
|
|
]
|
2024-03-05 16:43:23 +00:00
|
|
|
)
|
2020-10-04 13:54:36 +00:00
|
|
|
|
2022-06-27 20:22:45 +00:00
|
|
|
# another round
|
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-04 13:54:36 +00:00
|
|
|
child.sendline('c')
|
|
|
|
child.expect('\r\n')
|
|
|
|
|
|
|
|
# process should exit
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2020-10-04 13:54:36 +00:00
|
|
|
|
|
|
|
|
2022-07-12 17:02:59 +00:00
|
|
|
def test_subactor_breakpoint(
|
|
|
|
spawn,
|
|
|
|
ctlc: bool,
|
|
|
|
):
|
2020-10-04 21:31:02 +00:00
|
|
|
"Single subactor with an infinite breakpoint loop"
|
|
|
|
|
2020-10-04 13:54:36 +00:00
|
|
|
child = spawn('subactor_breakpoint')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2024-03-05 16:43:23 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
|
|
|
[_pause_msg,
|
|
|
|
"('breakpoint_forever'",]
|
2024-03-05 16:43:23 +00:00
|
|
|
)
|
2020-10-03 23:35:18 +00:00
|
|
|
|
2020-10-04 13:54:36 +00:00
|
|
|
# do some "next" commands to demonstrate recurrent breakpoint
|
|
|
|
# entries
|
|
|
|
for _ in range(10):
|
|
|
|
child.sendline('next')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 13:54:36 +00:00
|
|
|
|
2022-07-12 17:02:59 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-04 13:54:36 +00:00
|
|
|
# now run some "continues" to show re-entries
|
|
|
|
for _ in range(5):
|
|
|
|
child.sendline('continue')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2024-03-05 16:43:23 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
2024-03-05 16:43:23 +00:00
|
|
|
[_pause_msg, "('breakpoint_forever'"]
|
|
|
|
)
|
2020-10-04 13:54:36 +00:00
|
|
|
|
2022-07-12 17:02:59 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-04 13:54:36 +00:00
|
|
|
# finally quit the loop
|
|
|
|
child.sendline('q')
|
|
|
|
|
|
|
|
# child process should exit but parent will capture pdb.BdbQuit
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 13:54:36 +00:00
|
|
|
|
2024-05-22 19:01:31 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
2024-05-22 19:01:31 +00:00
|
|
|
['RemoteActorError:',
|
|
|
|
"('breakpoint_forever'",
|
|
|
|
'bdb.BdbQuit',]
|
|
|
|
)
|
2020-10-04 13:54:36 +00:00
|
|
|
|
2022-07-12 17:02:59 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-04 13:54:36 +00:00
|
|
|
# quit the parent
|
|
|
|
child.sendline('c')
|
|
|
|
|
|
|
|
# process should exit
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2020-10-03 23:35:18 +00:00
|
|
|
|
2024-05-22 19:01:31 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
2024-05-22 19:01:31 +00:00
|
|
|
['RemoteActorError:',
|
|
|
|
"('breakpoint_forever'",
|
|
|
|
'bdb.BdbQuit',]
|
|
|
|
)
|
2020-10-04 21:31:02 +00:00
|
|
|
|
|
|
|
|
2022-08-01 16:00:25 +00:00
|
|
|
@has_nested_actors
|
2022-07-12 17:49:36 +00:00
|
|
|
def test_multi_subactors(
|
|
|
|
spawn,
|
|
|
|
ctlc: bool,
|
|
|
|
):
|
2022-07-29 23:34:54 +00:00
|
|
|
'''
|
|
|
|
Multiple subactors, both erroring and
|
|
|
|
breakpointing as well as a nested subactor erroring.
|
|
|
|
|
|
|
|
'''
|
2020-10-04 21:31:02 +00:00
|
|
|
child = spawn(r'multi_subactors')
|
|
|
|
|
2023-05-15 13:12:13 +00:00
|
|
|
# scan for the prompt
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 21:31:02 +00:00
|
|
|
|
|
|
|
before = str(child.before.decode())
|
2024-03-05 16:43:23 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
2024-03-05 16:43:23 +00:00
|
|
|
[_pause_msg, "('breakpoint_forever'"]
|
|
|
|
)
|
2020-10-04 21:31:02 +00:00
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-04 21:31:02 +00:00
|
|
|
# do some "next" commands to demonstrate recurrent breakpoint
|
|
|
|
# entries
|
|
|
|
for _ in range(10):
|
|
|
|
child.sendline('next')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 21:31:02 +00:00
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-04 21:31:02 +00:00
|
|
|
# continue to next error
|
|
|
|
child.sendline('c')
|
|
|
|
|
|
|
|
# first name_error failure
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2024-03-05 16:43:23 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
|
|
|
[
|
|
|
|
_crash_msg,
|
|
|
|
"('name_error'",
|
|
|
|
"NameError",
|
|
|
|
]
|
2024-03-05 16:43:23 +00:00
|
|
|
)
|
2020-10-04 21:31:02 +00:00
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-04 21:31:02 +00:00
|
|
|
# continue again
|
|
|
|
child.sendline('c')
|
|
|
|
|
|
|
|
# 2nd name_error failure
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2022-07-15 00:35:14 +00:00
|
|
|
|
2022-10-12 21:43:55 +00:00
|
|
|
# TODO: will we ever get the race where this crash will show up?
|
|
|
|
# blocklist strat now prevents this crash
|
|
|
|
# assert_before(child, [
|
|
|
|
# "Attaching to pdb in crashed actor: ('name_error_1'",
|
|
|
|
# "NameError",
|
|
|
|
# ])
|
2022-07-15 00:35:14 +00:00
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-04 21:31:02 +00:00
|
|
|
# breakpoint loop should re-engage
|
|
|
|
child.sendline('c')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2024-03-05 16:43:23 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
2024-03-05 16:43:23 +00:00
|
|
|
[_pause_msg, "('breakpoint_forever'"]
|
|
|
|
)
|
2020-10-04 21:31:02 +00:00
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2021-10-14 03:34:25 +00:00
|
|
|
# wait for spawn error to show up
|
2021-12-07 21:46:03 +00:00
|
|
|
spawn_err = "Attaching to pdb in crashed actor: ('spawn_error'"
|
2022-07-29 04:15:56 +00:00
|
|
|
start = time.time()
|
|
|
|
while (
|
|
|
|
spawn_err not in before
|
2022-08-01 15:02:31 +00:00
|
|
|
and (time.time() - start) < 3 # timeout eventually
|
2022-07-29 04:15:56 +00:00
|
|
|
):
|
2021-10-14 03:34:25 +00:00
|
|
|
child.sendline('c')
|
2021-12-07 21:46:03 +00:00
|
|
|
time.sleep(0.1)
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2021-10-14 03:34:25 +00:00
|
|
|
before = str(child.before.decode())
|
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2021-10-14 03:34:25 +00:00
|
|
|
# 2nd depth nursery should trigger
|
2022-08-01 16:00:25 +00:00
|
|
|
# (XXX: this below if guard is technically a hack that makes the
|
|
|
|
# nested case seem to work locally on linux but ideally in the long
|
|
|
|
# run this can be dropped.)
|
|
|
|
if not ctlc:
|
|
|
|
assert_before(child, [
|
|
|
|
spawn_err,
|
|
|
|
"RemoteActorError: ('name_error_1'",
|
|
|
|
])
|
2021-10-14 03:34:25 +00:00
|
|
|
|
2020-10-04 21:31:02 +00:00
|
|
|
# now run some "continues" to show re-entries
|
|
|
|
for _ in range(5):
|
|
|
|
child.sendline('c')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 21:31:02 +00:00
|
|
|
|
|
|
|
# quit the loop and expect parent to attach
|
|
|
|
child.sendline('q')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-04 21:31:02 +00:00
|
|
|
before = str(child.before.decode())
|
2022-07-12 17:49:36 +00:00
|
|
|
|
2024-03-05 16:43:23 +00:00
|
|
|
assert_before(
|
|
|
|
child, [
|
|
|
|
# debugger attaches to root
|
|
|
|
# "Attaching to pdb in crashed actor: ('root'",
|
|
|
|
_crash_msg,
|
|
|
|
"('root'",
|
|
|
|
|
|
|
|
# expect a multierror with exceptions for each sub-actor
|
|
|
|
"RemoteActorError: ('breakpoint_forever'",
|
|
|
|
"RemoteActorError: ('name_error'",
|
|
|
|
"RemoteActorError: ('spawn_error'",
|
|
|
|
"RemoteActorError: ('name_error_1'",
|
|
|
|
'bdb.BdbQuit',
|
|
|
|
]
|
|
|
|
)
|
2020-10-04 21:31:02 +00:00
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-04 21:31:02 +00:00
|
|
|
# process should exit
|
|
|
|
child.sendline('c')
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2022-07-29 23:34:54 +00:00
|
|
|
|
2021-10-14 03:34:25 +00:00
|
|
|
# repeat of previous multierror for final output
|
2022-07-29 23:34:54 +00:00
|
|
|
assert_before(child, [
|
|
|
|
"RemoteActorError: ('breakpoint_forever'",
|
|
|
|
"RemoteActorError: ('name_error'",
|
|
|
|
"RemoteActorError: ('spawn_error'",
|
|
|
|
"RemoteActorError: ('name_error_1'",
|
|
|
|
'bdb.BdbQuit',
|
|
|
|
])
|
2020-10-05 15:38:39 +00:00
|
|
|
|
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
def test_multi_daemon_subactors(
|
|
|
|
spawn,
|
|
|
|
loglevel: str,
|
|
|
|
ctlc: bool
|
|
|
|
):
|
|
|
|
'''
|
|
|
|
Multiple daemon subactors, both erroring and breakpointing within a
|
2020-10-16 03:15:20 +00:00
|
|
|
stream.
|
2022-07-12 17:49:36 +00:00
|
|
|
|
|
|
|
'''
|
2020-10-16 03:15:20 +00:00
|
|
|
child = spawn('multi_daemon_subactors')
|
|
|
|
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-16 03:15:20 +00:00
|
|
|
|
2022-10-14 19:40:48 +00:00
|
|
|
# there can be a race for which subactor will acquire
|
|
|
|
# the root's tty lock first so anticipate either crash
|
|
|
|
# message on the first entry.
|
2021-07-04 14:25:41 +00:00
|
|
|
|
2024-07-12 19:57:41 +00:00
|
|
|
bp_forev_parts = [
|
|
|
|
_pause_msg,
|
|
|
|
"('bp_forever'",
|
|
|
|
]
|
2024-03-05 16:43:23 +00:00
|
|
|
bp_forev_in_msg = partial(
|
|
|
|
in_prompt_msg,
|
|
|
|
parts=bp_forev_parts,
|
|
|
|
)
|
|
|
|
|
2024-07-12 19:57:41 +00:00
|
|
|
name_error_msg: str = "NameError: name 'doggypants' is not defined"
|
|
|
|
name_error_parts: list[str] = [name_error_msg]
|
2021-07-04 14:25:41 +00:00
|
|
|
|
2022-10-14 19:40:48 +00:00
|
|
|
before = str(child.before.decode())
|
2024-03-05 16:43:23 +00:00
|
|
|
|
2024-07-12 19:57:41 +00:00
|
|
|
if bp_forev_in_msg(child=child):
|
2024-03-05 16:43:23 +00:00
|
|
|
next_parts = name_error_parts
|
2021-07-04 14:25:41 +00:00
|
|
|
|
|
|
|
elif name_error_msg in before:
|
2024-03-05 16:43:23 +00:00
|
|
|
next_parts = bp_forev_parts
|
2021-07-04 14:25:41 +00:00
|
|
|
|
|
|
|
else:
|
2024-07-12 19:57:41 +00:00
|
|
|
raise ValueError('Neither log msg was found !?')
|
2020-10-16 03:15:20 +00:00
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2021-06-30 17:49:56 +00:00
|
|
|
# NOTE: previously since we did not have clobber prevention
|
|
|
|
# in the root actor this final resume could result in the debugger
|
|
|
|
# tearing down since both child actors would be cancelled and it was
|
2021-08-01 14:43:21 +00:00
|
|
|
# unlikely that `bp_forever` would re-acquire the tty lock again.
|
2021-06-30 17:49:56 +00:00
|
|
|
# Now, we should have a final resumption in the root plus a possible
|
|
|
|
# second entry by `bp_forever`.
|
2020-10-16 03:15:20 +00:00
|
|
|
|
2021-06-30 17:49:56 +00:00
|
|
|
child.sendline('c')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2024-03-05 16:43:23 +00:00
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
next_parts,
|
|
|
|
)
|
2020-10-16 03:15:20 +00:00
|
|
|
|
2021-08-01 14:43:21 +00:00
|
|
|
# XXX: hooray the root clobbering the child here was fixed!
|
2021-06-30 17:49:56 +00:00
|
|
|
# IMO, this demonstrates the true power of SC system design.
|
2020-10-16 03:15:20 +00:00
|
|
|
|
2021-06-30 17:49:56 +00:00
|
|
|
# now the root actor won't clobber the bp_forever child
|
|
|
|
# during it's first access to the debug lock, but will instead
|
|
|
|
# wait for the lock to release, by the edge triggered
|
2023-10-19 15:17:07 +00:00
|
|
|
# ``devx._debug.Lock.no_remote_has_tty`` event before sending cancel messages
|
2021-06-30 17:49:56 +00:00
|
|
|
# (via portals) to its underlings B)
|
|
|
|
|
|
|
|
# at some point here there should have been some warning msg from
|
|
|
|
# the root announcing it avoided a clobber of the child's lock, but
|
|
|
|
# it seems unreliable in testing here to gnab it:
|
|
|
|
# assert "in use by child ('bp_forever'," in before
|
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2022-10-14 19:40:48 +00:00
|
|
|
# expect another breakpoint actor entry
|
|
|
|
child.sendline('c')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2021-06-30 17:49:56 +00:00
|
|
|
|
2022-12-12 19:05:32 +00:00
|
|
|
try:
|
2024-03-05 16:43:23 +00:00
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
bp_forev_parts,
|
|
|
|
)
|
2022-12-12 19:05:32 +00:00
|
|
|
except AssertionError:
|
2024-03-05 16:43:23 +00:00
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
name_error_parts,
|
|
|
|
)
|
2022-12-12 19:05:32 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
2021-06-30 17:49:56 +00:00
|
|
|
|
2022-12-12 19:05:32 +00:00
|
|
|
# should crash with the 2nd name error (simulates
|
|
|
|
# a retry) and then the root eventually (boxed) errors
|
|
|
|
# after 1 or more further bp actor entries.
|
2021-06-30 17:49:56 +00:00
|
|
|
|
2022-12-12 19:05:32 +00:00
|
|
|
child.sendline('c')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2024-03-05 16:43:23 +00:00
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
name_error_parts,
|
|
|
|
)
|
2020-10-16 03:15:20 +00:00
|
|
|
|
2022-10-14 19:40:48 +00:00
|
|
|
# wait for final error in root
|
|
|
|
# where it crashs with boxed error
|
|
|
|
while True:
|
2024-07-12 19:57:41 +00:00
|
|
|
child.sendline('c')
|
|
|
|
child.expect(PROMPT)
|
|
|
|
if not in_prompt_msg(
|
|
|
|
child,
|
|
|
|
bp_forev_parts
|
|
|
|
):
|
2022-10-14 19:40:48 +00:00
|
|
|
break
|
2022-07-12 17:49:36 +00:00
|
|
|
|
2022-10-14 19:40:48 +00:00
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
[
|
|
|
|
# boxed error raised in root task
|
2024-03-05 16:43:23 +00:00
|
|
|
# "Attaching to pdb in crashed actor: ('root'",
|
|
|
|
_crash_msg,
|
2024-05-22 19:01:31 +00:00
|
|
|
"('root'", # should attach in root
|
|
|
|
"_exceptions.RemoteActorError:", # with an embedded RAE for..
|
|
|
|
"('name_error'", # the src subactor which raised
|
2022-10-14 19:40:48 +00:00
|
|
|
]
|
|
|
|
)
|
2021-07-04 16:55:36 +00:00
|
|
|
|
2022-10-14 19:40:48 +00:00
|
|
|
child.sendline('c')
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2020-12-18 23:38:22 +00:00
|
|
|
|
2020-10-16 03:15:20 +00:00
|
|
|
|
2022-08-01 16:00:25 +00:00
|
|
|
@has_nested_actors
|
2022-07-12 17:49:36 +00:00
|
|
|
def test_multi_subactors_root_errors(
|
|
|
|
spawn,
|
|
|
|
ctlc: bool
|
|
|
|
):
|
2021-12-10 16:54:27 +00:00
|
|
|
'''
|
|
|
|
Multiple subactors, both erroring and breakpointing as well as
|
2020-10-05 15:38:39 +00:00
|
|
|
a nested subactor erroring.
|
2021-12-10 16:54:27 +00:00
|
|
|
|
|
|
|
'''
|
2020-10-05 15:38:39 +00:00
|
|
|
child = spawn('multi_subactor_root_errors')
|
|
|
|
|
2023-05-15 13:12:13 +00:00
|
|
|
# scan for the prompt
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-05 15:38:39 +00:00
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2021-10-14 03:34:25 +00:00
|
|
|
# continue again to catch 2nd name error from
|
|
|
|
# actor 'name_error_1' (which is 2nd depth).
|
2020-10-05 15:38:39 +00:00
|
|
|
child.sendline('c')
|
2022-10-12 21:43:55 +00:00
|
|
|
|
|
|
|
# due to block list strat from #337, this will no longer
|
|
|
|
# propagate before the root errors and cancels the spawner sub-tree.
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-10-05 15:38:39 +00:00
|
|
|
|
2022-10-12 21:43:55 +00:00
|
|
|
# only if the blocking condition doesn't kick in fast enough
|
|
|
|
before = str(child.before.decode())
|
|
|
|
if "Debug lock blocked for ['name_error_1'" not in before:
|
2022-07-12 17:49:36 +00:00
|
|
|
|
2022-10-12 21:43:55 +00:00
|
|
|
assert_before(child, [
|
|
|
|
"Attaching to pdb in crashed actor: ('name_error_1'",
|
|
|
|
"NameError",
|
|
|
|
])
|
2020-12-26 20:11:42 +00:00
|
|
|
|
2022-10-12 21:43:55 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
2022-07-12 17:49:36 +00:00
|
|
|
|
2022-10-12 21:43:55 +00:00
|
|
|
child.sendline('c')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2022-10-12 21:43:55 +00:00
|
|
|
|
|
|
|
# check if the spawner crashed or was blocked from debug
|
|
|
|
# and if this intermediary attached check the boxed error
|
|
|
|
before = str(child.before.decode())
|
|
|
|
if "Attaching to pdb in crashed actor: ('spawn_error'" in before:
|
|
|
|
|
|
|
|
assert_before(child, [
|
|
|
|
# boxed error from spawner's child
|
|
|
|
"RemoteActorError: ('name_error_1'",
|
|
|
|
"NameError",
|
|
|
|
])
|
|
|
|
|
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
|
|
|
child.sendline('c')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2022-10-12 21:43:55 +00:00
|
|
|
|
|
|
|
# expect a root actor crash
|
2022-07-29 23:34:54 +00:00
|
|
|
assert_before(child, [
|
|
|
|
"RemoteActorError: ('name_error'",
|
|
|
|
"NameError",
|
2020-12-26 20:11:42 +00:00
|
|
|
|
2022-10-12 21:43:55 +00:00
|
|
|
# error from root actor and root task that created top level nursery
|
|
|
|
"Attaching to pdb in crashed actor: ('root'",
|
|
|
|
"AssertionError",
|
|
|
|
])
|
2022-07-12 17:49:36 +00:00
|
|
|
|
2020-10-05 15:38:39 +00:00
|
|
|
child.sendline('c')
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2020-10-05 15:38:39 +00:00
|
|
|
|
2022-10-12 21:43:55 +00:00
|
|
|
assert_before(child, [
|
|
|
|
# "Attaching to pdb in crashed actor: ('root'",
|
|
|
|
# boxed error from previous step
|
|
|
|
"RemoteActorError: ('name_error'",
|
|
|
|
"NameError",
|
|
|
|
"AssertionError",
|
|
|
|
'assert 0',
|
|
|
|
])
|
2020-10-06 13:21:53 +00:00
|
|
|
|
|
|
|
|
2022-08-01 16:00:25 +00:00
|
|
|
@has_nested_actors
|
2022-07-12 17:49:36 +00:00
|
|
|
def test_multi_nested_subactors_error_through_nurseries(
|
|
|
|
spawn,
|
|
|
|
|
|
|
|
# TODO: address debugger issue for nested tree:
|
2022-08-02 00:00:05 +00:00
|
|
|
# https://github.com/goodboy/tractor/issues/320
|
2022-07-12 17:49:36 +00:00
|
|
|
# ctlc: bool,
|
|
|
|
):
|
2024-05-22 19:01:31 +00:00
|
|
|
'''
|
|
|
|
Verify deeply nested actors that error trigger debugger entries
|
2020-10-14 13:06:40 +00:00
|
|
|
at each actor nurserly (level) all the way up the tree.
|
2020-10-06 13:21:53 +00:00
|
|
|
|
2024-05-22 19:01:31 +00:00
|
|
|
'''
|
2021-08-01 14:43:21 +00:00
|
|
|
# NOTE: previously, inside this script was a bug where if the
|
2020-10-14 13:06:40 +00:00
|
|
|
# parent errors before a 2-levels-lower actor has released the lock,
|
|
|
|
# the parent tries to cancel it but it's stuck in the debugger?
|
|
|
|
# A test (below) has now been added to explicitly verify this is
|
|
|
|
# fixed.
|
2020-10-06 13:21:53 +00:00
|
|
|
|
|
|
|
child = spawn('multi_nested_subactors_error_up_through_nurseries')
|
|
|
|
|
2024-03-05 16:43:23 +00:00
|
|
|
# timed_out_early: bool = False
|
2021-08-01 17:10:51 +00:00
|
|
|
|
2022-10-14 21:42:54 +00:00
|
|
|
for send_char in itertools.cycle(['c', 'q']):
|
2020-12-18 23:38:22 +00:00
|
|
|
try:
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2022-10-14 21:42:54 +00:00
|
|
|
child.sendline(send_char)
|
|
|
|
time.sleep(0.01)
|
2020-12-18 23:38:22 +00:00
|
|
|
|
2022-07-29 17:50:53 +00:00
|
|
|
except EOF:
|
2020-12-18 23:38:22 +00:00
|
|
|
break
|
2020-10-06 13:21:53 +00:00
|
|
|
|
2024-05-22 19:01:31 +00:00
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
[ # boxed source errors
|
|
|
|
"NameError: name 'doggypants' is not defined",
|
|
|
|
"tractor._exceptions.RemoteActorError:",
|
|
|
|
"('name_error'",
|
|
|
|
"bdb.BdbQuit",
|
|
|
|
|
|
|
|
# first level subtrees
|
|
|
|
# "tractor._exceptions.RemoteActorError: ('spawner0'",
|
|
|
|
"src_uid=('spawner0'",
|
|
|
|
|
|
|
|
# "tractor._exceptions.RemoteActorError: ('spawner1'",
|
|
|
|
|
|
|
|
# propagation of errors up through nested subtrees
|
|
|
|
# "tractor._exceptions.RemoteActorError: ('spawn_until_0'",
|
|
|
|
# "tractor._exceptions.RemoteActorError: ('spawn_until_1'",
|
|
|
|
# "tractor._exceptions.RemoteActorError: ('spawn_until_2'",
|
|
|
|
# ^-NOTE-^ old RAE repr, new one is below with a field
|
|
|
|
# showing the src actor's uid.
|
|
|
|
"src_uid=('spawn_until_0'",
|
|
|
|
"relay_uid=('spawn_until_1'",
|
|
|
|
"src_uid=('spawn_until_2'",
|
|
|
|
]
|
|
|
|
)
|
2020-10-07 02:39:20 +00:00
|
|
|
|
|
|
|
|
2022-08-01 19:08:15 +00:00
|
|
|
@pytest.mark.timeout(15)
|
2022-08-01 20:57:42 +00:00
|
|
|
@has_nested_actors
|
2021-02-24 18:07:36 +00:00
|
|
|
def test_root_nursery_cancels_before_child_releases_tty_lock(
|
|
|
|
spawn,
|
2022-07-12 17:49:36 +00:00
|
|
|
start_method,
|
|
|
|
ctlc: bool,
|
2021-02-24 18:07:36 +00:00
|
|
|
):
|
2022-08-01 20:57:42 +00:00
|
|
|
'''
|
|
|
|
Test that when the root sends a cancel message before a nested child
|
|
|
|
has unblocked (which can happen when it has the tty lock and is
|
|
|
|
engaged in pdb) it is indeed cancelled after exiting the debugger.
|
|
|
|
|
|
|
|
'''
|
2020-12-17 18:35:45 +00:00
|
|
|
timed_out_early = False
|
|
|
|
|
2020-10-07 02:39:20 +00:00
|
|
|
child = spawn('root_cancelled_but_child_is_in_tty_lock')
|
|
|
|
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2024-07-12 19:57:41 +00:00
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
[
|
|
|
|
"NameError: name 'doggypants' is not defined",
|
|
|
|
"tractor._exceptions.RemoteActorError: ('name_error'",
|
|
|
|
],
|
|
|
|
)
|
2020-12-17 18:35:45 +00:00
|
|
|
time.sleep(0.5)
|
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-13 04:36:34 +00:00
|
|
|
child.sendline('c')
|
|
|
|
|
2020-12-17 18:35:45 +00:00
|
|
|
for i in range(4):
|
|
|
|
time.sleep(0.5)
|
2020-12-12 18:29:22 +00:00
|
|
|
try:
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2020-12-18 22:57:44 +00:00
|
|
|
|
|
|
|
except (
|
2022-07-29 17:50:53 +00:00
|
|
|
EOF,
|
|
|
|
TIMEOUT,
|
2020-12-18 22:57:44 +00:00
|
|
|
):
|
|
|
|
# races all over..
|
|
|
|
|
2020-12-17 18:35:45 +00:00
|
|
|
print(f"Failed early on {i}?")
|
|
|
|
before = str(child.before.decode())
|
|
|
|
|
|
|
|
timed_out_early = True
|
|
|
|
|
|
|
|
# race conditions on how fast the continue is sent?
|
|
|
|
break
|
|
|
|
|
2020-10-13 04:36:34 +00:00
|
|
|
before = str(child.before.decode())
|
|
|
|
assert "NameError: name 'doggypants' is not defined" in before
|
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2020-10-07 02:39:20 +00:00
|
|
|
child.sendline('c')
|
2022-07-29 15:11:54 +00:00
|
|
|
time.sleep(0.1)
|
2020-10-07 02:39:20 +00:00
|
|
|
|
2022-07-29 21:51:33 +00:00
|
|
|
for i in range(3):
|
2021-07-31 19:01:26 +00:00
|
|
|
try:
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF, timeout=0.5)
|
2021-07-31 19:01:26 +00:00
|
|
|
break
|
2022-07-29 17:50:53 +00:00
|
|
|
except TIMEOUT:
|
2021-08-01 17:10:51 +00:00
|
|
|
child.sendline('c')
|
2022-07-29 15:11:54 +00:00
|
|
|
time.sleep(0.1)
|
2021-08-01 14:43:21 +00:00
|
|
|
print('child was able to grab tty lock again?')
|
2022-07-29 16:20:56 +00:00
|
|
|
else:
|
2022-08-01 18:28:04 +00:00
|
|
|
print('giving up on child releasing, sending `quit` cmd')
|
2022-07-29 16:20:56 +00:00
|
|
|
child.sendline('q')
|
2022-08-01 19:08:15 +00:00
|
|
|
expect(child, EOF)
|
2020-12-17 18:35:45 +00:00
|
|
|
|
|
|
|
if not timed_out_early:
|
|
|
|
before = str(child.before.decode())
|
2024-03-05 16:43:23 +00:00
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
[
|
|
|
|
"tractor._exceptions.RemoteActorError: ('spawner0'",
|
|
|
|
"tractor._exceptions.RemoteActorError: ('name_error'",
|
|
|
|
"NameError: name 'doggypants' is not defined",
|
|
|
|
],
|
|
|
|
)
|
2021-06-30 20:48:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_root_cancels_child_context_during_startup(
|
|
|
|
spawn,
|
2022-07-12 17:49:36 +00:00
|
|
|
ctlc: bool,
|
2021-06-30 20:48:32 +00:00
|
|
|
):
|
|
|
|
'''Verify a fast fail in the root doesn't lock up the child reaping
|
|
|
|
and all while using the new context api.
|
|
|
|
|
|
|
|
'''
|
|
|
|
child = spawn('fast_error_in_root_after_spawn')
|
|
|
|
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2021-06-30 20:48:32 +00:00
|
|
|
|
|
|
|
before = str(child.before.decode())
|
|
|
|
assert "AssertionError" in before
|
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2021-06-30 20:48:32 +00:00
|
|
|
child.sendline('c')
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2021-12-09 22:51:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_different_debug_mode_per_actor(
|
|
|
|
spawn,
|
2022-07-12 17:49:36 +00:00
|
|
|
ctlc: bool,
|
2021-12-09 22:51:53 +00:00
|
|
|
):
|
|
|
|
child = spawn('per_actor_debug')
|
2023-04-15 23:43:58 +00:00
|
|
|
child.expect(PROMPT)
|
2021-12-09 22:51:53 +00:00
|
|
|
|
|
|
|
# only one actor should enter the debugger
|
2024-03-05 16:43:23 +00:00
|
|
|
assert in_prompt_msg(
|
2024-07-12 19:57:41 +00:00
|
|
|
child,
|
2024-03-05 16:43:23 +00:00
|
|
|
[_crash_msg, "('debugged_boi'", "RuntimeError"],
|
|
|
|
)
|
2021-12-09 22:51:53 +00:00
|
|
|
|
2022-07-12 17:49:36 +00:00
|
|
|
if ctlc:
|
|
|
|
do_ctlc(child)
|
|
|
|
|
2021-12-09 22:51:53 +00:00
|
|
|
child.sendline('c')
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2021-12-09 22:51:53 +00:00
|
|
|
|
|
|
|
# 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.
|
|
|
|
# assert "tractor._exceptions.RemoteActorError: ('debugged_boi'" in before
|
|
|
|
|
2021-12-10 16:54:27 +00:00
|
|
|
# the crash boi should not have made a debugger request but
|
|
|
|
# instead crashed completely
|
2024-05-22 19:01:31 +00:00
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
[
|
|
|
|
"tractor._exceptions.RemoteActorError:",
|
|
|
|
"src_uid=('crash_boi'",
|
|
|
|
"RuntimeError",
|
|
|
|
]
|
|
|
|
)
|
2024-03-22 20:41:49 +00:00
|
|
|
|
|
|
|
|
2024-05-30 20:03:28 +00:00
|
|
|
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')
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2024-05-30 20:03:28 +00:00
|
|
|
|
|
|
|
|
2024-05-30 21:52:24 +00:00
|
|
|
def test_shield_pause(
|
|
|
|
spawn,
|
|
|
|
):
|
|
|
|
'''
|
|
|
|
Verify the `tractor.pause()/.post_mortem()` API works inside an
|
|
|
|
already cancelled `trio.CancelScope` and that you can step to the
|
|
|
|
next checkpoint wherein the cancelled will get raised.
|
|
|
|
|
|
|
|
'''
|
|
|
|
child = spawn('shielded_pause')
|
|
|
|
|
|
|
|
# First entry is via manual `.post_mortem()`
|
|
|
|
child.expect(PROMPT)
|
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
[
|
|
|
|
_pause_msg,
|
|
|
|
"cancellable_pause_loop'",
|
|
|
|
"('cancelled_before_pause'", # actor name
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
# since 3 tries in ex. shield pause loop
|
|
|
|
for i in range(3):
|
|
|
|
child.sendline('c')
|
|
|
|
child.expect(PROMPT)
|
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
[
|
|
|
|
_pause_msg,
|
|
|
|
"INSIDE SHIELDED PAUSE",
|
|
|
|
"('cancelled_before_pause'", # actor name
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
# back inside parent task that opened nursery
|
|
|
|
child.sendline('c')
|
|
|
|
child.expect(PROMPT)
|
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
[
|
|
|
|
_crash_msg,
|
|
|
|
"('cancelled_before_pause'", # actor name
|
2024-06-10 21:57:43 +00:00
|
|
|
_repl_fail_msg,
|
2024-05-30 21:52:24 +00:00
|
|
|
"trio.Cancelled",
|
|
|
|
"raise Cancelled._create()",
|
|
|
|
|
|
|
|
# we should be handling a taskc inside
|
|
|
|
# the first `.port_mortem()` sin-shield!
|
|
|
|
'await DebugStatus.req_finished.wait()',
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
# same as above but in the root actor's task
|
|
|
|
child.sendline('c')
|
|
|
|
child.expect(PROMPT)
|
|
|
|
assert_before(
|
|
|
|
child,
|
|
|
|
[
|
|
|
|
_crash_msg,
|
|
|
|
"('root'", # actor name
|
2024-06-10 21:57:43 +00:00
|
|
|
_repl_fail_msg,
|
2024-05-30 21:52:24 +00:00
|
|
|
"trio.Cancelled",
|
|
|
|
"raise Cancelled._create()",
|
|
|
|
|
|
|
|
# handling a taskc inside the first unshielded
|
|
|
|
# `.port_mortem()`.
|
|
|
|
# BUT in this case in the root-proc path ;)
|
|
|
|
'wait Lock._debug_lock.acquire()',
|
|
|
|
]
|
|
|
|
)
|
|
|
|
child.sendline('c')
|
2024-07-10 22:17:42 +00:00
|
|
|
child.expect(EOF)
|
2024-05-30 21:52:24 +00:00
|
|
|
|
|
|
|
|
2024-06-28 23:26:35 +00:00
|
|
|
# TODO: better error for "non-ideal" usage from the root actor.
|
|
|
|
# -[ ] if called from an async scope emit a message that suggests
|
|
|
|
# using `await tractor.pause()` instead since it's less overhead
|
|
|
|
# (in terms of `greenback` and/or extra threads) and if it's from
|
|
|
|
# a sync scope suggest that usage must first call
|
|
|
|
# `ensure_portal()` in the (eventual parent) async calling scope?
|
|
|
|
def test_sync_pause_from_bg_task_in_root_actor_():
|
|
|
|
'''
|
|
|
|
When used from the root actor, normally we can only implicitly
|
|
|
|
support `.pause_from_sync()` from the main-parent-task (that
|
|
|
|
opens the runtime via `open_root_actor()`) since `greenback`
|
|
|
|
requires a `.ensure_portal()` call per `trio.Task` where it is
|
|
|
|
used.
|
|
|
|
|
|
|
|
'''
|
|
|
|
...
|
|
|
|
|
2024-05-30 20:03:28 +00:00
|
|
|
# TODO: needs ANSI code stripping tho, see `assert_before()` # above!
|
2024-05-22 19:01:31 +00:00
|
|
|
def test_correct_frames_below_hidden():
|
|
|
|
'''
|
|
|
|
Ensure that once a `tractor.pause()` enages, when the user
|
|
|
|
inputs a "next"/"n" command the actual next line steps
|
|
|
|
and that using a "step"/"s" into the next LOC, particuarly
|
|
|
|
`tractor` APIs, you can step down into that code.
|
|
|
|
|
|
|
|
'''
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
def test_cant_pause_from_paused_task():
|
2024-05-30 20:03:28 +00:00
|
|
|
'''
|
|
|
|
Pausing from with an already paused task should raise an error.
|
|
|
|
|
2024-12-09 20:38:28 +00:00
|
|
|
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.
|
2024-05-30 20:03:28 +00:00
|
|
|
|
|
|
|
'''
|
|
|
|
...
|