From ecced3d09af5178c523afe4b3fd7329549f0631d Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sun, 26 Jan 2020 21:36:08 -0500 Subject: [PATCH] Allow choosing the spawn backend per test session Add a `--spawn-backend` option which can be set to one of {'mp', 'trio_run_in_process'} which will either run the test suite using the `multiprocessing` or `trio-run-in-process` backend respectively. Currently trying to run both in the same session can result in hangs seemingly due to a lack of cleanup of forkservers / resource trackers from `multiprocessing` which cause broken pipe errors on occasion (no idea on the details). For `test_cancellation.py::test_nested_multierrors`, use less nesting when mp is used since it breaks if we push it too hard with the whole recursive subprocess spawning thing... --- tests/conftest.py | 41 ++++++++++++++++++++++++-------- tests/test_cancellation.py | 22 ++++++++--------- tests/test_rpc.py | 2 +- tractor/testing/_tractor_test.py | 4 ++-- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index bdef563..924036b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,8 +14,24 @@ _arb_addr = '127.0.0.1', random.randint(1000, 9999) def pytest_addoption(parser): - parser.addoption("--ll", action="store", dest='loglevel', - default=None, help="logging level to set when testing") + parser.addoption( + "--ll", action="store", dest='loglevel', + default=None, help="logging level to set when testing" + ) + + parser.addoption( + "--spawn-backend", action="store", dest='spawn_backend', + default='trio_run_in_process', + help="Processing spawning backend to use for test run", + ) + + +def pytest_configure(config): + backend = config.option.spawn_backend + if backend == 'mp': + tractor._spawn.try_set_start_method('spawn') + elif backend == 'trio_run_in_process': + tractor._spawn.try_set_start_method(backend) @pytest.fixture(scope='session', autouse=True) @@ -32,16 +48,21 @@ def arb_addr(): def pytest_generate_tests(metafunc): + spawn_backend = metafunc.config.getoption("spawn_backend") + assert spawn_backend in ('mp', 'trio_run_in_process') + if 'start_method' in metafunc.fixturenames: + if spawn_backend == 'mp': + from multiprocessing import get_all_start_methods + methods = get_all_start_methods() + if 'fork' in methods: # fork not available on windows, so check before removing + # XXX: the fork method is in general incompatible with + # trio's global scheduler state + methods.remove('fork') + elif spawn_backend == 'trio_run_in_process': + if platform.system() == "Windows": + pytest.fail("Only `--spawn-backend=mp` is supported on Windows") - from multiprocessing import get_all_start_methods - methods = get_all_start_methods() - - if platform.system() != "Windows": methods = ['trio_run_in_process'] - if 'fork' in methods: # fork not available on windows, so check before removing - # XXX: the fork method is in general incompatible with - # trio's global scheduler state - methods.remove('fork') metafunc.parametrize("start_method", methods, scope='module') diff --git a/tests/test_cancellation.py b/tests/test_cancellation.py index a8e0bec..bbf8598 100644 --- a/tests/test_cancellation.py +++ b/tests/test_cancellation.py @@ -284,21 +284,21 @@ async def spawn_and_error(breadth, depth) -> None: @tractor_test -async def test_nested_multierrors(loglevel): +async def test_nested_multierrors(loglevel, start_method): """Test that failed actor sets are wrapped in `trio.MultiError`s. This test goes only 2 nurseries deep but we should eventually have tests for arbitrary n-depth actor trees. """ - # if start_method == 'trio_run_in_process': - depth = 2 - subactor_breadth = 3 - # else: - # # XXX: multiprocessing can't seem to handle any more then 2 depth - # # process trees for whatever reason. - # # Any more process levels then this and we see bugs that cause - # # hangs and broken pipes all over the place... - # depth = 1 - # subactor_breadth = 2 + if start_method == 'trio_run_in_process': + depth = 3 + subactor_breadth = 2 + else: + # XXX: multiprocessing can't seem to handle any more then 2 depth + # process trees for whatever reason. + # Any more process levels then this and we see bugs that cause + # hangs and broken pipes all over the place... + depth = 2 + subactor_breadth = 2 with trio.fail_after(120): try: diff --git a/tests/test_rpc.py b/tests/test_rpc.py index 0258f35..b3fa1df 100644 --- a/tests/test_rpc.py +++ b/tests/test_rpc.py @@ -60,7 +60,7 @@ def test_rpc_errors(arb_addr, to_call, testdir): if exposed_mods == ['tmp_mod']: # create an importable module with a bad import testdir.syspathinsert() - # module should cause a raise of a ModuleNotFoundError at import + # module should raise a ModuleNotFoundError at import testdir.makefile('.py', tmp_mod=funcname) # no need to exposed module to the subactor diff --git a/tractor/testing/_tractor_test.py b/tractor/testing/_tractor_test.py index 2399318..82f38f2 100644 --- a/tractor/testing/_tractor_test.py +++ b/tractor/testing/_tractor_test.py @@ -19,6 +19,7 @@ def tractor_test(fn): - ``arb_addr`` (a socket addr tuple where arbiter is listening) - ``loglevel`` (logging level passed to tractor internals) + - ``start_method`` (subprocess spawning backend) are defined in the `pytest` fixture space they will be automatically injected to tests declaring these funcargs. @@ -41,8 +42,7 @@ def tractor_test(fn): # that activates the internal logging kwargs['loglevel'] = loglevel if 'start_method' in inspect.signature(fn).parameters: - # allows test suites to define a 'loglevel' fixture - # that activates the internal logging + # set of subprocess spawning backends kwargs['start_method'] = start_method return run( partial(fn, *args, **kwargs),