diff --git a/tractor/_testing/pytest.py b/tractor/_testing/pytest.py index c33406ff..ef3cc9a7 100644 --- a/tractor/_testing/pytest.py +++ b/tractor/_testing/pytest.py @@ -224,8 +224,10 @@ def pytest_addoption( ) -def pytest_configure(config): - backend = config.option.spawn_backend +def pytest_configure( + config: pytest.Config, +): + backend: str = config.option.spawn_backend from tractor.spawn._spawn import try_set_start_method try: try_set_start_method(backend) @@ -241,10 +243,52 @@ def pytest_configure(config): 'markers', 'no_tpt(proto_key): test will (likely) not behave with tpt backend' ) + config.addinivalue_line( + 'markers', + 'skipon_spawn_backend(*start_methods, reason=None): ' + 'skip this test under any of the given `--spawn-backend` ' + 'values; useful for backend-specific known-hang / -borked ' + 'cases (e.g. the `subint` GIL-starvation class documented ' + 'in `ai/conc-anal/subint_sigint_starvation_issue.md`).' + ) + + +def pytest_collection_modifyitems( + config: pytest.Config, + items: list[pytest.Function], +): + ''' + Expand any `@pytest.mark.skipon_spawn_backend(''[, + ...], reason='...')` markers into concrete + `pytest.mark.skip(reason=...)` calls for tests whose + backend-arg set contains the active `--spawn-backend`. + + Uses `item.iter_markers(name=...)` which walks function + + class + module-level marks in the correct scope order (and + handles both the single-`MarkDecorator` and `list[Mark]` + forms of a module-level `pytestmark`) — so the same marker + works at any level a user puts it. + + ''' + backend: str = config.option.spawn_backend + default_reason: str = f'Borked on --spawn-backend={backend!r}' + for item in items: + for mark in item.iter_markers(name='skipon_spawn_backend'): + if backend in mark.args: + reason: str = mark.kwargs.get( + 'reason', + default_reason, + ) + item.add_marker(pytest.mark.skip(reason=reason)) + # first matching mark wins; no value in stacking + # multiple `skip`s on the same item. + break @pytest.fixture(scope='session') -def debug_mode(request) -> bool: +def debug_mode( + request: pytest.FixtureRequest, +) -> bool: ''' Flag state for whether `--tpdb` (for `tractor`-py-debugger) was passed to the test run. @@ -258,12 +302,16 @@ def debug_mode(request) -> bool: @pytest.fixture(scope='session') -def spawn_backend(request) -> str: +def spawn_backend( + request: pytest.FixtureRequest, +) -> str: return request.config.option.spawn_backend @pytest.fixture(scope='session') -def tpt_protos(request) -> list[str]: +def tpt_protos( + request: pytest.FixtureRequest, +) -> list[str]: # allow quoting on CLI proto_keys: list[str] = [ @@ -291,7 +339,7 @@ def tpt_protos(request) -> list[str]: autouse=True, ) def tpt_proto( - request, + request: pytest.FixtureRequest, tpt_protos: list[str], ) -> str: proto_key: str = tpt_protos[0] @@ -343,7 +391,6 @@ def pytest_generate_tests( metafunc: pytest.Metafunc, ): spawn_backend: str = metafunc.config.option.spawn_backend - if not spawn_backend: # XXX some weird windows bug with `pytest`? spawn_backend = 'trio'