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>
Add a cross-platform, standard-library-only Python package under
scripts/device-testing/ that replicates LibreMail's on-device performance-test
scenarios and logging capture, codifying the methodology run by hand on
2026-07-05 (Pixel 10 Pro XL).
Modules:
- breadcrumbs.py: a pure, unit-tested parser for the ImapPerf / MailReader /
Reader / MailBackfiller breadcrumbs, plus open-correlation that reproduces the
manual timing-tables.md figures exactly.
- adb.py: a safety-guarded adb wrapper -- an allow-list of adb subcommands and a
deny-list + assertions on shell commands. The only sanctioned app-state
mutation is clearing LibreMail's own cache/ (exact-match); no pm clear /
uninstall / data wipe, and no touching databases/ files/ shared_prefs/
datastore/ can be constructed.
- uidump.py: uiautomator XML parser + screen recognition (mailbox rows with the
cached "Available offline" flag, reader, and the keyguard / foreign-app guards).
- scenarios.py: cold-open, message-open (uncached), back-nav, prefetch A/B
(fetch-policy toggle) and cross-provider, each keyguard-guarded and driven
through the guarded wrapper.
- report.py + perf_harness.py: aggregates, a timing-tables.md renderer mirroring
the manual write-up, and the CLI (timestamped run dir with the raw logcat, a
filtered breadcrumb extract, and the tables). --dry-run prints the exact
command plan without touching device state.
Tests (stdlib unittest, 68 cases) validate the parser, guardrails, UI
recognition and report against the manual run's real captures (fixtures include
a verbatim perf-extract slice and the reader/lockscreen/alarm dumps). Track the
*.log fixture past the gitignore *.log rule via a scoped negation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>