Commit Graph
764 Commits
Author SHA1 Message Date
Jason Ross d3264db920 Merge branch 'main' into mergify/configuration-deprecated-update 2026-07-07 07:20:22 -05:00
Jason Ross 404c107aef Merge pull request #419 from JMR-dev/refactor-405-harness-coldfetch
refactor(scripts): fold cold-fetch pause-hook A/B into device-testing harness (#405)
2026-07-07 05:29:48 -05:00
JMR-devandClaude Opus 4.8 55f1f59e3d refactor(scripts): fold cold-fetch pause-hook A/B into device-testing harness (#405)
Bakes the proven 2026-07-06 cold-vs-warm pause-hook flow into
scripts/device-testing/ as a first-class, reproducible `cold-fetch-ab`
scenario, upstreaming the scratchpad driver.

- fetchgate.py: FETCH_GATE pause/resume/query helpers through the guarded
  adb wrapper, with ordered-broadcast read-back parsing (paused=[...]).
- scenarios.cold_fetch_ab: pre-arm halt -> detect sign-in (sync all
  breadcrumb) -> confirm halt (prefetch skipped) -> wait for header sync ->
  measure cold opens -> resume -> measure warm opens. ALWAYS resumes on exit
  (finally), even on error -- never leaves fetch paused.
- report.render_cold_fetch_ab: gate summary, cold/warm tables, cold-vs-warm
  delta, connect=0ms reuse proof, throttle signature.
- Portability (subsumes #392): file-based uiautomator dump (not /dev/tty),
  UTF-8 adb decode + PYTHONUTF8/console I/O, openMessage-breadcrumb readiness,
  row-selection hardening (skip non-message rows).

The pause hook is debug-build-only (#393/#395), so the scenario needs a debug
APK. Automated validation: mocked unittest coverage (adb/breadcrumbs/gate) for
the helpers and the A/B scenario incl. restore-on-error, plus a --dry-run path
exercised end-to-end through perf_harness.main. A full on-device run is a
follow-up. Dev-tooling only; no app/src changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 05:07:11 -05:00
Jason Ross 582d1f3077 Merge pull request #418 from JMR-dev/test-386-ratchet-floor
test(compose): re-ratchet JaCoCo line-coverage floor to 0.84 (#386)
2026-07-07 03:33:12 -05:00
JMR-dev 4464e3f5e4 test(compose): re-ratchet JaCoCo line-coverage floor to 0.84 (#386)
Final step of the Robolectric Compose epic (#373): now that batches
#376-384 have all landed and proven stable, measure the new whole-app
JVM line-coverage baseline and raise the no-regression floor to match.

Measured 87.89% line (7994/9095), up from 80.21% (4838/6032) when the
floor was last set. Floor moves 0.79 -> 0.84, a deliberately wider
~3.9% headroom (vs. the usual ~0.5-1%) for this first post-epic
measurement; the maintainer can tighten it further in a follow-up PR.
Docs (CLAUDE.md, preflight SKILL.md) updated to match.
2026-07-07 03:14:01 -05:00
Jason Ross 4f09efaf84 Merge pull request #395 from JMR-dev/feat-393-debug-fetch-gate
feat(debug): dev-only pause/halt mail-fetch hook for test harnesses (#393)
2026-07-07 02:46:14 -05:00
Jason Ross 85009ee88a Merge branch 'main' into feat-393-debug-fetch-gate 2026-07-07 02:26:28 -05:00
Jason Ross 2887516faa Merge pull request #417 from JMR-dev/test-384-robolectric-app-shell
test(compose): Robolectric JVM tests — app shell & lock gate host (#384)
2026-07-07 01:38:33 -05:00
JMR-devandClaude Opus 4.8 9f7ebdafb9 test(compose): Robolectric JVM tests — app shell & lock gate host (#384)
Batch 9/9 (final) of the Robolectric Compose JVM-test epic (#373).

- AppLockGateHostJvmTest: drives the app-lock gate host on the JVM via the
  v2 createComposeRule under Robolectric, covering the Unlocked / Checking /
  Locked render branches, the "content stays composed after re-lock" latch,
  and the no-FragmentActivity auth-error path. AppLockViewModel is mocked.
  Drops **/AppLockGateHost* from jacocoNonJvmTestableSurface.
- LibreMailAppJvmTest: covers the JVM-tractable parts of LibreMailApp.kt —
  LibreMailBottomBar, StartupCrashPrompt (+ its dialog buttons), and
  LibreMailApp's cold-start "hold until known" guards.
- LibreMailApp itself KEPT excluded (the acceptable exception noted in #384):
  its NavHost start destinations call hiltViewModel() and the graph needs
  owners a plain JVM compose rule can't surface, so graph-level nav stays on
  the instrumented OnboardingFlowTest. Documented in the jacoco list.

Instrumented LibreMailBottomBarTest / StartupCrashPromptTest stay as the
on-device E2E. JaCoCo floor unchanged (0.79); scoped line coverage 0.8426.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 01:15:07 -05:00
Jason Ross 97a51ed17d Merge pull request #416 from JMR-dev/test-383-robolectric-mailbox
test(compose): Robolectric JVM tests — mailbox screen + folder drawer (#383)
2026-07-07 01:11:42 -05:00
Jason Ross a1cf982a84 Merge branch 'main' into test-383-robolectric-mailbox 2026-07-07 00:53:06 -05:00
Jason Ross 5f69082ba3 Merge pull request #415 from JMR-dev/test-382-robolectric-compose-editor
test(compose): Robolectric JVM tests — compose editor screen (#382)
2026-07-07 00:43:33 -05:00
Jason Ross 4b6f206f2d Merge branch 'main' into test-382-robolectric-compose-editor 2026-07-07 00:23:13 -05:00
Jason Ross 190343bd84 Merge pull request #401 from JMR-dev/test-380-robolectric-settings
test(compose): Robolectric JVM tests for settings screens (#380)
2026-07-07 00:19:42 -05:00
JMR-devandClaude Opus 4.8 cd25544e07 test(compose): Robolectric JVM tests — mailbox screen + folder drawer (#383)
Convert the Paging 3 mailbox list + folder drawer to Robolectric JVM Compose
tests (batch 8/9 of umbrella #373) and drop them from jacocoNonJvmTestableSurface.

- MailboxScreenJvmTest drives the real MailboxScreen + MailboxViewModel over
  mocked repositories, feeding Paging via static PagingData.from flows (no real
  Room/Paging source, mirroring MailboxViewModelTest). Covers the no-accounts
  welcome fallback, populated list (sender/subject/snippet, offline badge,
  unified per-account labels + filter chips, drafts/outbox entries), the
  empty/loading/no-results states, search open/close, and the multi-select
  contextual action bar (overflow, archive/spam/delete confirms, move picker,
  archive-hidden-in-archive, disambiguated app-bar title).
- FolderDrawerJvmTest drives the callback-driven FolderDrawer: friendly role
  names, duplicate-name provider disambiguation + account-switch gap, folder
  taps, the multi-account switcher/dropdown, and the unread badge (incl. 99+ cap).
- Remove **/MailboxScreen* and **/FolderDrawer* from jacocoNonJvmTestableSurface;
  overall JVM line coverage 84.69% (floor unchanged at 0.79).

The instrumented MailboxScreenTest/FolderDrawerTest stay as the on-device E2E.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 00:08:04 -05:00
JMR-devandClaude Opus 4.8 c42f9a7e01 test(compose): Robolectric JVM tests for settings screens (#380)
Convert the settings screens/components to Robolectric JVM Compose tests
(umbrella #373, batch 5/9) and drop their globs from
`jacocoNonJvmTestableSurface`, so they count toward JaCoCo's JVM-testable
surface without an emulator.

New `src/test` Robolectric Compose tests (v2 createComposeRule, @Config sdk=36,
NATIVE graphics), mocking each ViewModel where needed:
- SettingsComponentsJvmTest (SectionHeader/SwitchRow/ClickRow/RadioRow/RetentionSection)
- SettingsScreenJvmTest (+ stateless ContactAutocompleteRow)
- AccountSettingsScreenJvmTest
- SignaturesScreenJvmTest
- SignatureEditScreenJvmTest

Line coverage of the newly-included files: SettingsComponents 100%,
SignatureEditScreen 100%, SignaturesScreen 97%, SettingsScreen 95%,
AccountSettingsScreen 84%. Overall scoped line coverage 86.2%. The instrumented
androidTest E2E stay; the JaCoCo floor is unchanged (re-ratchet is #386).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:57:05 -05:00
JMR-devandClaude Opus 4.8 b9cadde5bd test(compose): Robolectric JVM tests — compose editor screen (#382)
Port the instrumented ComposeScreenTest to a Robolectric JVM Compose
test (batch 7/9 of umbrella #373) so ComposeScreen's render + interaction
code counts toward JaCoCo's JVM-testable surface, and drop
`**/ComposeScreen*` from `jacocoNonJvmTestableSurface`.

ComposeViewModel is large (7 collaborators, several Context/Room-backed),
so it is mocked — mirroring AccountPickerScreenJvmTest / ManualSetup
ScreenJvmTest — with its state/accounts/finished flows stubbed so every
render/state branch is injectable. A RESUMED lifecycle owner (also the
back-press dispatcher owner) and a no-op ActivityResultRegistryOwner let
`collectAsStateWithLifecycle`, the BackHandler, and the attachment/inline
-image launchers compose on the JVM. The embedded RichTextBodyField renders
live; its toolbar accessibility labels and body-change plumbing are covered.

The instrumented ComposeScreenTest stays as the on-device E2E. JaCoCo floor
unchanged (0.79); overall line coverage 0.86.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:54:29 -05:00
Jason Ross 74a2cde7b1 Merge pull request #400 from JMR-dev/test-381-robolectric-reader
test(compose): Robolectric JVM tests for the reader screen (#381)
2026-07-06 23:51:44 -05:00
JMR-devandClaude Opus 4.8 15f4af864f test(compose): Robolectric JVM tests for the reader screen (#381)
Port the reader screen's chrome to a Robolectric JVM Compose test (umbrella

ReaderScreenJvmTest drives the real ReaderViewModel over a mocked
MailRepository/SettingsRepository via the v2 createComposeRule() — no emulator —
covering the top bar, star/delete/reply/reply-all/forward actions, the
attachment accordion + downloaded indicator, the attachment download-failure
snackbar, and the loading/plain-text/empty/error/remote-images-banner branches.

WebView caveat: the HTML body renders through HtmlBody, a hardened WebView that
Robolectric can only present as a non-rendering shadow, so the banner branch is
driven via an HTML message with a blank body (no HtmlBody call) and no
WebView-rendered HTML is asserted. HtmlBody.kt stays in scope, covered by its
existing HtmlBodyTest/InlineImageResolverTest. The instrumented ReaderScreenTest
stays as the on-device E2E. ReaderScreenKt lands at 94.3% line coverage; the
bundle rises to 82.7%, above the unchanged 0.79 floor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:27:34 -05:00
Jason Ross 9259591dc8 Merge pull request #397 from JMR-dev/test-379-robolectric-mail-lists
test(compose): Robolectric JVM tests — mail list screens drafts/outbox/reports (batch 4/9)
2026-07-06 23:15:26 -05:00
mergify[bot] 33a7ca1b7d ci(mergify): upgrade configuration to current format 2026-07-07 04:05:32 +00:00
Jason Ross 37008d622e Merge branch 'main' into test-379-robolectric-mail-lists 2026-07-06 22:57:06 -05:00
Jason Ross b4453f6991 Merge pull request #412 from JMR-dev/ci-409-mergify-phase1
ci(mergify): implement Phase 1 — serial merge queue (require-up-to-date KEPT ON)
2026-07-06 22:50:20 -05:00
JMR-devandClaude Opus 4.8 40b14d51cc ci(mergify): add Phase 1 serial merge queue (.mergify.yml)
Implements issue #409: a serial Mergify merge queue that supersedes the
hand-rolled poor-man's queue (autoupdate.yml + ci-trigger.yml +
traffic-control.yml, all already disabled_manually).

- queue_rules "default": batch_size 1 (serial, no batching), merge_method
  merge (merge commits, never squash/rebase), merge_conditions gate on
  check-success = "CI passed" + -draft + -conflict + label != broken.
- merge_queue.max_parallel_checks 1 (true serial; unambiguously
  require-up-to-date-compatible).
- priority_rules map P0..P9 labels (P0 = 10000 highest .. P9 = 1000).
- pull_request_rules queue action triggers auto-queueing (queue_conditions
  alone do NOT auto-queue per Mergify lifecycle docs).

require-up-to-date STAYS ON (Phase 1 is the only trilemma combo that keeps
the checkbox literally enabled AND preserves merge commits). No batching
(that is Phase 2 / #410). Single required gate stays "CI passed".

Validated: YAML parses and conforms to Mergify's published JSON schema
(negative-control confirmed). Merging this activates Mergify, so NO
auto-merge — must be reviewed first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:28:14 -05:00
Jason Ross 594c6167d3 Merge pull request #408 from JMR-dev/ci-407-mergify-spec
docs(ci): propose Mergify merge-queue integration spec (#407)
2026-07-06 22:08:34 -05:00
Jason Ross 6a5e86bb11 Merge branch 'main' into ci-407-mergify-spec 2026-07-06 22:08:23 -05:00
Jason Ross e4db3cadf6 Merge branch 'main' into test-379-robolectric-mail-lists 2026-07-06 21:59:00 -05:00
JMR-devandClaude Opus 4.8 0307df88a1 docs(ci): propose Mergify merge-queue integration spec (#407)
Investigate Mergify (free-for-OSS merge queue + batching + speculative checks)
as the right-way replacement for the hand-rolled traffic-controller
(autoupdate.yml + ci-trigger.yml + mothballed traffic-control.yml) and the
manual serial-bump grind, now that GitHub's native merge queue is org-only and
unavailable to a user account.

Proposal only — NO live .mergify.yml, nothing activates:
- docs/ci/mergify-integration-spec.md: how the queue coexists with the single
  `CI passed` gate; the require-up-to-date x merge-commits x batching trilemma
  and its resolution (Phase 1 serial keeps the rule literally; Phase 2 merge-batch
  moves the up-to-date GUARANTEE into the queue); P0-P9 -> priority_rules mapping;
  what it replaces; interaction with path-filter/sharding/wedge-diag; risks;
  phased adopt recommendation.
- docs/ci/mergify.yml.proposed: annotated, NOT-active proposed config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 21:55:51 -05:00
Jason Ross 41aed01544 Merge pull request #402 from JMR-dev/ci-399-e2e-path-filter
ci: skip the E2E matrix for test-only/docs PRs via a paths-filter + skip-tolerant gate (#399)
2026-07-06 21:54:25 -05:00
JMR-devandClaude Opus 4.8 98b90a19c3 test(compose): Robolectric JVM tests for mail list screens (#379)
Convert the VM-driven mail list screens (DraftsScreen, OutboxScreen,
ProblemReportsScreen) to Robolectric JVM Compose tests in the `test`
source set, driving each real ViewModel over a mocked MailRepository /
ReportStore + DiagnosticsCollector via the v2 createComposeRule() — no
emulator. Each test covers the empty/populated render states, item
rendering (subject/recipient/body, queued-vs-failed status, crash/manual
kind labels), and the interactions (open, delete, cancel, retry, create).

Drop the three now-JVM-covered globs from jacocoNonJvmTestableSurface so
the screens count toward the JaCoCo denominator; measured coverage is
DraftsScreen 100%, OutboxScreen 100%, ProblemReportsScreen 97%, and the
bundle line ratio rises to ~82.7% (floor 0.79 unchanged). The instrumented
androidTest E2Es (DraftsScreenTest / OutboxScreenTest /
ProblemReportsScreenTest) stay as the on-device tests.

Part of the Robolectric Compose umbrella (#373); mirrors the #375/#376
pattern (AddAnotherAccountScreenJvmTest, format-control JVM tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 21:40:27 -05:00
Jason Ross 7f1fb3ea43 Merge branch 'main' into ci-399-e2e-path-filter 2026-07-06 21:35:38 -05:00
Jason Ross b8e3b97377 Merge pull request #396 from JMR-dev/test-377-robolectric-onboarding-lock
test(compose): Robolectric JVM tests for onboarding & lock screens (#377)
2026-07-06 21:25:50 -05:00
Jason Ross c4607c2df1 Merge branch 'main' into test-377-robolectric-onboarding-lock 2026-07-06 21:05:23 -05:00
JMR-devandClaude Opus 4.8 e668330151 ci: skip the E2E matrix for test-only/docs PRs via a paths-filter + skip-tolerant gate (#399)
Add a cheap `changes` job (dorny/paths-filter v4, pinned SHA) that sets
e2e_needed=false only when EVERY changed file is in a safe allow-list
(app/src/test/**, **/*.md, docs/**, scripts/**, .claude/**); anything
else -- or any non-pull_request event -- defaults to true (conservative,
"err toward running E2E").

Gate `e2e` and `e2e-preview` on needs.changes.outputs.e2e_needed so the
whole matrix runs or skips together, and rewrite the `ci-passed` gate:
it now BLOCKS on changes!=success, any of traffic-control-tests /
static-analysis / debug-build / unit-tests !=success, or e2e/e2e-preview
==failure|cancelled -- while TOLERATING an intentional e2e/e2e-preview
'skipped'. So test-only/docs PRs go green on the fast gate, a real E2E
failure/cancel still blocks, and a broken filter (changes!=success)
still blocks. Branch protection ("CI passed") context is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 20:55:58 -05:00
Jason Ross d922b9a6ee Merge pull request #398 from JMR-dev/test-378-robolectric-account-setup
test(compose): Robolectric JVM tests for account setup screens (#378)
2026-07-06 20:54:48 -05:00
JMR-devandClaude Opus 4.8 3ea00a1ed9 test(compose): Robolectric JVM tests for account setup screens (#378)
Add Robolectric JVM Compose tests (umbrella #373, batch 3/9) for the
account-setup screens and drop them from `jacocoNonJvmTestableSurface` so
their render/interaction code counts toward the JVM-testable coverage surface:

- AccountPickerScreen (98.9% line)
- AppPasswordSetupScreen (98.7% line)
- ManualSetupScreen (98.5% line)

Each test drives the real screen via the v2 `createComposeRule()` under
RobolectricTestRunner with a mocked ViewModel (their own logic stays covered by
the ViewModel unit tests), a RESUMED LifecycleOwner for
`collectAsStateWithLifecycle`, a no-op ActivityResultRegistry for the Outlook
launcher, and a recording UriHandler for the app-password help links — covering
render, per-provider chrome, field/submit wiring, and the enabled/busy/error/
done branches. The instrumented androidTest E2Es stay as the on-device coverage.

The JaCoCo floor (0.79) is unchanged — the re-ratchet is the final #373 step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 20:34:21 -05:00
JMR-devandClaude Opus 4.8 799669d6a6 test(compose): Robolectric JVM tests for onboarding & lock screens (#377)
Add Robolectric JVM Compose tests (umbrella #373, batch 2/9) for the
stateless onboarding + lock screens, and drop each from
jacocoNonJvmTestableSurface so its render/interaction code now counts as
JVM-testable surface:

- LockScreen: locked title/body, optional error, unlock callback.
- WelcomeContent + OnboardingWelcomeScreen: render + add-account; the
  wrapper's NotificationPermissionEffect launcher is wired to a no-op
  ActivityResultRegistry so no system dialog is surfaced on the JVM.
- LicenseScreen: real bundled GPL text renders, Agree gated on
  scroll-to-end, Decline.
- ContactsAccessContent (skip/grant/request/rationale) plus the
  ContactsAccessScreen wrapper, driven by a mocked OnboardingViewModel.
- BatteryOptimizationScreen: offered vs. done states; Take me there marks
  the prompt handled and resolves the settings intent; Not now finishes.

Contacts/Battery use a tall @Config qualifier so their centered,
non-scrolling columns fit without the lower controls clipping. The
instrumented androidTest tests are kept (and remain the coverage for the
system back press, which the JVM compose rule cannot drive). JaCoCo floor
unchanged at 0.79 (the re-ratchet is the final #373 step).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 20:13:49 -05:00
JMR-devandClaude Opus 4.8 8f8608a430 feat(debug): dev-only pause/halt mail-fetch hook for test harnesses (#393)
The on-device perf harness cannot force a genuinely uncached body fetch: proactive
backfill (#12) and post-sync body prefetch (#88/#89) warm the cache before a test can
open a message. Add a debug-only, adb-reachable hook to pause proactive fetch so a real
uncached open can be measured.

Components:
- DebugFetchGate (src/main): thread-safe in-memory holder of paused FetchScopes
  (BACKFILL, PREFETCH; `all` alias). Defaults to not-paused; HEADER_SYNC and on-demand
  OPEN are never gateable.
- FetchGateReceiver (src/debug only): BroadcastReceiver registered in the debug manifest,
  driven by `adb shell am broadcast -a org.libremail.debug.FETCH_GATE -n .../FetchGateReceiver
  --es action <pause|resume|query> --es scope <backfill,prefetch|all>`. Returns the state as
  ordered-broadcast result data (paused=[...]) for a synchronous read-back.

Enforcement (each read guarded by BuildConfig.DEBUG so R8 strips it from release):
- BackfillWorker.doWork() entry -> skip-and-reschedule when BACKFILL is paused, mirroring
  the existing cache-lock deferral (covers periodic + backfillNow()).
- MailSyncer/MailBackfiller.prefetchIfEnabled -> early-return when PREFETCH is paused.
  openMessage / fetchBodyMarkingSeen / fetchAttachment are deliberately NOT gated.

Debug-only: receiver + <receiver> live wholly in src/debug; every gate read in main is
behind BuildConfig.DEBUG. Verified on assembleRelease that R8 strips DebugFetchGate /
FetchScope / FetchGateReceiver and the log strings from the release APK, and the merged
release manifest has no FETCH_GATE receiver.

PII-free AppLog breadcrumbs on pause/resume/query and on each gate-triggered defer/skip
(scope names only).

Tests: DebugFetchGateTest, BackfillWorkerTest / MailSyncerTest / MailBackfillerTest
enforcement cases, and FetchGateReceiverInstrumentedTest (ordered-broadcast -> gate ->
read-back; gated worker defers while an un-gated path runs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 20:13:22 -05:00
Jason Ross ae10a5ff3b Merge pull request #394 from JMR-dev/test-376-robolectric-richtext
test(compose): Robolectric JVM tests for rich-text format controls (#376)
2026-07-06 20:09:40 -05:00
Jason Ross c2ab0e3141 Merge branch 'main' into test-376-robolectric-richtext 2026-07-06 19:54:03 -05:00
JMR-devandClaude Opus 4.8 9bbfa2108a test(compose): Robolectric JVM tests for rich-text format controls (#376)
Port the instrumented ColorSwatchRow / FontPicker / FontSizePicker /
ParagraphAlignmentControl tests to Robolectric JVM Compose tests (v2
createComposeRule, @GraphicsMode NATIVE, @Config sdk=36) in the `test`
source set, and drop their four globs from `jacocoNonJvmTestableSurface`
so they count toward the JVM coverage metric. The instrumented tests stay.

Also fix a latent gap in the #375 infra: the JaCoCo agent skips classes
with no code-source location, which is exactly how Robolectric loads the
classes-under-test through its sandbox classloader — so Robolectric-only
Compose coverage recorded as zero (the PoC AddAnotherAccountScreen
included). `isIncludeNoLocationClasses = true` on the Test tasks makes
that coverage register; scoped bundle line coverage rises ~0.80 -> ~0.82.
Floor left at 0.79 (#386 re-ratchets).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 19:49:59 -05:00
Jason Ross cfd433af6f Merge pull request #367 from JMR-dev/fix-359-sqlcipher-16kb
fix(security): fail closed on cache-encryption load failure + StrongBox-back keys (#359)
2026-07-06 19:41:35 -05:00
Jason Ross 8689885964 Merge branch 'main' into fix-359-sqlcipher-16kb 2026-07-06 19:23:24 -05:00
Jason Ross 6213ee8901 Merge pull request #391 from JMR-dev/ci-389-sdk-setup-hardening
ci: retry + cache Android SDK/emulator setup to survive corrupt-zip sdkmanager failures (#389)
2026-07-06 19:22:02 -05:00
Jason Ross 8ac1d28dc8 Merge branch 'main' into ci-389-sdk-setup-hardening 2026-07-06 19:03:26 -05:00
JMR-devandClaude Opus 4.8 aa628831be ci: retry + cache Android SDK/emulator setup to survive corrupt-zip sdkmanager failures (#389)
The dominant merge-blocking flake was the "Set up Android SDK" step
(android-actions/setup-android v4.0.1) dying BEFORE the emulator starts:

    Wrong version in preinstalled sdkmanager
    Warning: ... preparing SDK package Android Emulator: Error reading Zip
    content from a SeekableByteChannel.
    Error: The process '.../sdkmanager' failed with exit code 1

Root cause: the action's default cmdline-tools version (20.0) rarely matches the
runner image's preinstalled one, so it logs "Wrong version in preinstalled
sdkmanager" and re-fetches cmdline-tools with NO checksum; it then runs its
default `sdkmanager tools platform-tools` install. Any of those downloads can be
a corrupt/truncated zip, which sdkmanager turns into an un-retried exit 1. v4.0.1
is the latest release, so this is fixed by configuration + hardening, not a bump.

Harden with verify -> reject -> retry, never trusting sdkmanager's exit code
alone, via a new stdlib-only helper .github/scripts/setup_android_sdk.py:

- bootstrap: download the pinned cmdline-tools zip, verify size + SHA-256
  (authoritative pin, cross-checked against Google's published SHA-1), and
  install it to $ANDROID_SDK_ROOT/cmdline-tools/20.0 -- the exact path
  setup-android probes first, so the action reuses the verified tree and never
  does its own unverified "Wrong version" re-download. A mismatch (corrupt OR
  wrong version) deletes the bad zip + any half-extracted dir and re-downloads.
- install: sdkmanager --install with retry + backoff; on a corrupt package zip it
  purges the partial/corrupt package dir (and sdkmanager's temp dirs) before
  retrying, forcing a fresh download instead of a re-read.
- setup-android now runs with packages: "" (no flaky tools/platform-tools
  install) and cmdline-tools-version: "14742923" (reuse the verified bootstrap).
- actions/cache restore + success-gated save so only a verified SDK is ever
  cached (integrity gates the cache); shrinks the re-download/corruption surface.

Applied to every SDK-setup job (debug-build, unit-tests, static-analysis, e2e
matrix, e2e-preview). Emulator BOOT logic, #372 API-37 sharding, and #388
diagnostics are untouched. Pure-logic helpers are unit-tested
(test_setup_android_sdk.py, run by the traffic-control-tests job).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 18:59:05 -05:00
Jason Ross 1053fa80f0 Merge pull request #375 from JMR-dev/robolectric-compose-scope
test(compose): Robolectric JVM Compose testing infra + PoC (#373)
2026-07-06 18:36:36 -05:00
Jason Ross 35b6b35d16 Merge branch 'main' into robolectric-compose-scope 2026-07-06 17:44:37 -05:00
Jason Ross 5a8c4e386e Merge branch 'main' into fix-359-sqlcipher-16kb 2026-07-06 16:56:27 -05:00
Jason Ross c04198a158 Merge pull request #388 from JMR-dev/ci-387-e2e-diagnostics
ci(e2e): capture logcat + emulator/system diagnostics across the E2E matrix (#387)
2026-07-06 16:55:52 -05:00