test: make two tests portable to Python 3.10 and free of a startup race - #961
Merged
agentforce314 merged 1 commit intoOct 4, 2026
Conversation
`requires-python = ">=3.10"`, but both tests below hard-coded 3.11-only behaviour, so neither could run on a supported interpreter. `test_repl_bridge.py` used `asyncio.timeout`, added in 3.11. Rewritten with `asyncio.wait_for` around the polling coroutine, which is the portable equivalent. The `refreshed` binding is carried out of the closure through a dict holder rather than `nonlocal`, which cannot target a name that only exists in the enclosing function's try-block. `test_init_integration.py` signalled its child after a fixed 100/200/300 ms. That races interpreter startup: when the signal lands before `setup_graceful_shutdown()` installs its handler, the default disposition terminates the child and it exits `-SIGINT` instead of the handled `130`, which reads as a product bug but is purely test timing. Measured directly — a 0.1 s delay yields `rc=-2`, 0.3 s yields `rc=130`. Replaced the guessed delay with a readiness handshake: the child prints `__READY__` once its handlers are installed and `_run_in_subprocess` waits for that marker before signalling. `_drain_until_ready` reads with `select` under a deadline, so a child that dies during startup cannot hang the test. `wait_for_ready` defaults to False, leaving existing callers untouched. Verified green on both interpreters, 61 passed on 3.11 and on 3.10.
Test Results 5 files 1 021 suites 20m 44s ⏱️ Results for commit 643ed9e. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
requires-python = ">=3.10", but both tests below hard-coded 3.11-only behaviour, so neither could run on a supported interpreter. A second failure was a genuine race that could bite on any interpreter.1.
asyncio.timeoutis 3.11+ (tests/bridge/test_repl_bridge.py)test_pointer_mtime_task_fires_and_advances_updated_at_msusedasync with asyncio.timeout(3). On 3.10 this is anAttributeErrorand the test cannot run at all.Rewritten with
asyncio.wait_foraround the polling coroutine — the portable equivalent. Therefreshedvalue is carried out of the closure through a dict holder;nonlocalcannot target a name that is only bound inside the enclosingtryblock.2. SIGINT/SIGTERM startup race (
tests/test_init_integration.py)Three tests signalled their child process after a fixed 100–300 ms delay. That races interpreter startup: if the signal arrives before
setup_graceful_shutdown()installs its handler, the default disposition terminates the child and it exits-SIGINTrather than the handled130/143.Measured directly:
-2(default disposition)130(handler ran)This reads as a product bug but is purely test timing — the child had not finished importing yet.
Fix
Replaced the guessed delay with a readiness handshake:
__READY__once its handlers are installed._run_in_subprocess(..., wait_for_ready=True)blocks until that marker appears before signalling._drain_until_readyreads from the child's stdout usingselectunder a deadline, so a child that dies during startup cannot hang the test. The newwait_for_readyparameter defaults toFalse, leaving existing callers unaffected.Verification
Green on both interpreters, which is the point for a portability fix:
tests/test_init_integration.pywas run three consecutive times to confirm the race is actually gone rather than merely unobserved.