Fail CI on lingering timers (#89292)

pull/89985/head
epenet 2023-03-20 10:20:19 +01:00 committed by GitHub
parent f62bb0e2ea
commit 9a784fddef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 26 additions and 4 deletions

View File

@ -9,6 +9,7 @@ import functools
import gc import gc
import itertools import itertools
import logging import logging
import os
import sqlite3 import sqlite3
import ssl import ssl
import threading import threading
@ -262,6 +263,22 @@ def expected_lingering_tasks() -> bool:
return False return False
@pytest.fixture(autouse=True)
def expected_lingering_timers() -> bool:
"""Temporary ability to bypass test failures.
Parametrize to True to bypass the pytest failure.
@pytest.mark.parametrize("expected_lingering_timers", [True])
This should be removed when all lingering timers have been cleaned up.
"""
current_test = os.getenv("PYTEST_CURRENT_TEST")
if current_test and current_test.startswith("tests/components"):
# As a starting point, we ignore components
return True
return False
@pytest.fixture @pytest.fixture
def wait_for_stop_scripts_after_shutdown() -> bool: def wait_for_stop_scripts_after_shutdown() -> bool:
"""Add ability to bypass _schedule_stop_scripts_after_shutdown. """Add ability to bypass _schedule_stop_scripts_after_shutdown.
@ -291,7 +308,9 @@ def skip_stop_scripts(
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def verify_cleanup( def verify_cleanup(
event_loop: asyncio.AbstractEventLoop, expected_lingering_tasks: bool event_loop: asyncio.AbstractEventLoop,
expected_lingering_tasks: bool,
expected_lingering_timers: bool,
) -> Generator[None, None, None]: ) -> Generator[None, None, None]:
"""Verify that the test has cleaned up resources correctly.""" """Verify that the test has cleaned up resources correctly."""
threads_before = frozenset(threading.enumerate()) threads_before = frozenset(threading.enumerate())
@ -311,16 +330,19 @@ def verify_cleanup(
tasks = asyncio.all_tasks(event_loop) - tasks_before tasks = asyncio.all_tasks(event_loop) - tasks_before
for task in tasks: for task in tasks:
if expected_lingering_tasks: if expected_lingering_tasks:
_LOGGER.warning("Linger task after test %r", task) _LOGGER.warning("Lingering task after test %r", task)
else: else:
pytest.fail(f"Linger task after test {repr(task)}") pytest.fail(f"Lingering task after test {repr(task)}")
task.cancel() task.cancel()
if tasks: if tasks:
event_loop.run_until_complete(asyncio.wait(tasks)) event_loop.run_until_complete(asyncio.wait(tasks))
for handle in event_loop._scheduled: # type: ignore[attr-defined] for handle in event_loop._scheduled: # type: ignore[attr-defined]
if not handle.cancelled(): if not handle.cancelled():
_LOGGER.warning("Lingering timer after test %r", handle) if expected_lingering_timers:
_LOGGER.warning("Lingering timer after test %r", handle)
else:
pytest.fail(f"Lingering timer after test {repr(handle)}")
handle.cancel() handle.cancel()
# Verify no threads where left behind. # Verify no threads where left behind.