Fix hanging test suite by closing per-loop Tortoise contexts

pwo.async_test runs every test in a fresh event loop and TortoiseMixin
builds one TortoiseContext per loop. Dropping the previous context when
the loop changed orphaned its aiosqlite connections; their non-daemon
worker threads then blocked threading._shutdown forever, so the suite
printed OK but the interpreter never exited.

Add TortoiseMixin.aclose() and a tests.helpers.async_test wrapper that
closes the context in a finally on the test's own loop, and switch the
test modules over to it.
This commit is contained in:
2026-09-19 03:20:24 +00:00
parent af480bb9b7
commit 031d933204
10 changed files with 57 additions and 14 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
"""Test helpers package."""
from .asynctest import async_test
from .oidc import make_user, oidc_user, ws_users
__all__ = ["make_user", "oidc_user", "ws_users"]
__all__ = ["async_test", "make_user", "oidc_user", "ws_users"]
+32
View File
@@ -0,0 +1,32 @@
"""An ``async_test`` that also closes the app's Tortoise context.
``pwo.async_test`` runs every test in a fresh event loop (``asyncio.Runner``).
The app's :class:`~tavolo.tortoise_mixin.TortoiseMixin` builds one
``TortoiseContext`` per loop, so without an explicit close each test orphans
an aiosqlite connection whose non-daemon worker thread keeps the interpreter
alive after the suite reports "OK".
"""
from __future__ import annotations
import asyncio
from functools import wraps
from typing import Any, Callable, Coroutine
from tavolo.app import tortoise_mixin
def async_test(coro: Callable[..., Coroutine[Any, Any, None]]) -> Callable[..., None]:
"""Like ``pwo.async_test``, but close the Tortoise context afterwards."""
@wraps(coro)
def wrapper(*args: Any, **kwargs: Any) -> None:
async def run() -> None:
try:
await coro(*args, **kwargs)
finally:
await tortoise_mixin.aclose()
with asyncio.Runner() as runner:
runner.run(run())
return wrapper