Skip to content

Commit 6ffc8c8

Browse files
committed
test: autouse gh-106749 heal fixture so CTracer desync cannot cross tests
Under xdist, a desync at the end of one test carried into the start of the next on the same worker; the missed lines moved with worker test ordering (last seen as tests/shared/test_streamable_http.py:1399-1404, previously :2072-2079). The per-throw-site heals in src/ covered every SDK site we found; this backstops any remaining ones in test code or test deps. The heal line itself runs while the tracer is desynced (and is dead on non-3.11), hence lax no cover.
1 parent 48e4c23 commit 6ffc8c8

1 file changed

Lines changed: 28 additions & 1 deletion

File tree

tests/conftest.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
2-
from collections.abc import Iterator
2+
import sys
3+
from collections.abc import AsyncIterator, Iterator
34

5+
import anyio.lowlevel
46
import pytest
57

68
# OpenTelemetry's `set_tracer_provider` is set-once per process, so the suite
@@ -23,6 +25,31 @@ def anyio_backend():
2325
return "asyncio"
2426

2527

28+
@pytest.fixture(autouse=True)
29+
async def _heal_gh106749(anyio_backend: str) -> AsyncIterator[None]:
30+
"""Re-sync coverage's CTracer after every async test on CPython 3.11.
31+
32+
CPython gh-106749: anyio delivers a task-group cancel via `coro.throw()`,
33+
which on 3.11 skips the outer await chain's `'call'` trace events.
34+
coverage.py's CTracer keys its frame stack on those events, so until the
35+
next `.send()` resumption, line events are misattributed and dropped. Under
36+
xdist a desync at the end of one test carries into the start of the next on
37+
the same worker; the missed lines move with worker test ordering.
38+
39+
`cancel_shielded_checkpoint()` resumes via `.send()`, re-stamping the
40+
missing events. The shielded variant is a pure scheduling yield with no
41+
cancel delivery, so it cannot perturb a test's cancel-scope nesting. The
42+
`anyio_backend` dependency makes anyio's pytest plugin own this fixture so
43+
it runs in the test's task; sync tests skip it.
44+
"""
45+
yield
46+
# The heal line itself runs while the tracer is desynced (that's the
47+
# point), so it cannot record its own execution; on non-3.11 the body is
48+
# never entered. Hence lax no cover on the whole tail.
49+
if sys.version_info[:2] == (3, 11): # pragma: lax no cover
50+
await anyio.lowlevel.cancel_shielded_checkpoint()
51+
52+
2653
@pytest.fixture(name="capfire")
2754
def _capfire_isolated(capfire: CaptureLogfire) -> Iterator[CaptureLogfire]:
2855
"""Override of logfire's `capfire` that scopes the MCP tracer to the test.

0 commit comments

Comments
 (0)