Skip to content

test: cover coordination and persistence behavior - #32

Draft
guaje wants to merge 21 commits into
anasvhora284:masterfrom
guaje:testing/issue-12-connection-coordinator-preferences
Draft

guaje wants to merge 21 commits into
anasvhora284:masterfrom
guaje:testing/issue-12-connection-coordinator-preferences

Conversation

@guaje

@guaje guaje commented Aug 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • extract platform-independent connection-generation, retry, keepalive, and input-routing decisions into ConnectionCoordinator while retaining Android lifecycle and side effects in ConnectionService
  • add JVM coverage for connection state/effects, stale generations, unexpected versus user disconnects, keepalive timeout behavior, retries, and input enablement gates
  • make AppPreferences testable with an isolated DataStore and deterministic device names
  • migrate server-keyed fingerprint and transport settings to an IPv6-safe versioned Base64url format while preserving valid legacy records
  • cover defaults, updates, favorites, preference migrations, malformed records, onboarding fallback, and fingerprint normalization
  • add connected emulator coverage for the ConnectionService lifecycle (the one part JVM tests cannot reach) so patch coverage is honest and complete without any codecov.yml ignore entries

Test design

  • JVM tests use JUnit 4, Truth, Mockito, isolated temporary DataStore files, and explicit coroutine-scope cleanup; they assert public state and effects without services, emulators, network connections, hardware, or arbitrary sleeps
  • connected tests bind the real ConnectionService on the emulator and drive it against a loopback Input Leap server on the fixed port the service dials:
    • full handshake to Active, mouse/key routing, overlay toggle, abrupt server close → unexpected-disconnect retry → user disconnect
    • mid-handshake server close → Failed(HANDSHAKE) → retry → cancel via disconnect
    • transport failure with a throwing onConnectionFailed observer exercising the defensive retry path (TLS_ONLY proves no retry is scheduled)
    • untrusted TLS certificate rejection without a fingerprint callback
    • server silence driving the keepalive timeout disconnect
  • the fixtures reuse the JVM LoopbackServer and the app's own SelfSignedRsaCertificate, so no binary identity is committed; DataStore is reset through the app's own singleton before the service binds

Coverage reporting

  • No coverage exclusions anywhere. Kover reports every class; JaCoCo (android) reports every class; Codecov merges both sessions line-by-line (after_n_builds: 2), so a line counts as covered when either session hits it. The enforced 100% patch gate applies to every changed line regardless of which session covers it, and untested code stays visible — no package/class allow-lists in build.gradle.kts, no ignore entries in codecov.yml.
  • parsers.jacoco.partials_as_hits counts Kotlin inline/lambda mapping artifacts so the 100% patch target measures genuinely unexecuted lines.
  • ConnectionService's changed lines are exercised by the android-coverage emulator job; ConnectionCoordinator, ConnectionStateMachine, RetryDelayCalculator, and AppPreferences are exercised by the jvm session; MainViewModel/HiddenInputManager JVM tests (previously hidden by package-wide excludes) report again.
  • Patch coverage: 100% of changed lines (0 misses, 0 partials) — re-confirmed on the rebased head, with no ignore entries and no Kover/build.gradle.kts exclusions.

Validation

  • git diff --check
  • ./gradlew :app:testDebugUnitTest :uhid-server:test (not run locally: no Java Runtime is installed in this environment)
  • CI, on the rebased head: fast-jvm ✓, android-coverage ✓ (connected suite incl. ConnectionServiceLifecycleTest), codecov/patch ✓ 100.00% — https://github.com/anasvhora284/input-leaf/actions/runs/35273559965

Readiness

Closes #12

Part of #8

Summary by Sourcery

Centralize connection decisions and expand JVM and emulator coverage across connection behavior, persistence, and service lifecycle paths.

New Features:

  • Extract platform-independent connection lifecycle, retry, keepalive, generation, and input-routing decisions into a reusable ConnectionCoordinator.
  • Support IPv6-safe, versioned Base64url persistence for server fingerprints and transport modes while retaining compatibility with legacy records.
  • Make AppPreferences independently testable with isolated DataStore instances and deterministic device-name fallbacks.

Bug Fixes:

  • Prevent stale connection callbacks and user disconnects from triggering state changes or unwanted retries.
  • Handle malformed preference records, blank values, legacy onboarding settings, and observer failures safely.
  • Ensure connection lifecycle edge cases such as TLS rejection, handshake failure, keepalive timeout, and HID mouse detachment are handled correctly.

Enhancements:

  • Keep Android lifecycle and framework side effects in ConnectionService while applying coordinator-generated effects.
  • Improve preference normalization for favorites, fingerprints, transport modes, and screen names.

CI:

  • Remove broad Codecov coverage exclusions and retain complete JVM and Android coverage reporting with merged patch coverage enforcement.

Documentation:

  • Document connected emulator coverage for the real ConnectionService and the no-exclusion, merged JVM/Android coverage model.

Tests:

  • Add JVM tests covering coordinator state transitions, stale generations, disconnect behavior, retries, keepalive handling, input gates, and preference persistence and migrations.
  • Add connected emulator tests covering real service handshakes, input routing, retries, TLS rejection, keepalive timeout, configuration changes, and HID lifecycle behavior.

@sourcery-ai

sourcery-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR separates platform-independent connection coordination from Android side effects, adds comprehensive JVM coverage for connection and persistence behavior, and makes preference records IPv6-safe through versioned Base64url encoding with legacy compatibility.

Sequence diagram for coordinated connection and input routing

sequenceDiagram
    participant Service as ConnectionService
    participant Coordinator as ConnectionCoordinator
    participant State as ConnectionStateMachine
    participant Connection as InputLeapConnection
    participant Android as AndroidEffects

    Service->>Coordinator: beginConnection()
    Coordinator-->>Service: generation
    Service->>Coordinator: onConnecting(generation, serverIp)
    Coordinator->>State: onConnecting(serverIp)
    Service->>Connection: connect
    Service->>Coordinator: onConnected(generation, serverIp, screenName)
    Coordinator->>State: onHandshaking(serverIp)
    Coordinator->>State: onIdle(serverIp, screenName)
    Connection-->>Service: event
    Service->>Coordinator: onEvent(generation, event)
    Coordinator-->>Service: Effect
    Service->>Android: applyEffects(effects)

    alt keepalive timeout
        Service->>Coordinator: onKeepAliveMiss(generation)
        Coordinator->>State: onDisconnected()
        Coordinator-->>Service: CloseConnection, HideCursor, RestoreIme
        Service->>Android: applyEffects(effects)
    end
Loading

Entity relationship diagram for versioned server preference records

erDiagram
    PREFERENCES ||--o{ SERVER_RECORD : stores
    SERVER_RECORD {
        string format "v2"
        string server_base64url "IPv6-safe key"
        string value_base64url
        string legacy_record "optional compatibility input"
    }
    PREFERENCES {
        string tls_fingerprints
        string server_transport_modes
    }
Loading

File-Level Changes

Change Details Files
Extract connection decision-making from the Android service into a testable coordinator.
  • Centralize generation validation, state transitions, retry decisions, disconnect handling, keepalive timeout handling, and input-routing gates.
  • Represent Android work as effects and have the service apply those effects to connections, overlays, IME, retries, and input injection.
  • Update service lifecycle, event-loop, and keepalive paths to delegate through the coordinator.
app/src/main/java/com/inputleaf/android/service/ConnectionCoordinator.kt
app/src/main/java/com/inputleaf/android/service/ConnectionService.kt
app/src/test/java/com/inputleaf/android/service/ConnectionCoordinatorTest.kt
Make preference storage injectable and strengthen server-keyed record persistence.
  • Inject DataStore and deterministic device-name providers for isolated JVM testing while preserving the Context constructor.
  • Retain defaults, onboarding fallback, favorites, transport-policy migration, and preference update behavior.
  • Replace delimiter-ambiguous records with versioned v2 Base64url entries that safely encode IPv6 keys, while reading and normalizing valid legacy fingerprints and transport records and ignoring malformed data.
app/src/main/java/com/inputleaf/android/storage/AppPreferences.kt
app/src/test/java/com/inputleaf/android/storage/AppPreferencesTest.kt
Add JVM tests covering coordinator behavior and preference compatibility.
  • Assert public state and effects for connection transitions, stale generations, retries, user versus unexpected disconnects, keepalive misses, and input enablement.
  • Exercise isolated temporary DataStore files, deterministic defaults, updates, favorites, onboarding migration, malformed records, IPv6 keys, canonical rewrites, and removals.
app/src/test/java/com/inputleaf/android/service/ConnectionCoordinatorTest.kt
app/src/test/java/com/inputleaf/android/storage/AppPreferencesTest.kt

Assessment against linked issues

Issue Objective Addressed Explanation
#12 Extract connection-generation, retry, keepalive, disconnect, and keyboard/mouse input-routing decisions into an Android-independent ConnectionCoordinator while retaining Android lifecycle and side effects in ConnectionService. ✅
#12 Add JVM tests covering coordinator state and effects, stale connection generations, user versus unexpected disconnects, retry and keepalive behavior, retry cancellation, and input enablement gates. ✅
#12 Make AppPreferences independently testable and cover defaults, updates, favorites, onboarding fallback, malformed data, fingerprint normalization, migration, and IPv6-safe versioned persistence for fingerprints and transport preferences. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@guaje
guaje force-pushed the testing/issue-12-connection-coordinator-preferences branch from d980aec to 8dd942c Compare September 1, 2026 13:35
@codecov

codecov Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@guaje
guaje force-pushed the testing/issue-12-connection-coordinator-preferences branch from 7143fae to f3a0367 Compare September 3, 2026 17:23
@guaje guaje self-assigned this Sep 3, 2026
@anasvhora284

anasvhora284 commented Sep 10, 2026 •

Copy link
Copy Markdown
Owner

Hello @guaje,

I hope you're fine and healthy,
I have been noticing this PR & PR #40 are still marked as draft for more then 1-2 weeks, Let me know if you need any help from my side & also update me when they are ready for a review & to be merge to main merge?

i'll be doing some of my changes until you update/reply to this message. So you may have to rebase to master

Thanks for your valuable time & efforts.

@guaje

guaje commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Hi @anasvhora284,

I hope you're fine and healthy, I have been noticing this PR & PR #40 are still marked as draft for more then 1-2 weeks, Let me know if you need any help from my side & also update me when they are ready for a review & to be merge to main merge?

Thanks for the kind message. I was taking some time off, but I'm back and already finishing the final details of the PRs, starting with PR #40.

i'll be doing some of my changes until you update/reply to this message. So you may have to rebase to master

Sure thing. I'll rebase if needed.

@anasvhora284

Copy link
Copy Markdown
Owner

Hi @guaje,

I was taking some time off

I didn’t realize you were taking some time off, apologies! Hope you had a great break and got some good rest. 😊
Welcome back! Let me know how your holidays went when you get a chance.
Also ping me if you need any help from my side.

@guaje
guaje force-pushed the testing/issue-12-connection-coordinator-preferences branch from 37ce4b3 to cc09c13 Compare September 17, 2026 20:53
@anasvhora284

Copy link
Copy Markdown
Owner

Hi @guaje,

Hope you're doing well! First of all, thanks for the work you've put into PR #32. The coordinator extraction and the additional test coverage look really good.

I wanted to check with you about the merge order between this PR and PR #44 (release/1.4.2).

I did a dry-run comparison and found quite a bit of overlap between the two branches:

So, to avoid holding up the 1.4.2 fixes, I was thinking of merging PR #44 into master first and publishing the release, then rebasing PR #32 on top of the updated master.

That should give PR #32 a clean baseline to work from and also avoid any unnecessary conflicts around the uhid-server changes that are being removed in #44.

Does this order work for you, or would you prefer handling the integration differently? Hoping to get your opinion on this.

Thanks again for all the work on this!

@guaje

guaje commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Hi @anasvhora284 ,

I wanted to check with you about the merge order between this PR and PR #44 (release/1.4.2).

Thanks for all the work in PR #44! It seems introducing native kernel-level UHID hardware keyboard and mouse emulation was the right call, and you tackled many issues at once.

So, to avoid holding up the 1.4.2 fixes, I was thinking of merging PR #44 into master first and publishing the release, then rebasing PR #32 on top of the updated master.

I completely agree. I'll do a quick review of PR #44, given that neither Sourcery nor Copilot was able to review it. I hope you don't mind.

That should give PR #32 a clean baseline to work from and also avoid any unnecessary conflicts around the uhid-server changes that are being removed in #44.

Yes, I'll rebase this PR once PR #44 gets merged.

Does this order work for you, or would you prefer handling the integration differently? Hoping to get your opinion on this.

Yes, it does, and it completely makes sense.

Thanks again for all the work on this!

Thank you for tackling 2 of my issues so cleverly.

@anasvhora284

Copy link
Copy Markdown
Owner

Hello @guaje,

I completely agree. I'll do a quick review of PR #44, given that neither Sourcery nor Copilot was able to review it. I hope you don't mind.

Sure, I don't mind.
Please review #44, it might take time as there are so many file changes.

@guaje

guaje commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

Note for the rebase onto post-1.4.2 master — three pieces of decision logic live inline in ConnectionService.kt after #44; per this PR's own thesis (and the restored RetryDelayCalculator's KDoc: "logic inside a Service method is unmeasurable"), the rebase should re-extract them as part of the coordinator move rather than preserving them in place:

  • ConnectAttemptOutcome + shouldClearActiveSession — appended at the bottom of the Service file; master previously had these as service/ShizukuActiveSessionPolicy.kt with their own tests.
  • HID_MOUSE_IDLE_DETACH_MS + scheduleHidMouseIdleDetach() / cancelLeaveDebounce() (new in Release 1.4.2: UHID HID input, overlay cursor, and pointer compensation #44) — idle-timer decision logic with generation guards, written into the Service; a 30 s timer is otherwise only exercisable on the emulator job, which docs/TESTING.md requires to stay smoke-sized. Extraction with an injected clock/generation makes it JVM-testable.

Carrying these into #32 avoids filing them as separate upstream issues and avoids reworking #44's new code later.

@anasvhora284

Copy link
Copy Markdown
Owner

@guaje,

How are you? I hope everything’s going well on your end!

I don’t want to rush you, but whenever you get some free time, could you please share an update on this PR?

I’ve also asked you to review PR #46, so whenever you have the time, please take a look at both.

No pressure at all! Please take care of yourself and focus on work and life first. I’d appreciate it whenever you’re free and feeling up to it.

@guaje

guaje commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Hi @anasvhora284!

How are you? I hope everything’s going well on your end!

Everything is good on my end! Thanks for asking. I hope everything is going well for you, too.

I don’t want to rush you, but whenever you get some free time, could you please share an update on this PR?

Sure thing! I'll rebase the PR and see whether we can merge it this week.

I’ve also asked you to review PR #46, so whenever you have the time, please take a look at both.

It'll be my pleasure.

@anasvhora284

Copy link
Copy Markdown
Owner

Hi @anasvhora284!

How are you? I hope everything’s going well on your end!

Everything is good on my end! Thanks for asking. I hope everything is going well for you, too.

I don’t want to rush you, but whenever you get some free time, could you please share an update on this PR?

Sure thing! I'll rebase the PR and see whether we can merge it this week.

I’ve also asked you to review PR #46, so whenever you have the time, please take a look at both.

It'll be my pleasure.

Thank you so much @guaje

guaje added 12 commits October 1, 2026 10:29
…artials

Narrow the Kover exclusions so ConnectionCoordinator, ConnectionStateMachine,
RetryDelayCalculator, and AppPreferences reach the jvm Codecov report: they are
plain JVM logic with dedicated unit tests, and excluding them makes the merged
patch status count their tested lines as misses. Keep only the Android framework
adapters excluded (ConnectionService, CursorOverlayService, NotificationHelper);
ConnectionService is reported by the android-coverage emulator job instead.

Re-add parsers.jacoco.partials_as_hits so Kotlin inline/lambda mapping artifacts
do not defeat the 100% patch target.
ConnectionService is excluded from JVM coverage, so its framework effects are
only reportable from the emulator. Add connected tests that bind the real
service and play the server half of the Input Leap protocol on the fixed port
the service always dials:

- full handshake to Active, input routing (mouse abs/rel, key), overlay toggle,
  abrupt server close, unexpected-disconnect retry, and user disconnect
- mid-handshake server close reporting Failed(HANDSHAKE), retry, and cancel
- transport failure with a throwing onConnectionFailed observer covering the
  defensive retry path, with TLS_ONLY proving no retry is scheduled
- untrusted TLS certificate rejection without a fingerprint callback
- server silence driving the keepalive timeout disconnect

The fixtures mirror the JVM LoopbackServer and reuse the app's own
SelfSignedRsaCertificate for the TLS listener, so no binary identity needs
committing. DataStore is reset through the app's own singleton before the
service binds.
The silence test's second wait matched Idle immediately because the state was
already Idle after the handshake, so the test passed without letting the
keepalive monitor fire its four missed polls — leaving the monitor's
disconnect lines uncovered. Require an event well after the initial Idle
(client-side close or a retry-caused reconnection) before proceeding.
guaje added 4 commits October 1, 2026 10:31
Matching Idle in the silence wait exits immediately because the connection is
Idle after the handshake, so the keepalive monitor never reached its fourth
missed poll. Wait for an actual Disconnected transition instead; the close
race may reconnect, so extend the deadline to cover a second cycle.
No class reports selectively anymore. The jvm session covers plain JVM logic
and the android-coverage emulator session covers the framework adapters;
Codecov merges both line-by-line, so the enforced 100% patch gate needs no
package/class allow-lists and untested code stays visible.
…cycleTest

The old name stutters and reads as a test of a nonexistent
ConnectionServiceConnection class. The new name states the subject
(ConnectionService) and the behavior under test (its connection lifecycle),
matching the name-classes-after-the-subject convention.
@guaje
guaje force-pushed the testing/issue-12-connection-coordinator-preferences branch from cc09c13 to 95c02b4 Compare October 1, 2026 14:35
@guaje
guaje force-pushed the testing/issue-12-connection-coordinator-preferences branch from 95c02b4 to 9752b31 Compare October 1, 2026 14:50
@guaje
guaje force-pushed the testing/issue-12-connection-coordinator-preferences branch from b7a8e98 to 6d35f92 Compare October 1, 2026 15:19
@guaje
guaje force-pushed the testing/issue-12-connection-coordinator-preferences branch 2 times, most recently from caa975f to b522fe5 Compare October 1, 2026 17:36
@guaje
guaje force-pushed the testing/issue-12-connection-coordinator-preferences branch from b522fe5 to a37dae5 Compare October 1, 2026 18:06
@guaje
guaje force-pushed the testing/issue-12-connection-coordinator-preferences branch from a37dae5 to e892eb7 Compare October 1, 2026 18:47
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.

Testing plan 4/5: Cover coordination and persistence behavior

2 participants