Skip to content

test(calls): coverage for self-heal fix — retry re-arm, call_link_ready, async BT connect (tincan-cq5c7)#175

Merged
quad341 merged 2 commits into
mainfrom
feature/tincan-cq5c7
Jul 5, 2026
Merged

test(calls): coverage for self-heal fix — retry re-arm, call_link_ready, async BT connect (tincan-cq5c7)#175
quad341 merged 2 commits into
mainfrom
feature/tincan-cq5c7

Conversation

@quad341

@quad341 quad341 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

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_retry steady-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_ready capability wiring: new capability (distinct from call_setup_ready, which stays SELinux-module-scoped per tincan-r41sx) that reflects whether CallController currently has a bound VoiceCallManager — i.e., whether dialing is actually possible right now.
  • Async _bt_connect_device(): Connect() now drives a real org.bluez.Device1.Connect() using reply_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/main had 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 current origin/main, resolving the conflicts:

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

Closes tincan-cq5c7

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 5 [email protected]

quad341 and others added 2 commits July 5, 2026 08:44
…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
quad341 merged commit 62576dc into main Jul 5, 2026
1 check passed
@quad341
quad341 deleted the feature/tincan-cq5c7 branch July 5, 2026 16:12
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]>
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