fix(test): stop interface microservice tests leaking threads on failure - #3769
Merged
Conversation
Tests called im.shutdown()/thread.join() only after their assertions, so a failed assertion left the microservice run, status and metrics threads alive for the rest of the pytest session. Combined with fixed 0.1s sleeps before asserting CONNECTED - too short on a loaded CI runner - this crashed the 3.12 job with SIGSEGV inside a later setup_system(). Register shutdown/join via addCleanup and poll for the expected state and log output instead of sleeping a fixed amount. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3769 +/- ##
==========================================
+ Coverage 79.09% 79.13% +0.03%
==========================================
Files 894 894
Lines 66865 66865
Branches 2551 2599 +48
==========================================
+ Hits 52890 52915 +25
+ Misses 13313 13290 -23
+ Partials 662 660 -2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Contributor
|
I have a fix for openc3/python/test/api/test_cmd_api.py::test_get_cmd_value_returns_command_values on my branch https://github.com/OpenC3/cosmos/pull/3767/changes#diff-5d5032335847f9c5d77d2185b7a3d3e853dfe1f5c031fc2a28717224ec8df64e |
mcosgriff
approved these changes
Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Problem
The Python 3.12 unit test job died with a segfault in this run:
Cause
Two coupled problems in
test/microservices/test_interface_microservice.py:Fixed sleeps before state assertions. Tests did
thread.start(),time.sleep(0.1), then asserted the interface state isCONNECTED. On a loaded runner 100 ms is not enough and the state is stillATTEMPTING. Three tests failed this way in that job:test_connect_handles_parameters,test_handles_a_clean_disconnect,test_run_does_not_write_status_after_cancel_thread_set.Cleanup only on the success path.
im.shutdown()andthread.join()were written after the assertions, so a failed assertion skipped them and leaked a live microservice into the rest of the session. The fault dump confirms it — five threads still running ininterface_microservice.py:742 run,:944 disconnect,microservice.py:275 _status_threadandmetric.py:119, long after those tests "finished".Those leaked threads keep touching
Systemwhile a latersetup_system()doesSystem.instance_obj = Noneand rebuilds. That madeadd_target'sprocess_fileraise, and formatting the resulting traceback segfaulted the interpreter.Fix
Two helpers, both following the
addCleanuppattern four tests in this file already used:start_microservice(im)— startsim.runand registersaddCleanup(thread.join, 5)+addCleanup(im.shutdown)(LIFO: shutdown signals stop, then join waits), so cleanup happens even when an assertion fails.wait_for_state(state)/wait_for_output(stdout, text)— poll with a 5s timeout instead of sleeping a fixed 0.1–0.5s, then assert, so the failure message still shows what was captured.Every test that constructs an
InterfaceMicroservicenow registersaddCleanup(im.shutdown)before its assertions, including the four that never start a run thread but still create handler and metrics threads.shutdown()is idempotent (shutdown_completeguard), so tests that need to assert on post-shutdown state still call it inline.Testing
uv run pytest test/microservices/test_interface_microservice.py— 18 passed (4.9s, down from ~11s of fixed sleeps)uv run pytest test/microservices/— 91 passeduv run ruff check/ruff format --check— clean['MainThread', 'Thread-1 (update_thread_body)', 'Thread-2 (_status_thread)', 'Thread-3 (run)', 'Thread-4 (run)']— and pytest hung at exitMainThreadonlyThe segfault itself is load-dependent and did not reproduce locally (that ordering passed 3/3 before this change), so this fixes the cause rather than a reproducible crash.
🤖 Generated with Claude Code