Skip to content

Commit 600c96e

Browse files
committed
Extend the per-test GC fixture to the SSE and Unicode HTTP test modules
tests/shared/test_sse.py and tests/client/test_http_unicode.py carry the same scoped MemoryObject*Stream filterwarnings marks as tests/shared/test_streamable_http.py and had the same gap: the leaked streams sit in reference cycles, so their deallocator warnings fired at pytest's session-unconfigure unraisable sweep, exiting 1 after all tests passed when run without xdist (-n 0). Both files documented this in their filter-mark comments. Apply the same autouse fixture that fixed test_streamable_http.py — a gc.collect() in each test's teardown, where the item-scoped ignores still apply — and update the comments to describe the fixed behavior. Verified: -n 0 runs of each file now exit 0 (previously 26 passed/exit 1 and 2 passed/exit 1), and five consecutive default-option runs of both files together pass.
1 parent 9dc96d6 commit 600c96e

2 files changed

Lines changed: 44 additions & 10 deletions

File tree

tests/client/test_http_unicode.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
(server→client and client→server) using the streamable HTTP transport.
66
"""
77

8+
import gc
89
from collections.abc import AsyncIterator, Iterator
910
from contextlib import asynccontextmanager
1011
from typing import Any
@@ -30,17 +31,33 @@
3031
# run in process; the old subprocess harness never observed them. The interaction suite registers
3132
# the same two scoped filters globally from tests/interaction/conftest.py (see the comment there),
3233
# but they only take effect when that package's conftest is loaded; these markers keep the tests
33-
# themselves passing in isolated runs. Markers are item-scoped, so they cannot cover the GC
34-
# flush at session cleanup: an isolated run without xdist (`-n 0`) still exits nonzero after all
35-
# tests pass. The default xdist runs (addopts has `-n auto`) are unaffected, as are full-suite
36-
# runs, where the interaction conftest's ini-level filters apply. The filters are scoped to
37-
# anyio's MemoryObject*Stream leak signature so an unrelated leak still fails the suite.
34+
# themselves passing in isolated runs. Markers are item-scoped, so the autouse
35+
# `_collect_leaked_streams` fixture below garbage-collects each test's leaks inside its own
36+
# teardown, where these filters apply; without it, leaks GC'd at session cleanup escape the
37+
# scoped ignores. The filters are scoped to anyio's MemoryObject*Stream leak signature so an
38+
# unrelated leak still fails the suite.
3839
pytestmark = [
3940
pytest.mark.filterwarnings("ignore:.*MemoryObject(Send|Receive)Stream:pytest.PytestUnraisableExceptionWarning"),
4041
pytest.mark.filterwarnings("ignore:.*MemoryObject(Send|Receive)Stream:ResourceWarning"),
4142
]
4243

4344

45+
@pytest.fixture(autouse=True)
46+
def _collect_leaked_streams() -> Iterator[None]:
47+
"""Garbage-collect each test's leaked memory streams inside its own teardown.
48+
49+
The filterwarnings marks above only apply while a test in this file is the
50+
active warning context. The leaked streams sit in reference cycles, so without
51+
a forced collection their deallocator warnings fire wherever the garbage
52+
collector happens to run next: during an unrelated test (failing it, since the
53+
global ``filterwarnings = ["error"]`` has no ignore there) or at pytest's
54+
session-unconfigure unraisable sweep (exit code 1 after all tests passed when
55+
running without xdist, e.g. ``-n 0`` for ``--pdb`` debugging).
56+
"""
57+
yield
58+
gc.collect()
59+
60+
4461
@pytest.fixture(autouse=True)
4562
def _reset_sse_starlette_exit_event() -> Iterator[None]:
4663
"""Reset sse-starlette's module-global exit Event around each test.

tests/shared/test_sse.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests for the SSE client and server transports, driven entirely in process."""
22

3+
import gc
34
import json
45
from collections.abc import AsyncGenerator, Iterable, Iterator
56
from typing import Any
@@ -50,17 +51,33 @@
5051
# process; the old subprocess harness never observed them. The interaction suite registers the
5152
# same two scoped filters globally from tests/interaction/conftest.py (see the comment there),
5253
# but they only take effect when that package's conftest is loaded; these markers keep the tests
53-
# themselves passing in isolated runs. Markers are item-scoped, so they cannot cover the GC
54-
# flush at session cleanup: an isolated run without xdist (`-n 0`) still exits nonzero after all
55-
# tests pass. The default xdist runs (addopts has `-n auto`) are unaffected, as are full-suite
56-
# runs, where the interaction conftest's ini-level filters apply. The filters are scoped to
57-
# anyio's MemoryObject*Stream leak signature so an unrelated leak still fails the suite.
54+
# themselves passing in isolated runs. Markers are item-scoped, so the autouse
55+
# `_collect_leaked_streams` fixture below garbage-collects each test's leaks inside its own
56+
# teardown, where these filters apply; without it, leaks GC'd at session cleanup escape the
57+
# scoped ignores. The filters are scoped to anyio's MemoryObject*Stream leak signature so an
58+
# unrelated leak still fails the suite.
5859
pytestmark = [
5960
pytest.mark.filterwarnings("ignore:.*MemoryObject(Send|Receive)Stream:pytest.PytestUnraisableExceptionWarning"),
6061
pytest.mark.filterwarnings("ignore:.*MemoryObject(Send|Receive)Stream:ResourceWarning"),
6162
]
6263

6364

65+
@pytest.fixture(autouse=True)
66+
def _collect_leaked_streams() -> Iterator[None]:
67+
"""Garbage-collect each test's leaked memory streams inside its own teardown.
68+
69+
The filterwarnings marks above only apply while a test in this file is the
70+
active warning context. The leaked streams sit in reference cycles, so without
71+
a forced collection their deallocator warnings fire wherever the garbage
72+
collector happens to run next: during an unrelated test (failing it, since the
73+
global ``filterwarnings = ["error"]`` has no ignore there) or at pytest's
74+
session-unconfigure unraisable sweep (exit code 1 after all tests passed when
75+
running without xdist, e.g. ``-n 0`` for ``--pdb`` debugging).
76+
"""
77+
yield
78+
gc.collect()
79+
80+
6481
@pytest.fixture(autouse=True)
6582
def _reset_sse_starlette_exit_event() -> Iterator[None]:
6683
"""Reset sse-starlette's module-global exit Event around each test.

0 commit comments

Comments
 (0)