fix(tests): let pytest-asyncio own the suite, drop anyio markers - #6
Merged
Conversation
Test (redis) has been failing on main since the toolchain drifted, with
"RuntimeError: Event loop is closed" raised from the module-scoped `client`
fixture teardown. Every test passed; only the finalizer errored.
Two async plugins were driving the same tests. The suite carried
@pytest.mark.anyio while pyproject sets asyncio_mode = "auto", so anyio
parametrized the marked tests ("[asyncio]") and ran them in loops it managed
itself, while the module-scoped fixtures stayed on pytest-asyncio's loop. The
Redis connection pool therefore accumulated connections opened on per-test
loops that were closed by the time the module fixture tore down, and
disconnecting them touched dead transports. SQLite never showed it because
aiosqlite is loop-agnostic.
CI instrumentation confirmed both the cause and the fix: before, the fixture
setup reported no current event loop; after, setup, teardown and finalization
all report the same loop.
asyncio_mode = "auto" already handles every async test, so the 47 markers were
redundant. Removing them leaves a single owner for the loop lifecycle. The
invariant is documented in pyproject.toml so it does not regress.
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.
Contexte
Test (redis)has been red onmainsince the toolchain drifted (baseline 29729210049), failing on a fixture teardown rather than a test:Every test passed; only the finalizer errored.
Test (sqlite)was always green.Root cause
Two async plugins were driving the same tests. The suite carried
@pytest.mark.anyiomarkers whilepyproject.tomlsetsasyncio_mode = "auto". anyio parametrized the marked tests — hence the[asyncio]suffix in the test IDs — and ran them in loops it managed itself, while the module-scopedclientfixture stayed on pytest-asyncio's module loop.The Redis connection pool therefore accumulated connections opened on per-test loops that were already closed by the time the module fixture tore down.
disconnect()then touched dead transports. SQLite never surfaced it because aiosqlite is loop-agnostic.Note the failing teardown ran on a live loop — the loop the pool's connections belonged to was a different, dead one. That is why "is the loop closed at teardown?" kept giving a misleading answer.
Evidence
The failure never reproduced locally, so I instrumented CI directly (29730652856 before, 29730869139 after):
no current event loopno current event loopid=140216260195536One coherent loop per module, and all three jobs green.
Changements
@pytest.mark.anyiomarkers across 4 test files.asyncio_mode = "auto"already handles every async test, so they were redundant — and they were the thing introducing a second loop owner.pyproject.toml, next to the existing module-scope rationale, so it does not regress.No production code touched. Test count is unchanged at 114, and the
[asyncio]parametrization is gone from the IDs.Tests
Test (sqlite)andTest (redis)all green,114 passed.python:3.14.6-slim), both backends againstredis:7-alpine: 114 passed.ruff check/ruff format --checkclean.Risques
The real risk is a marker removal silently turning async tests into no-ops. Guarded against: collection still reports exactly 114 tests, and the Redis-backed run genuinely exercises the backend (it is what surfaced this bug in the first place).
Earlier attempt #4 was closed after its diagnosis was falsified; this supersedes it.