Fail CI on lingering timers (#89292)
parent
f62bb0e2ea
commit
9a784fddef
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue