diff --git a/tests/conftest.py b/tests/conftest.py index 02095105..0534586b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ import re import sys import time +from typing import Set from unittest.mock import Mock import aiohttp.web @@ -700,9 +701,7 @@ def _no_asyncio_pending_tasks(event_loop): collection after every test, and check messages from `asyncio.Task.__del__`. This, however, requires intercepting all event-loop creation in the code. """ - - # See `asyncio.all_tasks()` implementation for reference. - before = {t for t in list(asyncio.tasks._all_tasks) if not t.done()} + before = _get_all_tasks() # Run the test. yield @@ -712,7 +711,22 @@ def _no_asyncio_pending_tasks(event_loop): event_loop.run_until_complete(asyncio.sleep(0)) # Detect all leftover tasks. - after = {t for t in list(asyncio.tasks._all_tasks) if not t.done()} + after = _get_all_tasks() remains = after - before if remains: pytest.fail(f"Unattended asyncio tasks detected: {remains!r}") + + +def _get_all_tasks() -> Set[asyncio.Task]: + """Similar to `asyncio.all_tasks`, but for all event loops at once.""" + i = 0 + while True: + try: + tasks = list(asyncio.tasks._all_tasks) + except RuntimeError: + i += 1 + if i >= 1000: + raise # we are truly unlucky today; try again tomorrow + else: + break + return {t for t in tasks if not t.done()}