Complete Android adaptive and accessibility hardening (#105)
Android ARM verification / rust (push) Failing after 50m4s
Android ARM verification / android (push) Failing after 9s
Dependency security audit / rustsec (push) Failing after 22s

This commit is contained in:
hugo committed 2026-10-15 11:44:31 +02:00
1 parent f4a6edbc68
commit 45d78aa5b3
33 files changed
+4596 -1376

No files matched your search

+80 -14
View File
@@ -251,8 +251,12 @@ PERFORMANCE_BUDGETS = {
"editor_update_p95_us": 32000,
"cache_rebuild_ms": 15000,
"qr_analysis_ms": 5000,
"tree_1000_navigation_ms": 3000,
"tree_1000_search_ms": 5000,
"totp_cached_open_p95_us": 1000000,
"totp_warm_refresh_p95_us": 3000000,
"provider_prepare_p95_us": 1000000,
"provider_authenticated_fill_ms": 5000,
"tree_10000_navigation_ms": 3000,
"tree_10000_search_ms": 5000,
}
@@ -270,15 +274,37 @@ def performance_metrics(result):
return values
def emulator_ready(user_state, power_state, trust_state):
return (user_state == "RUNNING_UNLOCKED" and
re.search(r"^\s*mWakefulness=Awake$", power_state, re.MULTILINE) is not None and
re.search(r'^\s*User .*\(id=0,.*deviceLocked=(?:0|false)\b', trust_state, re.MULTILINE) is not None)
def instrumentation(command, package, arguments=(), target=None):
try:
return command("shell", "am", "instrument", "-w", "-r", *arguments,
package + "/androidx.test.runner.AndroidJUnitRunner")
finally:
try:
command("shell", "am", "force-stop", package)
finally:
if target is not None and target != package:
command("shell", "am", "force-stop", target)
def emulator(serial):
adb = SDK / "platform-tools/adb"
def command(*args):
return run(adb, "-s", serial, *args, capture=True).stdout.strip()
return run(adb, "-s", serial, *args, capture=True, timeout=900).stdout.strip()
abi = command("shell", "getprop", "ro.product.cpu.abi")
if abi not in ABIS:
raise RuntimeError("Only ARM emulators are in scope")
if command("shell", "getprop", "ro.kernel.qemu") != "1":
raise RuntimeError("Physical devices are outside validation scope")
if not emulator_ready(command("shell", "am", "get-started-user-state", "0"),
command("shell", "dumpsys", "power"),
command("shell", "dumpsys", "trust")):
raise RuntimeError("Wake and unlock the Android emulator before starting instrumentation")
api = int(command("shell", "getprop", "ro.build.version.sdk"))
try:
pages = int(command("shell", "getconf", "PAGE_SIZE"))
@@ -295,21 +321,37 @@ def emulator(serial):
# Android's test runner and Rust fixtures cover typed errors, callbacks and cancellation.
suites = [("app", "de.rfc1437.ironstorage.test", None),
("core", "de.rfc1437.ironstorage.core.test", None),
("rust-bridge", "de.rfc1437.ironstorage.rustbridge.test", None)]
("rust-bridge", "de.rfc1437.ironstorage.rustbridge.test", None),
("platform", "de.rfc1437.ironstorage.platform.test",
"de.rfc1437.ironstorage.platform.AndroidQrCameraTest,"
"de.rfc1437.ironstorage.platform.AndroidKeystoreTest#deviceUnlockedRoundtripReplaceDeleteAndTamper,"
"de.rfc1437.ironstorage.platform.AndroidKeystoreTest#unsupportedUnlockedDeviceFlagDoesNotCreateAKey")]
native_timings = []
journey_timings = {}
for module, package, _ in suites:
for module, package, classes in suites:
test_apk = next((ROOT / "android" / module / "build/outputs/apk/androidTest/debug").glob("*.apk"))
command("install", "-r", test_apk)
if module == "rust-bridge":
for _ in range(5):
command("shell", "am", "force-stop", package)
measured = command("shell", "am", "instrument", "-w", "-r", "-e", "class", "de.rfc1437.ironstorage.bridge.NativeLoadPerformanceTest", package + "/androidx.test.runner.AndroidJUnitRunner")
measured = instrumentation(command, package, ("-e", "class", "de.rfc1437.ironstorage.bridge.NativeLoadPerformanceTest"))
samples = [int(line.split("=", 1)[1]) for line in measured.splitlines() if line.startswith("INSTRUMENTATION_STATUS: ironstorage_native_load_ns=")]
if len(samples) != 1 or "OK (1 test)" not in measured:
raise RuntimeError("Native cold-load measurement failed")
native_timings.extend(samples)
result = command("shell", "am", "instrument", "-w", "-r", package + "/androidx.test.runner.AndroidJUnitRunner")
camera_granted = None
if module == "platform":
camera_granted = "android.permission.CAMERA: granted=true" in command("shell", "dumpsys", "package", package)
if not camera_granted:
command("shell", "pm", "grant", package, "android.permission.CAMERA")
arguments = ["-e", "class", classes] if classes else []
print(f"ARM API {api}, {pages}-byte pages: invoking {module} instrumentation", flush=True)
try:
result = instrumentation(command, package, arguments,
"de.rfc1437.ironstorage" if module == "app" else package)
finally:
if camera_granted is False:
command("shell", "pm", "revoke", package, "android.permission.CAMERA")
if "FAILURES!!!" in result or "INSTRUMENTATION_FAILED" in result or "OK (" not in result:
local = ROOT / "android/build/local-validation"
local.mkdir(parents=True, exist_ok=True)
@@ -320,8 +362,7 @@ def emulator(serial):
raise RuntimeError("ARM emulator instrumentation failed (raw output retained locally only)")
if module == "core":
journey_timings = performance_metrics(result)
# Release instrumentation-owned activities and native owners before real app starts.
command("shell", "am", "force-stop", package)
print(f"ARM API {api}, {pages}-byte pages: {module} instrumentation passed", flush=True)
# No activity state or application data is deleted between process recreation runs.
timings = []
for _ in range(5):
@@ -364,11 +405,15 @@ def startup_metrics(data):
def performance(serial, profiles=False):
adb = SDK / "platform-tools/adb"
def command(*args):
return run(adb, "-s", serial, *args, capture=True).stdout.strip()
return run(adb, "-s", serial, *args, capture=True, timeout=900).stdout.strip()
abi = command("shell", "getprop", "ro.product.cpu.abi")
api = int(command("shell", "getprop", "ro.build.version.sdk"))
if abi not in ABIS or command("shell", "getprop", "ro.kernel.qemu") != "1":
raise RuntimeError("Performance validation requires an ARM emulator")
if not emulator_ready(command("shell", "am", "get-started-user-state", "0"),
command("shell", "dumpsys", "power"),
command("shell", "dumpsys", "trust")):
raise RuntimeError("Wake and unlock the Android emulator before performance validation")
if profiles and api < 34:
raise RuntimeError("Data-preserving baseline generation requires API 34 or newer")
variant = "profile" if profiles else "benchmark"
@@ -379,15 +424,16 @@ def performance(serial, profiles=False):
directory = "/sdcard/Android/media/" + harness + "/additional_test_output"
command("shell", "mkdir", "-p", directory)
test = "BaselineProfile" if profiles else "StartupBenchmark"
result = command("shell", "am", "instrument", "-w", "-r", "-e", "class", harness + "." + test,
"-e", "additionalTestOutputDir", directory, "-e", "androidx.benchmark.suppressErrors", "EMULATOR",
harness + "/androidx.test.runner.AndroidJUnitRunner")
print(f"ARM API {api}: invoking {test} instrumentation", flush=True)
result = instrumentation(command, harness, ("-e", "class", harness + "." + test,
"-e", "additionalTestOutputDir", directory,
"-e", "androidx.benchmark.suppressErrors", "EMULATOR"),
"de.rfc1437.ironstorage")
local = ROOT / "android/build/local-validation" / ("performance-" + serial)
local.mkdir(parents=True, exist_ok=True)
local.chmod(0o700)
(local / (test + ".txt")).write_text(result)
(local / (test + ".txt")).chmod(0o600)
command("shell", "am", "force-stop", harness)
if "FAILURES!!!" in result or "INSTRUMENTATION_FAILED" in result or "OK (" not in result:
raise RuntimeError("Performance instrumentation failed; raw output stays local")
command("pull", directory, local)
@@ -402,6 +448,26 @@ def performance(serial, profiles=False):
class SelfTest(unittest.TestCase):
def test_instrumentation_stops_harness_and_target_on_failure(self):
calls = []
def command(*args):
calls.append(args)
if args[2] == "instrument":
raise RuntimeError("disconnected emulator")
with self.assertRaisesRegex(RuntimeError, "disconnected emulator"):
instrumentation(command, "fixture.test", target="fixture")
self.assertEqual(calls[-2:], [("shell", "am", "force-stop", "fixture.test"),
("shell", "am", "force-stop", "fixture")])
def test_emulator_preflight_rejects_post_reboot_lock_and_sleep(self):
unlocked = ' User "Owner" (id=0, flags=0x13) (current): deviceLocked=0, strongAuthRequired=0x0'
self.assertTrue(emulator_ready("RUNNING_UNLOCKED", " mWakefulness=Awake\n", unlocked))
self.assertFalse(emulator_ready("RUNNING_LOCKED", " mWakefulness=Awake\n", unlocked))
self.assertFalse(emulator_ready("RUNNING_UNLOCKED", " mWakefulness=Asleep\n", unlocked))
self.assertFalse(emulator_ready("RUNNING_UNLOCKED", "", unlocked))
self.assertFalse(emulator_ready("RUNNING_UNLOCKED", " mWakefulness=Awake\n", unlocked.replace("deviceLocked=0", "deviceLocked=1")))
self.assertFalse(emulator_ready("RUNNING_UNLOCKED", " mWakefulness=Awake\n", ""))
def test_startup_requires_complete_bounded_numeric_samples(self):
def data():
return {"benchmarks": [{"name": name, "repeatIterations": 5, "metrics": {