Skip to content

Commit ea6a479

Browse files
committed
tests: avoid abandoned-async-generator warnings under the trio backend
The session-level timeout test now runs on trio's virtual clock. At teardown the streamable-HTTP client abandons its httpx/httpx-sse response generators; trio's asyncgen finalizer warns about each one (asyncio finalizes abandoned generators silently at loop shutdown), and filterwarnings=error turns that into a test failure. Scope-ignore the two third-party generator signatures, and make the bridge's response body delegate to the memory stream's own iterator so the harness itself leaves no abandoned generator.
1 parent b880950 commit ea6a479

2 files changed

Lines changed: 21 additions & 3 deletions

File tree

tests/interaction/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ def pytest_configure(config: pytest.Config) -> None:
2323
"filterwarnings", "ignore:.*MemoryObject(Send|Receive)Stream:pytest.PytestUnraisableExceptionWarning"
2424
)
2525
config.addinivalue_line("filterwarnings", "ignore:.*MemoryObject(Send|Receive)Stream:ResourceWarning")
26+
# The trio-mockclock leg of the session-level timeout test (test_timeouts.py) is the suite's
27+
# only test on the trio backend. v1's streamable-HTTP client abandons its httpx/httpx-sse
28+
# response generators when the session task group is cancelled at teardown; asyncio finalizes
29+
# abandoned async generators silently at loop shutdown, but trio's finalizer warns about each
30+
# one (`Async generator ... was garbage collected before it had been exhausted`). The fixes
31+
# live in `src/` on `main` and are out of scope for this tests-only backport. The filters are
32+
# scoped to the two known httpx generator signatures so an unrelated leak still fails the suite.
33+
config.addinivalue_line("filterwarnings", "ignore:Async generator 'httpx:ResourceWarning")
34+
config.addinivalue_line(
35+
"filterwarnings",
36+
"ignore:.*async_generator object (Response.aiter_text|EventSource.aiter_sse)"
37+
":pytest.PytestUnraisableExceptionWarning",
38+
)
2639

2740

2841
_FACTORIES: dict[str, Connect] = {

tests/interaction/transports/_bridge.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,14 @@ def __init__(self, chunks: MemoryObjectReceiveStream[bytes], client_disconnected
4747
self._chunks = chunks
4848
self._client_disconnected = client_disconnected
4949

50-
async def __aiter__(self) -> AsyncIterator[bytes]:
51-
async for chunk in self._chunks:
52-
yield chunk
50+
def __aiter__(self) -> AsyncIterator[bytes]:
51+
# Delegate to the memory stream's own async iterator instead of wrapping it in an async
52+
# generator. httpx abandons the iterator without closing it when a streamed response is
53+
# closed mid-stream; trio's asyncgen finalizer warns about abandoned generators (asyncio
54+
# finalizes them silently at loop shutdown), which would fail the suite's one trio-backend
55+
# test. The memory stream is a plain async iterator with the same EndOfStream ->
56+
# StopAsyncIteration semantics and is not tracked by that machinery.
57+
return self._chunks
5358

5459
async def aclose(self) -> None:
5560
self._client_disconnected.set()

0 commit comments

Comments
 (0)