Fix CI hang in reconnect tests after STATE_CONNECTED scope change#23
Merged
SteveEasley merged 3 commits intoSteveEasley:mainfrom May 6, 2026
Merged
Conversation
PR SteveEasley#18 (Refresh device state after auto-reconnect) added two tests that wait on `connect_signal` (STATE_CONNECTED) after the initial connect. PR SteveEasley#19 (Dispatch STATE_CONNECTED only on auto-reconnect) then moved the STATE_CONNECTED dispatch out of `_connect()` and into `_reconnect()`. PR SteveEasley#19 updated other tests with the same pattern, but SteveEasley#19 was opened before SteveEasley#18 merged, so the two tests added by SteveEasley#18 weren't updated. Result: `await connect_signal.wait()` after initial connect waits forever, hanging CI for the full 6h job timeout. Every CI run on main since SteveEasley#18 has been cancelled this way (SteveEasley#19, SteveEasley#20, SteveEasley#21, v1.1.6, SteveEasley#22). Match the pattern used by other tests updated in SteveEasley#19: assert `state == STATE_CONNECTED` directly after the initial connect, and keep `connect_signal.wait()` only for the post-reconnect signal where STATE_CONNECTED is actually dispatched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
test_send_syslog_identification_uses_dunder_version (PR SteveEasley#22) patched `kaleidescape.device.__version__`, which doesn't exist as a module attribute — `device.py` does `from . import __version__` inside `_send_syslog_identification`, making it a function-local. mock.patch requires the attribute to exist on the target, so the test failed with AttributeError. Patch `kaleidescape.__version__` (the real location) instead. The function's `from . import __version__` re-reads it on each call, so the patched sentinel propagates through. This test never executed in CI before merge because PR SteveEasley#22's runs were cancelled at the 6h job timeout (the same hang fixed in this PR). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two tests added in PR SteveEasley#18 omitted the standard `await asyncio.sleep(0.1)` between the initial `connect()` and `emulator.stop()`. Without that pause, the emulator's `_handle_client` callback hasn't registered the new client yet, so `emulator.stop()` finds an empty `_clients` list, doesn't disconnect anything, and the test client never sees EOF. STATE_DISCONNECTED is never dispatched and `disconnect_signal.wait()` hangs. Match the existing pattern from `test_reconnect_during_event` and `test_reconnect_cancelled` (both use the same sleep for the same reason). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SteveEasley
approved these changes
May 6, 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
Every CI run on
mainsince PR #18 merged (#19,#20,#21,v1.1.6release,#22) was cancelled at the 6-hour job timeout. Job logs show pytest gets through 34 tests, then hangs forever on test #35 (test_reconnect_calls_on_reconnect).This PR fixes the hang and also fixes a separate failing test that the hang was hiding.
Root cause 1: hanging reconnect tests
The two tests added in PR #18 (
Refresh device state after auto-reconnect) had two bugs that combined to produce an infinite hang:Bug A: waiting for STATE_CONNECTED on initial connect
After PR #19 (
Dispatch STATE_CONNECTED only on auto-reconnect) moved the dispatch out of_connect(),await connect_signal.wait()after initialconnect()waits forever. PR #19 updated other tests to assertstate == STATE_CONNECTEDdirectly, but PR #19 was opened before PR #18 merged so the two tests added by #18 were not on its radar.Bug B: missing sleep before
emulator.stop()Other reconnect tests (
test_reconnect_during_event,test_reconnect_cancelled) includeawait asyncio.sleep(0.1)between the initialconnect()andemulator.stop()to let the emulator's_handle_clientcallback register the client. PR #18's two tests omit it. Without the pause,emulator.stop()finds an empty_clientslist, doesn't disconnect anything, the test client never sees EOF, andSTATE_DISCONNECTEDis never dispatched —disconnect_signal.wait()hangs.Fix
await connect_signal.wait()after the initial connect; assertstate == STATE_CONNECTEDdirectly (matches the pattern used by tests updated in PR Dispatch STATE_CONNECTED only on auto-reconnect #19).connect_signal.clear()calls — the signal is no longer set during initial connect.await asyncio.sleep(0.1)beforeemulator.stop()(matches the pattern used by other reconnect tests).Root cause 2: bad mock.patch target
Once the hang was fixed and CI ran to completion,
tests/test_d_device.py::test_send_syslog_identification_uses_dunder_version(added in PR #22) failed:The test patched
kaleidescape.device.__version__, butdevice.pydoesfrom . import __version__inside_send_syslog_identification, so it's a function-local, not a module attribute.mock.patchrequires the attribute to exist on the target.This test never executed in CI before merge because PR #22's runs were cancelled by the same hang.
Fix
Patch
kaleidescape.__version__(the actual location). The function'sfrom . import __version__re-reads it on each call, so the patched sentinel propagates through.