test(calls): coverage for self-heal fix — retry re-arm, call_link_ready, async BT connect (tincan-cq5c7)#175
Merged
Merged
Conversation
…e modem re-arm, honest call readiness (tincan-c7b8g) Three gaps found in live testing after a phone drop overnight: - CallController gave up modem discovery permanently after a 30s fast backoff; now falls back to indefinite steady polling so a later reconnect self-heals without a daemon restart. - Daemon.Connect() only flipped internal state without touching BlueZ; now drives org.bluez Device1.Connect() (async — a sync call was found to block the daemon's single main loop for the whole connection attempt, verified live against a real device). - GetStatus() had no way to distinguish "SELinux module present" from "HFP link actually bound" — added a distinct call_link_ready capability reflecting CallController's live VoiceCallManager state, without repurposing the existing SELinux-scoped call_setup_ready. Filed tincan-cq5c7 (needs-tests) for coverage of the new decision paths.
tincan-c7b8g (3e5ddb7) fixed three reconnect gaps but shipped with no
dedicated coverage: steady-state modem re-arm, call_link_ready wiring,
and the new async BT connect helper. Existing suite (2377 tests) passed
unmodified, which only proves no regression -- it doesn't prove the new
decision logic is correct or will stay correct.
test_call_controller.py (+8):
- TestScheduleRetrySteadyStateRearm: fast backoff (1/2/4/8/15s) logs
"idling" once at exhaustion, then re-arms indefinitely every 30s
(asserted across 10+ further ticks, not just the first one) instead
of giving up permanently; a modem appearing after exhaustion still
binds, whether discovered via a later _discover_modem() tick or via
_on_modem_added.
- TestCallLinkReadyWiring: _bind_modem sets call_link_ready=True;
_on_modem_removed sets it False; removing an already-unbound path
is a no-op that doesn't touch set_capability.
test_dbus_service.py (+12):
- TestBtConnectDevice{MatchFound,NoMatch,FailuresSwallowed}: the new
_bt_connect_device() helper finds the right BlueZ Device1 by
address and calls Connect() with BOTH reply_handler and
error_handler -- the regression guard for the specific bug this
fix caused and fixed (a synchronous Device1.Connect() call blocked
tincand's single GLib main loop for 20s+ against a real iPhone).
GetManagedObjects()/SystemBus()/Connect() raising are all swallowed
per the "Connect() always succeeds unless AlreadyConnected" contract.
- TestConnectDrivesBtConnectDevice: Daemon.Connect() invokes the new
helper, but not when AlreadyConnected fires first; Connect() still
succeeds and still emits the Connected signal even if the real
(unmocked) helper hits a SystemBus() failure.
test_hfp_capability.py (+10):
- TestGetStatusCallLinkReady / TestSetCapabilityCallLinkReady: key is
always present, defaults False, set/unset emits CapabilityChanged.
- TestDisconnectResetsCallLinkReady: resets to False on Disconnect,
in direct contrast to call_setup_ready, which persists across
Disconnect (it reflects SELinux module presence, not BT/call
state -- see tincan-r41sx). One test asserts both behaviors side
by side across the same Disconnect() call so the distinction can't
silently erode.
Full suite: 2406 passed, 1 skipped, 0 failed.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
quad341
added a commit
that referenced
this pull request
Jul 5, 2026
PR #175 merged two locally-scoped imports that ruff I001 flags once this branch's CI-blocking lint step is in effect. Reproduces only in the PR merge-ref against current main, not on this branch in isolation.
quad341
added a commit
that referenced
this pull request
Jul 5, 2026
* lint: fix all ruff findings in tincand/tincan_gui/tests, make CI lint blocking 73 findings on main (ruff check tincand tincan_gui tests, line-length=99): 21 I001 unsorted-imports + 14 F401 unused-import (ruff --fix), 33 E501 line-too-long (hand-wrapped), 3 E402 module-import-not-at-top (moved to top of file). Two real bugs fixed among the findings: - tincan_gui/conversation_list.py: set_compose_new_enabled was defined twice (F811); the second definition silently won at runtime and had dropped the setAccessibleName() accessibility calls from the first. Merged into one method, keeping the runtime-winning tooltip strings and restoring the accessibility calls. - tincand/backends/ancs.py: the DBusException handler's fallback lambda closed over the except-bound name after Python deletes it (F821). Rewritten as an explicit conditional. CI's Lint (ruff) step no longer has continue-on-error, so lint failures now block the build. Co-authored-by: Claude <[email protected]> * chore: release gate PASS for tincan-12wwq (ruff lint fix + CI lint enforcement) * fix(tests): sort imports in test_call_controller.py (tincan-mcj9l) PR #175 merged two locally-scoped imports that ruff I001 flags once this branch's CI-blocking lint step is in effect. Reproduces only in the PR merge-ref against current main, not on this branch in isolation. --------- Co-authored-by: Claude <[email protected]>
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.
Summary
Adds test coverage for the self-healing phone reconnect behavior from tincan-c7b8g (commit 3e5ddb7), plus lands that fix itself — see Root cause below for why both are in this PR.
_schedule_retrysteady-state re-arm: once the 5-step fast backoff (1/2/4/8/15s) is exhausted, retries continue indefinitely at 30s instead of giving up, so a phone that's absent overnight still self-heals without a daemon restart.call_link_readycapability wiring: new capability (distinct fromcall_setup_ready, which stays SELinux-module-scoped per tincan-r41sx) that reflects whetherCallControllercurrently has a boundVoiceCallManager— i.e., whether dialing is actually possible right now._bt_connect_device():Connect()now drives a realorg.bluez.Device1.Connect()usingreply_handler/error_handler, instead of a synchronous call that was found to block tincand's single GLib main loop for 20s+ against a real iPhone.Root cause
While preparing this PR I found that
builder/tincan-c7b8g's fix commit (3e5ddb7) had never been pushed to origin or opened as a PR —origin/mainhad independently advanced 5 commits in the meantime (including #172 echo-cancellation and #174 remember-last-device), two of which touch the same production and test files this fix/test set touches. Rather than open a PR from a stale base, I rebased both commits (fix + new tests) onto currentorigin/main, resolving the conflicts:dbus_service.py: three trivial dict/frozenset conflicts where feat(calls): verify echo cancellation on every call — call_audio_aec capability #172'scall_audio_aecand this fix'scall_link_readyboth added sibling entries — kept both.test_call_controller.py/test_dbus_service.py: purely additive conflicts (both sides added new test classes at the same insertion point) — kept both._on_modem_removed(which predates this PR) also tears downcall_audio_aecvia_teardown_call_audio()(feat(calls): verify echo cancellation on every call — call_audio_aec capability #172), sotest_on_modem_removed_sets_call_link_ready_falseneededassert_any_callinstead ofassert_called_once_with— fixed and verified against the full merged suite.So this PR carries two commits: the original fix (rebased, no logic changes) and the new test coverage (rebased + one assertion fix for the above interaction).
Test plan
TestScheduleRetrySteadyStateRearm(5 tests),TestCallLinkReadyWiring(3 tests) intest_call_controller.py;TestBtConnectDeviceMatchFound/NoMatch/FailuresSwallowed,TestConnectDrivesBtConnectDevice(12 tests) intest_dbus_service.py.PYTHONPATH=. pytest tests/ -q→ 2446 passed, 1 skipped, 0 failed (includes feat(calls): verify echo cancellation on every call — call_audio_aec capability #172's and fix(daemon): remember last connected device — empty device_address is no longer fatal #174's own new tests alongside these).Closes tincan-cq5c7
🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 5 [email protected]