From c6c591e61a18e36556bc2c088ea9a785722484db Mon Sep 17 00:00:00 2001 From: goodboy Date: Thu, 9 Apr 2026 19:14:09 -0400 Subject: [PATCH] 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-code --- tests/spawn_test_support/__init__.py | 1 - .../parent_main_inheritance_case.py | 63 -------- .../parent_main_inheritance_support.py | 5 - tests/test_spawning.py | 150 +++++++++++------- 4 files changed, 93 insertions(+), 126 deletions(-) delete mode 100644 tests/spawn_test_support/__init__.py delete mode 100644 tests/spawn_test_support/parent_main_inheritance_case.py delete mode 100644 tests/spawn_test_support/parent_main_inheritance_support.py diff --git a/tests/spawn_test_support/__init__.py b/tests/spawn_test_support/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/tests/spawn_test_support/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/spawn_test_support/parent_main_inheritance_case.py b/tests/spawn_test_support/parent_main_inheritance_case.py deleted file mode 100644 index fd9ccc1e..00000000 --- a/tests/spawn_test_support/parent_main_inheritance_case.py +++ /dev/null @@ -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]) diff --git a/tests/spawn_test_support/parent_main_inheritance_support.py b/tests/spawn_test_support/parent_main_inheritance_support.py deleted file mode 100644 index bffe7766..00000000 --- a/tests/spawn_test_support/parent_main_inheritance_support.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys - - -async def get_main_mod_name() -> str: - return sys.modules['__main__'].__name__ diff --git a/tests/test_spawning.py b/tests/test_spawning.py index 77732050..3f053e75 100644 --- a/tests/test_spawning.py +++ b/tests/test_spawning.py @@ -3,11 +3,6 @@ Spawning basics """ from functools import partial -import json -import os -from pathlib import Path -import subprocess -import sys from typing import ( Any, ) @@ -22,11 +17,6 @@ data_to_pass_down = { 'doggy': 10, '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( @@ -178,41 +168,41 @@ async def check_loglevel(level): log.critical('yoyoyo') -async def get_main_mod_name() -> str: - import sys - return sys.modules['__main__'].__name__ +async def check_parent_main_inheritance( + expect_inherited: bool, +) -> 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( - tmp_path: Path, - *, - api: str, -) -> dict[str, str]: - output_file = tmp_path / 'out.json' + When ``inherit_parent_main=False`` the data dict is empty + (``{}``) so no fixup ever runs and the child keeps its own + ``__main__`` untouched. - env = os.environ.copy() - old_pythonpath = env.get('PYTHONPATH') - env['PYTHONPATH'] = ( - f'{_tests_dir}{os.pathsep}{_repo_root}{os.pathsep}{old_pythonpath}' - if old_pythonpath - else f'{_tests_dir}{os.pathsep}{_repo_root}' + NOTE: under pytest the parent ``__main__`` is + ``pytest.__main__`` whose ``_fixup_main_from_name()`` is a + no-op (the name ends with ``.__main__``), so we cannot observe + a difference in ``sys.modules['__main__'].__name__`` between + the two modes. Checking ``_parent_main_data`` directly is the + 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( - [ - 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()) + return has_data 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( 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': pytest.skip( 'parent main inheritance opt-out only affects the trio spawn backend' ) - assert _run_parent_main_inheritance_script( - tmp_path, - api='run_in_actor', - ) == { - 'replaying': '__mp_main__', - 'isolated': '__main__', - } + + async def main(): + async with tractor.open_nursery(start_method='trio') as an: + + # Default: child receives parent __main__ bootstrap data + 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( start_method, - tmp_path, ): + ''' + Verify ``inherit_parent_main=False`` on ``start_actor()`` + prevents parent ``__main__`` data from reaching the child. + + ''' if start_method != 'trio': pytest.skip( 'parent main inheritance opt-out only affects the trio spawn backend' ) - assert _run_parent_main_inheritance_script( - tmp_path, - api='start_actor', - ) == { - 'replaying': '__mp_main__', - 'isolated': '__main__', - } + + async def main(): + async with tractor.open_nursery(start_method='trio') as an: + + # Default: child receives parent __main__ bootstrap data + 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)