Exercise parent-main inheritance through spawn test support
Move the subprocess probe into dedicated spawn test support files so the inheritance tests cover the real __main__ replay path without monkeypatching or inline script strings.subint_spawner_backend
parent
00637764d9
commit
b883b27646
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
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])
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
async def get_main_mod_name() -> str:
|
||||||
|
return sys.modules['__main__'].__name__
|
||||||
|
|
@ -3,6 +3,11 @@ 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,
|
||||||
)
|
)
|
||||||
|
|
@ -17,6 +22,11 @@ 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(
|
||||||
|
|
@ -173,6 +183,38 @@ async def get_main_mod_name() -> str:
|
||||||
return sys.modules['__main__'].__name__
|
return sys.modules['__main__'].__name__
|
||||||
|
|
||||||
|
|
||||||
|
def _run_parent_main_inheritance_script(
|
||||||
|
tmp_path: Path,
|
||||||
|
*,
|
||||||
|
api: str,
|
||||||
|
) -> dict[str, str]:
|
||||||
|
output_file = tmp_path / 'out.json'
|
||||||
|
|
||||||
|
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}'
|
||||||
|
)
|
||||||
|
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())
|
||||||
|
|
||||||
|
|
||||||
def test_loglevel_propagated_to_subactor(
|
def test_loglevel_propagated_to_subactor(
|
||||||
start_method,
|
start_method,
|
||||||
capfd,
|
capfd,
|
||||||
|
|
@ -206,98 +248,33 @@ 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,
|
||||||
reg_addr,
|
tmp_path,
|
||||||
monkeypatch,
|
|
||||||
):
|
):
|
||||||
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'
|
||||||
)
|
)
|
||||||
from tractor.spawn import _mp_fixup_main
|
assert _run_parent_main_inheritance_script(
|
||||||
|
tmp_path,
|
||||||
monkeypatch.setattr(
|
api='run_in_actor',
|
||||||
_mp_fixup_main,
|
) == {
|
||||||
'_mp_figure_out_main',
|
'replaying': '__mp_main__',
|
||||||
lambda inherit_parent_main=True: (
|
'isolated': '__main__',
|
||||||
{'init_main_from_name': __name__}
|
}
|
||||||
if inherit_parent_main
|
|
||||||
else {}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
async with tractor.open_nursery(
|
|
||||||
name='registrar',
|
|
||||||
start_method=start_method,
|
|
||||||
registry_addrs=[reg_addr],
|
|
||||||
) as an:
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Stdlib spawn re-runs an importable parent ``__main__`` as
|
|
||||||
# ``__mp_main__``; opting out should leave the child bootstrap
|
|
||||||
# module alone instead.
|
|
||||||
# https://docs.python.org/3/library/multiprocessing.html#the-spawn-and-forkserver-start-methods
|
|
||||||
assert await replaying.result() == '__mp_main__'
|
|
||||||
assert await isolated.result() == '__main__'
|
|
||||||
|
|
||||||
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,
|
||||||
reg_addr,
|
tmp_path,
|
||||||
monkeypatch,
|
|
||||||
):
|
):
|
||||||
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'
|
||||||
)
|
)
|
||||||
from tractor.spawn import _mp_fixup_main
|
assert _run_parent_main_inheritance_script(
|
||||||
|
tmp_path,
|
||||||
monkeypatch.setattr(
|
api='start_actor',
|
||||||
_mp_fixup_main,
|
) == {
|
||||||
'_mp_figure_out_main',
|
'replaying': '__mp_main__',
|
||||||
lambda inherit_parent_main=True: (
|
'isolated': '__main__',
|
||||||
{'init_main_from_name': __name__}
|
}
|
||||||
if inherit_parent_main
|
|
||||||
else {}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
async with tractor.open_nursery(
|
|
||||||
name='registrar',
|
|
||||||
start_method=start_method,
|
|
||||||
registry_addrs=[reg_addr],
|
|
||||||
) as an:
|
|
||||||
replaying = await an.start_actor(
|
|
||||||
'replaying-parent-main',
|
|
||||||
enable_modules=[__name__],
|
|
||||||
)
|
|
||||||
isolated = await an.start_actor(
|
|
||||||
'isolated-parent-main',
|
|
||||||
enable_modules=[__name__],
|
|
||||||
inherit_parent_main=False,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
assert await replaying.run_from_ns(
|
|
||||||
__name__,
|
|
||||||
'get_main_mod_name',
|
|
||||||
) == '__mp_main__'
|
|
||||||
assert await isolated.run_from_ns(
|
|
||||||
__name__,
|
|
||||||
'get_main_mod_name',
|
|
||||||
) == '__main__'
|
|
||||||
finally:
|
|
||||||
await replaying.cancel_actor()
|
|
||||||
await isolated.cancel_actor()
|
|
||||||
|
|
||||||
trio.run(main)
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue