From 4b2a0886c3ac4b8fdeb1db3db820df2396f3c198 Mon Sep 17 00:00:00 2001 From: goodboy Date: Tue, 21 Apr 2026 21:33:15 -0400 Subject: [PATCH] Mark `subint`-hanging tests with `skipon_spawn_backend` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopt the `@pytest.mark.skipon_spawn_backend('subint', reason=...)` marker (a617b521) across the suites reproducing the `subint` GIL-contention / starvation hang classes doc'd in `ai/conc-anal/subint_*_issue.md`. Deats, - Module-level `pytestmark` on full-file-hanging suites: - `tests/test_cancellation.py` - `tests/test_inter_peer_cancellation.py` - `tests/test_pubsub.py` - `tests/test_shm.py` - Per-test decorator where only one test in the file hangs: - `tests/discovery/test_registrar.py ::test_stale_entry_is_deleted` — replaces the inline `if start_method == 'subint': pytest.skip` branch with a declarative skip. - `tests/test_subint_cancellation.py ::test_subint_non_checkpointing_child`. - A few per-test decorators are left commented-in- place as breadcrumbs for later finer-grained unskips. Also, some nearby tidying in the affected files: - Annotate loose fixture / test params (`pytest.FixtureRequest`, `str`, `tuple`, `bool`) in `tests/conftest.py`, `tests/devx/conftest.py`, and `tests/test_cancellation.py`. - Normalize `"""..."""` → `'''...'''` docstrings per repo convention on a few touched tests. - Add `timeout=6` / `timeout=10` to `@tractor_test(...)` on `test_cancel_infinite_streamer` and `test_some_cancels_all`. - Drop redundant `spawn_backend` param from `test_cancel_via_SIGINT`; use `start_method` in the `'mp' in ...` check instead. (this commit msg was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code --- tests/conftest.py | 6 +- tests/devx/conftest.py | 5 +- tests/discovery/test_registrar.py | 18 +++-- tests/test_cancellation.py | 107 +++++++++++++++++++++----- tests/test_inter_peer_cancellation.py | 9 +++ tests/test_pubsub.py | 8 ++ tests/test_shm.py | 8 ++ tests/test_subint_cancellation.py | 8 ++ 8 files changed, 136 insertions(+), 33 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c7b20531..90498ba0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -139,7 +139,9 @@ def pytest_addoption( @pytest.fixture(scope='session', autouse=True) -def loglevel(request) -> str: +def loglevel( + request: pytest.FixtureRequest, +) -> str: import tractor orig = tractor.log._default_loglevel level = tractor.log._default_loglevel = request.config.option.loglevel @@ -156,7 +158,7 @@ def loglevel(request) -> str: @pytest.fixture(scope='function') def test_log( - request, + request: pytest.FixtureRequest, loglevel: str, ) -> tractor.log.StackLevelAdapter: ''' diff --git a/tests/devx/conftest.py b/tests/devx/conftest.py index eb56d74c..747c859d 100644 --- a/tests/devx/conftest.py +++ b/tests/devx/conftest.py @@ -146,13 +146,12 @@ def spawn( ids='ctl-c={}'.format, ) def ctlc( - request, + request: pytest.FixtureRequest, ci_env: bool, ) -> bool: - use_ctlc = request.param - + use_ctlc: bool = request.param node = request.node markers = node.own_markers for mark in markers: diff --git a/tests/discovery/test_registrar.py b/tests/discovery/test_registrar.py index 02748370..a004ddac 100644 --- a/tests/discovery/test_registrar.py +++ b/tests/discovery/test_registrar.py @@ -520,8 +520,6 @@ async def kill_transport( -# @pytest.mark.parametrize('use_signal', [False, True]) -# # Wall-clock bound via `pytest-timeout` (`method='thread'`). # Under `--spawn-backend=subint` this test can wedge in an # un-Ctrl-C-able state (abandoned-subint + shared-GIL @@ -537,6 +535,16 @@ async def kill_transport( 3, # NOTE should be a 2.1s happy path. method='thread', ) +@pytest.mark.skipon_spawn_backend( + 'subint', + reason=( + 'XXX SUBINT HANGING TEST XXX\n' + 'See oustanding issue(s)\n' + # TODO, put issue link! + ) +) +# @pytest.mark.parametrize('use_signal', [False, True]) +# def test_stale_entry_is_deleted( debug_mode: bool, daemon: subprocess.Popen, @@ -549,12 +557,6 @@ def test_stale_entry_is_deleted( stale entry and not delivering a bad portal. ''' - if start_method == 'subint': - pytest.skip( - 'XXX SUBINT HANGING TEST XXX\n' - 'See oustanding issue(s)\n' - ) - async def main(): name: str = 'transport_fails_actor' diff --git a/tests/test_cancellation.py b/tests/test_cancellation.py index f1091372..645ee068 100644 --- a/tests/test_cancellation.py +++ b/tests/test_cancellation.py @@ -21,6 +21,16 @@ _non_linux: bool = platform.system() != 'Linux' _friggin_windows: bool = platform.system() == 'Windows' +pytestmark = pytest.mark.skipon_spawn_backend( + 'subint', + reason=( + 'XXX SUBINT HANGING TEST XXX\n' + 'See oustanding issue(s)\n' + # TODO, put issue link! + ) +) + + async def assert_err(delay=0): await trio.sleep(delay) assert 0 @@ -110,8 +120,17 @@ def test_remote_error(reg_addr, args_err): assert exc.boxed_type == errtype +# @pytest.mark.skipon_spawn_backend( +# 'subint', +# reason=( +# 'XXX SUBINT HANGING TEST XXX\n' +# 'See oustanding issue(s)\n' +# # TODO, put issue link! +# ) +# ) def test_multierror( reg_addr: tuple[str, int], + start_method: str, ): ''' Verify we raise a ``BaseExceptionGroup`` out of a nursery where @@ -141,15 +160,28 @@ def test_multierror( trio.run(main) -@pytest.mark.parametrize('delay', (0, 0.5)) @pytest.mark.parametrize( - 'num_subactors', range(25, 26), + 'delay', + (0, 0.5), + ids='delays={}'.format, ) -def test_multierror_fast_nursery(reg_addr, start_method, num_subactors, delay): - """Verify we raise a ``BaseExceptionGroup`` out of a nursery where +@pytest.mark.parametrize( + 'num_subactors', + range(25, 26), + ids= 'num_subs={}'.format, +) +def test_multierror_fast_nursery( + reg_addr: tuple, + start_method: str, + num_subactors: int, + delay: float, +): + ''' + Verify we raise a ``BaseExceptionGroup`` out of a nursery where more then one actor errors and also with a delay before failure to test failure during an ongoing spawning. - """ + + ''' async def main(): async with tractor.open_nursery( registry_addrs=[reg_addr], @@ -189,8 +221,15 @@ async def do_nothing(): pass -@pytest.mark.parametrize('mechanism', ['nursery_cancel', KeyboardInterrupt]) -def test_cancel_single_subactor(reg_addr, mechanism): +@pytest.mark.parametrize( + 'mechanism', [ + 'nursery_cancel', + KeyboardInterrupt, +]) +def test_cancel_single_subactor( + reg_addr: tuple, + mechanism: str|KeyboardInterrupt, +): ''' Ensure a ``ActorNursery.start_actor()`` spawned subactor cancels when the nursery is cancelled. @@ -232,9 +271,12 @@ async def stream_forever(): await trio.sleep(0.01) -@tractor_test -async def test_cancel_infinite_streamer(start_method): - +@tractor_test( + timeout=6, +) +async def test_cancel_infinite_streamer( + start_method: str +): # stream for at most 1 seconds with ( trio.fail_after(4), @@ -257,6 +299,14 @@ async def test_cancel_infinite_streamer(start_method): assert n.cancelled +# @pytest.mark.skipon_spawn_backend( +# 'subint', +# reason=( +# 'XXX SUBINT HANGING TEST XXX\n' +# 'See oustanding issue(s)\n' +# # TODO, put issue link! +# ) +# ) @pytest.mark.parametrize( 'num_actors_and_errs', [ @@ -286,7 +336,9 @@ async def test_cancel_infinite_streamer(start_method): 'no_daemon_actors_fail_all_run_in_actors_sleep_then_fail', ], ) -@tractor_test +@tractor_test( + timeout=10, +) async def test_some_cancels_all( num_actors_and_errs: tuple, start_method: str, @@ -370,7 +422,10 @@ async def test_some_cancels_all( pytest.fail("Should have gotten a remote assertion error?") -async def spawn_and_error(breadth, depth) -> None: +async def spawn_and_error( + breadth: int, + depth: int, +) -> None: name = tractor.current_actor().name async with tractor.open_nursery() as nursery: for i in range(breadth): @@ -396,7 +451,10 @@ async def spawn_and_error(breadth, depth) -> None: @tractor_test -async def test_nested_multierrors(loglevel, start_method): +async def test_nested_multierrors( + loglevel: str, + start_method: str, +): ''' Test that failed actor sets are wrapped in `BaseExceptionGroup`s. This test goes only 2 nurseries deep but we should eventually have tests @@ -483,20 +541,21 @@ async def test_nested_multierrors(loglevel, start_method): @no_windows def test_cancel_via_SIGINT( - loglevel, - start_method, - spawn_backend, + loglevel: str, + start_method: str, ): - """Ensure that a control-C (SIGINT) signal cancels both the parent and + ''' + Ensure that a control-C (SIGINT) signal cancels both the parent and child processes in trionic fashion - """ + + ''' pid: int = os.getpid() async def main(): with trio.fail_after(2): async with tractor.open_nursery() as tn: await tn.start_actor('sucka') - if 'mp' in spawn_backend: + if 'mp' in start_method: time.sleep(0.1) os.kill(pid, signal.SIGINT) await trio.sleep_forever() @@ -580,6 +639,14 @@ async def spawn_sub_with_sync_blocking_task(): print('exiting first subactor layer..\n') +# @pytest.mark.skipon_spawn_backend( +# 'subint', +# reason=( +# 'XXX SUBINT HANGING TEST XXX\n' +# 'See oustanding issue(s)\n' +# # TODO, put issue link! +# ) +# ) @pytest.mark.parametrize( 'man_cancel_outer', [ @@ -694,7 +761,7 @@ def test_cancel_while_childs_child_in_sync_sleep( def test_fast_graceful_cancel_when_spawn_task_in_soft_proc_wait_for_daemon( - start_method, + start_method: str, ): ''' This is a very subtle test which demonstrates how cancellation diff --git a/tests/test_inter_peer_cancellation.py b/tests/test_inter_peer_cancellation.py index b79c0393..fc5d741d 100644 --- a/tests/test_inter_peer_cancellation.py +++ b/tests/test_inter_peer_cancellation.py @@ -26,6 +26,15 @@ from tractor._testing import ( from .conftest import cpu_scaling_factor +pytestmark = pytest.mark.skipon_spawn_backend( + 'subint', + reason=( + 'XXX SUBINT GIL-CONTENTION HANGING TEST XXX\n' + 'See oustanding issue(s)\n' + # TODO, put issue link! + ) +) + # XXX TODO cases: # - [x] WE cancelled the peer and thus should not see any raised # `ContextCancelled` as it should be reaped silently? diff --git a/tests/test_pubsub.py b/tests/test_pubsub.py index 6d416f89..1bf8563a 100644 --- a/tests/test_pubsub.py +++ b/tests/test_pubsub.py @@ -7,6 +7,14 @@ import tractor from tractor.experimental import msgpub from tractor._testing import tractor_test +pytestmark = pytest.mark.skipon_spawn_backend( + 'subint', + reason=( + 'XXX SUBINT HANGING TEST XXX\n' + 'See oustanding issue(s)\n' + # TODO, put issue link! + ) +) def test_type_checks(): diff --git a/tests/test_shm.py b/tests/test_shm.py index 00a36f8a..3409f338 100644 --- a/tests/test_shm.py +++ b/tests/test_shm.py @@ -14,6 +14,14 @@ from tractor.ipc._shm import ( attach_shm_list, ) +pytestmark = pytest.mark.skipon_spawn_backend( + 'subint', + reason=( + 'XXX SUBINT GIL-CONTENTION HANGING TEST XXX\n' + 'See oustanding issue(s)\n' + # TODO, put issue link! + ) +) @tractor.context async def child_attach_shml_alot( diff --git a/tests/test_subint_cancellation.py b/tests/test_subint_cancellation.py index 18cbf78b..0f075738 100644 --- a/tests/test_subint_cancellation.py +++ b/tests/test_subint_cancellation.py @@ -161,6 +161,14 @@ def test_subint_happy_teardown( trio.run(partial(_happy_path, reg_addr, deadline)) +@pytest.mark.skipon_spawn_backend( + 'subint', + reason=( + 'XXX SUBINT HANGING TEST XXX\n' + 'See oustanding issue(s)\n' + # TODO, put issue link! + ) +) # Wall-clock bound via `pytest-timeout` (`method='thread'`) # as defense-in-depth over the inner `trio.fail_after(15)`. # Under the orphaned-channel hang class described in