Flush the stdio subprocess's coverage data before the clean-exit line#2840
Open
maxisbey wants to merge 1 commit into
Open
Flush the stdio subprocess's coverage data before the clean-exit line#2840maxisbey wants to merge 1 commit into
maxisbey wants to merge 1 commit into
Conversation
The stdio interaction test's child only persisted its coverage data in coverage's atexit hook. On a slow Windows runner the interpreter teardown plus sqlite save can overrun the transport's termination grace, and the Job Object kill then silently destroys (or tears) the data file: every test passes but the 100% gate trips on _stdio_server.py's subprocess-only lines. Save the data explicitly before printing the clean-exit line instead. The test already synchronizes on that line, so once it has been observed the data is durably on disk and a late kill is harmless. A kill before the print now fails the stderr assertion loudly instead of tripping the coverage gate. The trailing lines are excluded (lax no cover): nothing measured can execute after the flush by construction.
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.
Makes the stdio interaction test's subprocess flush its coverage data before printing the clean-exit line, so the transport's terminate escalation can no longer silently destroy the data file.
Motivation and Context
checks / test (3.10, locked, windows-latest)failed on #2838 with every test passing but the 100% gate tripping ontests\interaction\transports\_stdio_server.pyat 52.17% — exactly the subprocess-only lines (failing job).Root cause: the child only persisted its coverage data in coverage's atexit hook, which runs after the
stdio-echo: clean exitprint the test synchronizes on. The failing run's test took 12.40s ≈ 2.4s session + exactly the 10.0sPROCESS_TERMINATION_TIMEOUTthe test monkeypatches — the child printed the line (test green), then didn't finish interpreter teardown + the sqlite save within the grace, and the Windows escalation (TerminateJobObject) killed it mid-save. A torn save leaves a valid-but-empty data file thatcoverage combinemerges without a warning, which is why the failing run still combined the same 6 data files as passing runs.This is the same race #2773 narrowed by raising the grace from 2s to 10s; the wider grace made it rarer, not impossible. Extracting this test's wall time from ~400 recent Windows CI job logs shows a smooth heavy tail — p50 3.3s, p99 ~7s, then 8.8s and 9.0s (passed, barely under the grace) and 12.4s (killed) — so any finite grace loses occasionally as long as the data write happens after the observable sync point.
The fix inverts the ordering instead:
main()now stops and saves coverage before printing the clean-exit line. That makes the test's existing stderr assertion the durability barrier — once the parent has seen the line, the data is on disk and a late kill is harmless. A kill landing before the print now fails the stderr assertion loudly instead of tripping the coverage gate one step later.Details that fall out of the ordering:
cov.save()andprint(...)lines carry# pragma: lax no cover.cov.stop()is load-bearing twice: it ends tracing, and it leaves nothing for coverage's atexit re-save to flush — so a kill during interpreter teardown can't corrupt the already-written file (coverage opens it with sqlite journaling off; a torn rewrite would not roll back).if cov is not Nonebranch is# pragma: no branch: under coverage the instance always exists, and without coverage nothing is measured anyway.How Has This Been Tested?
coverage run -m pytest tests/interaction/transports/test_stdio.py→ both tests pass,_stdio_server.pyreports 100.00% (statements + branches) from the combined parent+child data../scripts/test: 100.00% total,strict-no-coverclean.windows-latestrunners (and ~2,200 on Linux) while investigating: the child's post-print phase is interpreter teardown plus the coverage sqlite save, and the save is the phase that runner load stretches — which is what made the atexit-ordering the wrong side of the sync point.Breaking Changes
None — test-support change only.
Types of changes
Checklist
Additional context
The
import coverageat module top makes this test-support file depend on the dev environment even for the docstring's standalonepython -minvocation; that's already true of the repo's dev venv everywhere this module is used, so it keeps imports-at-top rather than adding a guarded import plus more pragma machinery.AI Disclaimer