Add `skipon_spawn_backend` pytest marker

A reusable `@pytest.mark.skipon_spawn_backend( '<backend>' [, ...],
reason='...')` marker for backend-specific known-hang / -borked cases
— avoids scattering `@pytest.mark.skipif(lambda ...)` branches across
tests that misbehave under a particular `--spawn-backend`.

Deats,
- `pytest_configure()` registers the marker via
  `addinivalue_line('markers', ...)`.
- New `pytest_collection_modifyitems()` hook walks
  each collected item with `item.iter_markers(
  name='skipon_spawn_backend')`, checks whether the
  active `--spawn-backend` appears in `mark.args`, and
  if so injects a concrete `pytest.mark.skip(
  reason=...)`. `iter_markers()` makes the decorator
  work at function, class, or module (`pytestmark =
  [...]`) scope transparently.
- First matching mark wins; default reason is
  `f'Borked on --spawn-backend={backend!r}'` if the
  caller doesn't supply one.

Also, tighten type annotations on nearby `pytest`
integration points — `pytest_configure`, `debug_mode`,
`spawn_backend`, `tpt_protos`, `tpt_proto` — now taking
typed `pytest.Config` / `pytest.FixtureRequest` params.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
subint_forkserver_backend
Gud Boi 2026-04-21 21:24:51 -04:00
parent f3cea714bc
commit 3b26b59dad
1 changed files with 54 additions and 7 deletions

View File

@ -224,8 +224,10 @@ def pytest_addoption(
) )
def pytest_configure(config): def pytest_configure(
backend = config.option.spawn_backend config: pytest.Config,
):
backend: str = config.option.spawn_backend
from tractor.spawn._spawn import try_set_start_method from tractor.spawn._spawn import try_set_start_method
try: try:
try_set_start_method(backend) try_set_start_method(backend)
@ -241,10 +243,52 @@ def pytest_configure(config):
'markers', 'markers',
'no_tpt(proto_key): test will (likely) not behave with tpt backend' '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('<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') @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) Flag state for whether `--tpdb` (for `tractor`-py-debugger)
was passed to the test run. was passed to the test run.
@ -258,12 +302,16 @@ def debug_mode(request) -> bool:
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def spawn_backend(request) -> str: def spawn_backend(
request: pytest.FixtureRequest,
) -> str:
return request.config.option.spawn_backend return request.config.option.spawn_backend
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def tpt_protos(request) -> list[str]: def tpt_protos(
request: pytest.FixtureRequest,
) -> list[str]:
# allow quoting on CLI # allow quoting on CLI
proto_keys: list[str] = [ proto_keys: list[str] = [
@ -291,7 +339,7 @@ def tpt_protos(request) -> list[str]:
autouse=True, autouse=True,
) )
def tpt_proto( def tpt_proto(
request, request: pytest.FixtureRequest,
tpt_protos: list[str], tpt_protos: list[str],
) -> str: ) -> str:
proto_key: str = tpt_protos[0] proto_key: str = tpt_protos[0]
@ -343,7 +391,6 @@ def pytest_generate_tests(
metafunc: pytest.Metafunc, metafunc: pytest.Metafunc,
): ):
spawn_backend: str = metafunc.config.option.spawn_backend spawn_backend: str = metafunc.config.option.spawn_backend
if not spawn_backend: if not spawn_backend:
# XXX some weird windows bug with `pytest`? # XXX some weird windows bug with `pytest`?
spawn_backend = 'trio' spawn_backend = 'trio'