Skip to content

Fail tests that leave tasks behind - #1907

Draft
zigpy-review-bot wants to merge 5 commits into
devfrom
zigpy-bot/fail-lingering-tasks
Draft

zigpy-review-bot wants to merge 5 commits into
devfrom
zigpy-bot/fail-lingering-tasks

Conversation

@zigpy-review-bot

Copy link
Copy Markdown
Collaborator

Merge order: #1900 first. This PR is stacked on #1900 and will be rebased onto dev once #1900 is merged. Until then, the diff below also shows #1900's commits; the only commit that belongs to this PR is the last one, "Fail tests that leave tasks behind".

verify_cleanup only logged lingering tasks after a test, so leaks went unnoticed. The topology scan loop that stop_periodic_scans() couldn't stop (#1904) had been visible for years only as one of those warnings, and since #1751, when verify_cleanup stopped running, not even that. Like Home Assistant's version, a lingering task now fails the test, as timers and leaked threads already do with #1900.

Changes

  • verify_cleanup cancels and awaits the test's leftover tasks and cancels its timers, then fails the test once, listing both. The message is now "Lingering tasks after test" instead of "Linger task after test".
  • make_app() registers every application it creates in a list like Home Assistant's INSTANCES. Unlike HA, which aborts the run on leftover instances, verify_cleanup shuts them down before its checks. It skips closing a database the test already closed, because ControllerApplication.shutdown() isn't idempotent and would log a spurious error. It also takes all applications out of the list first, and reports a failing shutdown as a test failure, so one test can't leak an application into the next.
  • With Make verify_cleanup run and close resources leaked by tests #1900's fixture teardown in place, 50 tasks lingered in 20 tests: 49 in 19 tests from applications started with make_app() and never shut down (startup, backup, TX power, channel move and watchdog tests in test_application.py, and one OTA test), and one from the OTA cluster in test_request_future_matching.
  • The app and app_mock fixtures are plain fixtures again, because the registry shuts their applications down.
  • test_request_future_matching waits, with a timeout, for the background tasks in which the OTA cluster handles its packets. CatchingTaskMixin's task set is shared by all objects, so it only waits for the tasks the test started.
  • Unlike Home Assistant, there is no expected_lingering_tasks opt-out. The suite is small enough to fix every leak, and an opt-out would only let new ones back in.

No library code changes.

Testing: the full suite passes, including with -W error::ResourceWarning -W error::pytest.PytestUnraisableExceptionWarning, in random order. A leaked task now fails both async and sync tests. With the plain fixtures but without the registry shutdown, 27 tests in test_application.py and tests/ota fail. The number of "Failed to disconnect from database" teardown warnings is unchanged from #1900.

`verify_cleanup` is a sync fixture that looked up the running event loop.
Sync fixtures run outside the loop under pytest-asyncio, so it always took
its early return and never checked anything: a test that leaked a thread
still passed. Like Home Assistant's version, it now uses
`pytest_asyncio.fixture` with `asyncio.get_event_loop()`.

With the fixture active, fix what it and `ResourceWarning`s turned up:
- the `app` and `app_mock` fixtures shut their application down, so its
  watchdog, backup and topology tasks no longer linger
- `with sqlite3.connect(...) as conn` only commits, so wrap the connections
  in `contextlib.closing()` as well
- `test_v4_to_v5_migration_bad_neighbors` read the migrated table through
  the first block's cursor, which only worked because that connection was
  never closed
- `test_dynamic_bounded_semaphore_multiple_event_loops` closes its event loops
When the second loop's `gather()` raises, the first `test_semaphore` task
is still sleeping. Closing a loop does not cancel its tasks, so cancel and
await them first.
`auto_kill_aiosqlite` was torn down before `verify_cleanup` and quietly
stopped leaked aiosqlite threads, so a leaked connection still passed in the
modules that use it. `verify_cleanup` now requests the fixture, which tears
it down afterwards: the leak fails the test in every module, and the thread
is still stopped so pytest can exit.

No test leaves a timer behind, so lingering timers now fail the test as in
Home Assistant instead of only being logged.
They only worked as plain `pytest.fixture`s because of `asyncio_mode = auto`.
Declare them like `verify_cleanup` so they don't depend on that setting.
`verify_cleanup` only logged lingering tasks, so leaks went unnoticed: the
topology scan loop that `stop_periodic_scans()` could not stop showed up
only as such a warning, and not at all while `verify_cleanup` did not run.
Fail the test instead, like timers and threads. Tasks and timers are
cleaned up first and reported together.

Nearly all lingering tasks came from applications that tests create with
`make_app()` and never shut down. `make_app()` now registers each
application in a list like Home Assistant's `INSTANCES`, but instead of
aborting the run, `verify_cleanup` shuts them down before its checks. It
does not close a database the test already closed, and reports a failing
shutdown without leaking applications into the next test. The `app` and
`app_mock` fixtures no longer need their own teardown, and
`test_request_future_matching` waits for the OTA cluster's packet handling.
@codecov

codecov Bot commented Oct 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.53%. Comparing base (b4543ff) to head (b1bb587).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #1907      +/-   ##
==========================================
+ Coverage   99.51%   99.53%   +0.02%     
==========================================
  Files          61       61              
  Lines       12773    12773              
==========================================
+ Hits        12711    12714       +3     
+ Misses         62       59       -3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code quality Improvement to code quality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant