Drop `spawn_test_support` pkg, inline parent-main tests
Replace the subproc-based test harness with inline `tractor.open_nursery()` calls that directly check `actor._parent_main_data` instead of comparing `__main__.__name__` across a process boundary (which is a no-op under pytest bc the parent `__main__` is `pytest.__main__`). Deats, - delete `tests/spawn_test_support/` pkg (3 files) - add `check_parent_main_inheritance()` helper fn that asserts on `_parent_main_data` emptiness - rewrite both `run_in_actor` and `start_actor` parent-main tests as inline async fns - drop `tmp_path` fixture and unused imports Review: PR #434 (goodboy, Copilot) https://github.com/goodboy/tractor/pull/434 (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-codesubint_spawner_backend
parent
b883b27646
commit
c6c591e61a
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import trio
|
|
||||||
import tractor
|
|
||||||
|
|
||||||
from spawn_test_support.parent_main_inheritance_support import get_main_mod_name
|
|
||||||
|
|
||||||
|
|
||||||
async def main(api: str, output_path: str) -> None:
|
|
||||||
async with tractor.open_nursery(start_method='trio') as an:
|
|
||||||
if api == 'run_in_actor':
|
|
||||||
replaying = await an.run_in_actor(
|
|
||||||
get_main_mod_name,
|
|
||||||
name='replaying-parent-main',
|
|
||||||
)
|
|
||||||
isolated = await an.run_in_actor(
|
|
||||||
get_main_mod_name,
|
|
||||||
name='isolated-parent-main',
|
|
||||||
inherit_parent_main=False,
|
|
||||||
)
|
|
||||||
replaying_name = await replaying.result()
|
|
||||||
isolated_name = await isolated.result()
|
|
||||||
elif api == 'start_actor':
|
|
||||||
replaying = await an.start_actor(
|
|
||||||
'replaying-parent-main',
|
|
||||||
enable_modules=[
|
|
||||||
'spawn_test_support.parent_main_inheritance_support',
|
|
||||||
],
|
|
||||||
)
|
|
||||||
isolated = await an.start_actor(
|
|
||||||
'isolated-parent-main',
|
|
||||||
enable_modules=[
|
|
||||||
'spawn_test_support.parent_main_inheritance_support',
|
|
||||||
],
|
|
||||||
inherit_parent_main=False,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
replaying_name = await replaying.run_from_ns(
|
|
||||||
'spawn_test_support.parent_main_inheritance_support',
|
|
||||||
'get_main_mod_name',
|
|
||||||
)
|
|
||||||
isolated_name = await isolated.run_from_ns(
|
|
||||||
'spawn_test_support.parent_main_inheritance_support',
|
|
||||||
'get_main_mod_name',
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
await replaying.cancel_actor()
|
|
||||||
await isolated.cancel_actor()
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Unknown api: {api}')
|
|
||||||
|
|
||||||
Path(output_path).write_text(
|
|
||||||
json.dumps({
|
|
||||||
'replaying': replaying_name,
|
|
||||||
'isolated': isolated_name,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
trio.run(main, sys.argv[1], sys.argv[2])
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
async def get_main_mod_name() -> str:
|
|
||||||
return sys.modules['__main__'].__name__
|
|
||||||
|
|
@ -3,11 +3,6 @@ Spawning basics
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
)
|
)
|
||||||
|
|
@ -22,11 +17,6 @@ data_to_pass_down = {
|
||||||
'doggy': 10,
|
'doggy': 10,
|
||||||
'kitty': 4,
|
'kitty': 4,
|
||||||
}
|
}
|
||||||
_tests_dir = Path(__file__).resolve().parent
|
|
||||||
_repo_root = _tests_dir.parent
|
|
||||||
_parent_main_case_script = (
|
|
||||||
_tests_dir / 'spawn_test_support' / 'parent_main_inheritance_case.py'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def spawn(
|
async def spawn(
|
||||||
|
|
@ -178,41 +168,41 @@ async def check_loglevel(level):
|
||||||
log.critical('yoyoyo')
|
log.critical('yoyoyo')
|
||||||
|
|
||||||
|
|
||||||
async def get_main_mod_name() -> str:
|
async def check_parent_main_inheritance(
|
||||||
import sys
|
expect_inherited: bool,
|
||||||
return sys.modules['__main__'].__name__
|
) -> bool:
|
||||||
|
'''
|
||||||
|
Assert that the child actor's ``_parent_main_data`` matches the
|
||||||
|
``inherit_parent_main`` flag it was spawned with.
|
||||||
|
|
||||||
|
With the trio spawn backend the parent's ``__main__`` bootstrap
|
||||||
|
data is captured and forwarded to each child so it can replay
|
||||||
|
the parent's ``__main__`` as ``__mp_main__``, mirroring the
|
||||||
|
stdlib ``multiprocessing`` bootstrap:
|
||||||
|
https://docs.python.org/3/library/multiprocessing.html#the-spawn-and-forkserver-start-methods
|
||||||
|
|
||||||
def _run_parent_main_inheritance_script(
|
When ``inherit_parent_main=False`` the data dict is empty
|
||||||
tmp_path: Path,
|
(``{}``) so no fixup ever runs and the child keeps its own
|
||||||
*,
|
``__main__`` untouched.
|
||||||
api: str,
|
|
||||||
) -> dict[str, str]:
|
|
||||||
output_file = tmp_path / 'out.json'
|
|
||||||
|
|
||||||
env = os.environ.copy()
|
NOTE: under pytest the parent ``__main__`` is
|
||||||
old_pythonpath = env.get('PYTHONPATH')
|
``pytest.__main__`` whose ``_fixup_main_from_name()`` is a
|
||||||
env['PYTHONPATH'] = (
|
no-op (the name ends with ``.__main__``), so we cannot observe
|
||||||
f'{_tests_dir}{os.pathsep}{_repo_root}{os.pathsep}{old_pythonpath}'
|
a difference in ``sys.modules['__main__'].__name__`` between
|
||||||
if old_pythonpath
|
the two modes. Checking ``_parent_main_data`` directly is the
|
||||||
else f'{_tests_dir}{os.pathsep}{_repo_root}'
|
most reliable verification that the flag is threaded through
|
||||||
|
correctly; a ``RemoteActorError[AssertionError]`` propagates
|
||||||
|
on mismatch.
|
||||||
|
'''
|
||||||
|
import tractor
|
||||||
|
actor = tractor.current_actor()
|
||||||
|
has_data: bool = bool(actor._parent_main_data)
|
||||||
|
assert has_data == expect_inherited, (
|
||||||
|
f'Expected _parent_main_data to be '
|
||||||
|
f'{"non-empty" if expect_inherited else "empty"}, '
|
||||||
|
f'got: {actor._parent_main_data!r}'
|
||||||
)
|
)
|
||||||
proc = subprocess.run(
|
return has_data
|
||||||
[
|
|
||||||
sys.executable,
|
|
||||||
str(_parent_main_case_script),
|
|
||||||
api,
|
|
||||||
str(output_file),
|
|
||||||
],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
text=True,
|
|
||||||
env=env,
|
|
||||||
)
|
|
||||||
assert proc.returncode == 0, (
|
|
||||||
f'stdout:\n{proc.stdout}\n\nstderr:\n{proc.stderr}'
|
|
||||||
)
|
|
||||||
return json.loads(output_file.read_text())
|
|
||||||
|
|
||||||
|
|
||||||
def test_loglevel_propagated_to_subactor(
|
def test_loglevel_propagated_to_subactor(
|
||||||
|
|
@ -248,33 +238,79 @@ def test_loglevel_propagated_to_subactor(
|
||||||
|
|
||||||
def test_run_in_actor_can_skip_parent_main_inheritance(
|
def test_run_in_actor_can_skip_parent_main_inheritance(
|
||||||
start_method,
|
start_method,
|
||||||
tmp_path,
|
|
||||||
):
|
):
|
||||||
|
'''
|
||||||
|
Verify ``inherit_parent_main=False`` on ``run_in_actor()``
|
||||||
|
prevents parent ``__main__`` data from reaching the child.
|
||||||
|
|
||||||
|
'''
|
||||||
if start_method != 'trio':
|
if start_method != 'trio':
|
||||||
pytest.skip(
|
pytest.skip(
|
||||||
'parent main inheritance opt-out only affects the trio spawn backend'
|
'parent main inheritance opt-out only affects the trio spawn backend'
|
||||||
)
|
)
|
||||||
assert _run_parent_main_inheritance_script(
|
|
||||||
tmp_path,
|
async def main():
|
||||||
api='run_in_actor',
|
async with tractor.open_nursery(start_method='trio') as an:
|
||||||
) == {
|
|
||||||
'replaying': '__mp_main__',
|
# Default: child receives parent __main__ bootstrap data
|
||||||
'isolated': '__main__',
|
replaying = await an.run_in_actor(
|
||||||
}
|
check_parent_main_inheritance,
|
||||||
|
name='replaying-parent-main',
|
||||||
|
expect_inherited=True,
|
||||||
|
)
|
||||||
|
await replaying.result()
|
||||||
|
|
||||||
|
# Opt-out: child gets no parent __main__ data
|
||||||
|
isolated = await an.run_in_actor(
|
||||||
|
check_parent_main_inheritance,
|
||||||
|
name='isolated-parent-main',
|
||||||
|
inherit_parent_main=False,
|
||||||
|
expect_inherited=False,
|
||||||
|
)
|
||||||
|
await isolated.result()
|
||||||
|
|
||||||
|
trio.run(main)
|
||||||
|
|
||||||
|
|
||||||
def test_start_actor_can_skip_parent_main_inheritance(
|
def test_start_actor_can_skip_parent_main_inheritance(
|
||||||
start_method,
|
start_method,
|
||||||
tmp_path,
|
|
||||||
):
|
):
|
||||||
|
'''
|
||||||
|
Verify ``inherit_parent_main=False`` on ``start_actor()``
|
||||||
|
prevents parent ``__main__`` data from reaching the child.
|
||||||
|
|
||||||
|
'''
|
||||||
if start_method != 'trio':
|
if start_method != 'trio':
|
||||||
pytest.skip(
|
pytest.skip(
|
||||||
'parent main inheritance opt-out only affects the trio spawn backend'
|
'parent main inheritance opt-out only affects the trio spawn backend'
|
||||||
)
|
)
|
||||||
assert _run_parent_main_inheritance_script(
|
|
||||||
tmp_path,
|
async def main():
|
||||||
api='start_actor',
|
async with tractor.open_nursery(start_method='trio') as an:
|
||||||
) == {
|
|
||||||
'replaying': '__mp_main__',
|
# Default: child receives parent __main__ bootstrap data
|
||||||
'isolated': '__main__',
|
replaying = await an.start_actor(
|
||||||
}
|
'replaying-parent-main',
|
||||||
|
enable_modules=[__name__],
|
||||||
|
)
|
||||||
|
result = await replaying.run(
|
||||||
|
check_parent_main_inheritance,
|
||||||
|
expect_inherited=True,
|
||||||
|
)
|
||||||
|
assert result is True
|
||||||
|
await replaying.cancel_actor()
|
||||||
|
|
||||||
|
# Opt-out: child gets no parent __main__ data
|
||||||
|
isolated = await an.start_actor(
|
||||||
|
'isolated-parent-main',
|
||||||
|
enable_modules=[__name__],
|
||||||
|
inherit_parent_main=False,
|
||||||
|
)
|
||||||
|
result = await isolated.run(
|
||||||
|
check_parent_main_inheritance,
|
||||||
|
expect_inherited=False,
|
||||||
|
)
|
||||||
|
assert result is False
|
||||||
|
await isolated.cancel_actor()
|
||||||
|
|
||||||
|
trio.run(main)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue