Files
JMR-devandClaude Opus 4.8 e436503eaf feat(scripts): device-testing perf harness
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>
2026-07-06 13:21:34 -05:00

109 lines
3.9 KiB
Python

# SPDX-License-Identifier: GPL-3.0-or-later
"""Tests for the uiautomator-dump parser and screen/keyguard recognition.
Fixtures are cleaned copies of the manual run's real dumps: the reader (``ui_reader.xml``),
a deskclock alarm (``ui_alarm.xml``) and the keyguard (``ui_lockscreen.xml``) are the
foreign-screen negatives the harness must skip; ``ui_mailbox.xml`` is a compact, structurally
faithful mailbox list.
"""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import uidump # noqa: E402
FIX = os.path.join(os.path.dirname(__file__), "fixtures")
APP = "org.libremail.app"
def load(name):
with open(os.path.join(FIX, name), encoding="utf-8") as fh:
return uidump.parse_dump(fh.read())
class TestBounds(unittest.TestCase):
def test_parse_bounds(self):
self.assertEqual(uidump.parse_bounds("[0,392][1344,617]"), (0, 392, 1344, 617))
def test_parse_bounds_none(self):
self.assertIsNone(uidump.parse_bounds(""))
self.assertIsNone(uidump.parse_bounds("not-bounds"))
class TestMailbox(unittest.TestCase):
def setUp(self):
self.root = load("ui_mailbox.xml")
def test_recognised_as_app_not_lockscreen(self):
self.assertEqual(uidump.foreground_package(self.root), APP)
self.assertFalse(uidump.is_lockscreen(self.root))
self.assertTrue(uidump.is_app_foreground(self.root, APP))
self.assertFalse(uidump.is_reader(self.root, APP))
def test_finds_three_message_rows(self):
rows = uidump.find_message_rows(self.root, APP)
self.assertEqual(len(rows), 3)
def test_row_centers_and_cached_flags(self):
rows = uidump.find_message_rows(self.root, APP)
self.assertEqual(rows[0].center, (672, 504))
self.assertFalse(rows[0].cached)
self.assertEqual(rows[1].center, (672, 732))
self.assertFalse(rows[1].cached)
# Row 2 carries the "Available offline" content-desc -> cached.
self.assertEqual(rows[2].center, (672, 988))
self.assertTrue(rows[2].cached)
def test_row_label_drops_monogram(self):
rows = uidump.find_message_rows(self.root, APP)
self.assertIn("github-actions[bot]", rows[0].label)
self.assertNotIn(" G ", rows[0].label) # single-letter avatar filtered out
def test_settings_entry_tappable(self):
node = uidump.find_by_text(self.root, "Settings")
self.assertIsNotNone(node)
anchor = node.first_clickable_ancestor()
self.assertIsNotNone(anchor)
self.assertEqual(anchor.center, (1014, 2728))
def test_top_bar_content_descs(self):
self.assertIsNotNone(uidump.find_by_content_desc(self.root, "Show folders"))
self.assertIsNotNone(uidump.find_by_content_desc(self.root, "Search"))
class TestReader(unittest.TestCase):
def setUp(self):
self.root = load("ui_reader.xml")
def test_recognised_as_reader(self):
self.assertTrue(uidump.is_app_foreground(self.root, APP))
self.assertTrue(uidump.is_reader(self.root, APP))
def test_progress_bar_signals_loading(self):
self.assertTrue(uidump.has_progress_bar(self.root))
def test_no_message_rows_in_reader(self):
self.assertEqual(uidump.find_message_rows(self.root, APP), [])
class TestForeignScreensAreGuarded(unittest.TestCase):
def test_lockscreen_detected(self):
root = load("ui_lockscreen.xml")
self.assertTrue(uidump.is_lockscreen(root))
self.assertFalse(uidump.is_app_foreground(root, APP))
self.assertFalse(uidump.is_reader(root, APP))
def test_alarm_is_foreign_app(self):
root = load("ui_alarm.xml")
self.assertEqual(uidump.foreground_package(root), "com.google.android.deskclock")
self.assertFalse(uidump.is_app_foreground(root, APP))
self.assertFalse(uidump.is_lockscreen(root))
self.assertFalse(uidump.is_reader(root, APP))
if __name__ == "__main__":
unittest.main()