Skip to content

test(plugins): stop the update check reaching the live registry mid-suite - #61

Merged
prokopto-dev merged 1 commit into
masterfrom
ao/nparseplus-5/fix-ci-segfault
Aug 10, 2026
Merged

test(plugins): stop the update check reaching the live registry mid-suite#61
prokopto-dev merged 1 commit into
masterfrom
ao/nparseplus-5/fix-ci-segfault

Conversation

@prokopto-dev

Copy link
Copy Markdown
Owner

What was failing

Semantic Release has been failing intermittently on master with exit code 139 — the Verify release candidate step (uv run pytest) segfaults partway through tests/ui, so Calculate version, update changelog, commit, tag, and push never runs. Two of the last four runs died this way (31349283155, 31349817083). The release tooling is fine; the test suite was crashing.

Why

tests/ui/test_plugin_window_rows.py::make_ui calls build_plugin_ui with default settings, which arms the post-launch plugin update check on a 12 s QTimer. Six tests, six timers, and nothing cancels them when the test that created them ends. They fire a quarter-minute later — inside whatever test is running by then — each starting a daemon thread that fetches the real registry over HTTPS.

CI's fault traceback caught it precisely there: five live threads in socket.getaddrinfo while the main thread sat in tests/ui/test_pluginmanager.py's fixture, garbage-collecting the previous test's Qt objects.

Thread 0x…  socket.getaddrinfo → httpcore connect_tcp → httpx stream
            → install.fetch_https_bytes → registry.fetch_indexes
            → updatecheck.check_for_updates → pluginbootstrap.py:171 in work
Current thread …  Garbage-collecting
            → composition.build_backend → tests/ui/test_pluginmanager.py:44 in host

Live sockets crossing another test's Qt teardown is what segfaults. Whether it lands depends on suite timing, which is why this only started biting as the suite grew — tests/ui now runs long enough for the timers to fire inside the session rather than after it.

Reproduced locally by running test_plugin_window_rows.py followed by a test that spins the event loop for 16 s: plugin-update-check threads appear after the tests that armed them have finished. With this change, zero.

The fix (tests only)

  • test_plugin_window_rows.py turns update_check off — the file is about Settings > Windows rows and never wanted the check.
  • tests/conftest.py grows a session-scoped guard that refuses any fetch_https_bytes call arriving without a transport. Session-scoped because the gap between tests is exactly where a leaked timer fires, and a function-scoped patch would already be unwound. The transport seam stays open, so patch_default_transport and the redirect/size-limit tests are untouched.

Tests

  • QT_QPA_PLATFORM=offscreen uv run pytest1994 passed, 2 deselected, and ~20 s faster (56 s vs 78 s) now that it no longer waits on live DNS.
  • uv run ruff check . / ruff format --check . clean.
  • Verified the guard bites: a registry fetch from inside tests/ now raises could not reach the registry: the test suite must not fetch … for real. Before this branch, that same call fetched the live index successfully — the suite really was talking to the internet.

Risk / notes

No product code changes, so test: — correctly no version bump. Merge with a merge commit so the commit survives for versioning.

…uite

Semantic Release has been failing intermittently on master with exit 139:
`uv run pytest` segfaulted partway through tests/ui, so the version/tag
step never ran. The release job is fine — the suite was crashing.

test_plugin_window_rows.py calls build_plugin_ui with the default
settings, which arms the post-launch plugin update check on a 12 s
QTimer. Six tests, six timers, none of them cancelled when the test that
created them ended. They fired a quarter-minute later, inside whatever
was running by then, each starting a daemon thread that fetched the real
registry over HTTPS. CI's fault traceback caught it exactly there: five
live threads in socket.getaddrinfo while the main thread was in
tests/ui/test_pluginmanager.py's fixture, garbage-collecting the
previous test's Qt objects. Sockets crossing another test's teardown is
what segfaulted; whether it lands depends on suite timing, which is why
this only started biting as the suite grew.

Two changes, both in tests:

- that helper turns update_check off, since the file is about window
  rows and never wanted the check;
- tests/conftest.py grows a session-scoped guard that refuses any
  fetch_https_bytes call arriving without a transport. Session-scoped
  because the gap between tests is precisely where a leaked timer fires,
  and a function-scoped patch would already be unwound. The transport
  seam stays open, so patch_default_transport and the redirect/size
  tests are untouched.

No product code changes. Full suite green offscreen (1994 passed) and
~20 s faster, since it no longer waits on live DNS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant