From f1a6600e42618e0fb90f854099d05d6eca2ad75c Mon Sep 17 00:00:00 2001 From: be-nj Date: Wed, 26 Aug 2026 13:29:33 +0200 Subject: [PATCH] Emulator helper so tests start in seconds, not minutes The Google TV emulator was launched by hand with -no-snapshot, which forces a cold boot every time. tests/helpers/emulator.sh boots from the AVD's default_boot snapshot (~7s) and refreshes that snapshot on stop, so the next run stays fast. It also wraps the two things a test run needs afterwards: installing an APK and pulling a screenshot into tests/runs/. CONTEXT.md's example dialogue still described Xtream credentials entered through the Remote, which ADR-0005 and ADR-0007 replaced with device flow and phone-side onboarding. Co-Authored-By: Claude Opus 5 (1M context) --- CONTEXT.md | 7 ++-- README.md | 18 ++++++++++ tests/helpers/emulator.sh | 70 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 3 deletions(-) create mode 100755 tests/helpers/emulator.sh diff --git a/CONTEXT.md b/CONTEXT.md index fe78548..c37dd95 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -73,9 +73,10 @@ Channel, no full guide timeline. ## Example dialogue > **Dev:** "Does the **Remote** need Backend credentials?" -> **Domain expert:** "No — the user enters the Xtream credentials of a -> **Quelle** once via the Remote, the **TV-App** stores them and is the only -> one talking to the **Backend**." +> **Domain expert:** "No — the Remote is where **Onboarding** happens, not +> where credentials live: the user types the server URL there and finishes the +> IdP login on the phone. The **TV-App** holds the tokens and is the only one +> talking to the **Backend**. A generic M3U **Quelle** needs no login at all." ## Flagged ambiguities diff --git a/README.md b/README.md index 9142c18..081074e 100644 --- a/README.md +++ b/README.md @@ -79,4 +79,22 @@ sind signiert; über einem Debug-Build muss einmal deinstalliert werden (Signaturwechsel). Updates bezieht die App über die Release-API dieses Repos, die APK selbst liegt auf dem Branch `apk`. +### Tests + +``` +./gradlew test # Unit-Tests, Reports unter tests/runs/ +tests/helpers/emulator.sh start # headloser Google-TV-Emulator +tests/helpers/emulator.sh install app/build/outputs/apk/debug/app-debug.apk +tests/helpers/emulator.sh shot name # Screenshot nach tests/runs/screenshots/ +tests/helpers/emulator.sh stop +``` + +Der Emulator (AVD `castarr-googletv`, API 34) startet aus einem Snapshot und +ist in wenigen Sekunden oben; `stop` schreibt den Snapshot vorher neu. Ein +Kaltstart entsteht nur, wenn der Snapshot fehlt. + +Demo-Daten für einen Lauf ohne echtes Backend: `tests/demo/make-demo-data.py` +erzeugt Playlist und EPG, die ein lokaler HTTP-Server als generische Quelle +ausliefert. + Architektur-Notizen: [CONTEXT.md](CONTEXT.md) und [docs/adr/](docs/adr/). diff --git a/tests/helpers/emulator.sh b/tests/helpers/emulator.sh new file mode 100755 index 0000000..5b719fa --- /dev/null +++ b/tests/helpers/emulator.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Headless Google-TV emulator for test runs. +# +# The AVD keeps a "default_boot" snapshot, so a start restores a booted +# system in seconds instead of cold-booting for minutes. "stop" refreshes +# that snapshot before killing, so the next start stays fast. +# +# tests/helpers/emulator.sh start # boot (or restore) and wait +# tests/helpers/emulator.sh stop # snapshot, then kill +# tests/helpers/emulator.sh status +# tests/helpers/emulator.sh install +# tests/helpers/emulator.sh shot # screenshot into tests/runs/screenshots +set -euo pipefail + +AVD="${CASTARR_AVD:-castarr-googletv}" +SERIAL="${CASTARR_SERIAL:-emulator-5554}" +SDK="${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}" +ADB="$SDK/platform-tools/adb" +EMULATOR="$SDK/emulator/emulator" + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +RUNS="$ROOT/tests/runs" +LOG="$RUNS/emulator.log" + +booted() { [ "$("$ADB" -s "$SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" = "1" ]; } + +start() { + if booted; then echo "already up: $SERIAL"; return 0; fi + mkdir -p "$RUNS" + # No -no-snapshot here: that is what makes a start slow. + nohup "$EMULATOR" -avd "$AVD" \ + -no-window -no-audio -no-boot-anim \ + -gpu swiftshader_indirect \ + >"$LOG" 2>&1 & + local start_ts=$SECONDS + "$ADB" -s "$SERIAL" wait-for-device + for _ in $(seq 1 180); do + booted && { echo "up after $((SECONDS - start_ts))s"; return 0; } + sleep 1 + done + echo "timeout waiting for boot; see $LOG" >&2 + return 1 +} + +stop() { + if ! pgrep -f "qemu-system-.*-avd $AVD" >/dev/null 2>&1 && ! booted; then + echo "not running"; return 0 + fi + # Refresh the snapshot so the next start is a restore, not a cold boot. + "$ADB" -s "$SERIAL" emu avd snapshot save default_boot + "$ADB" -s "$SERIAL" emu kill + echo "stopped, snapshot saved" +} + +case "${1:-status}" in + start) start ;; + stop) stop ;; + status) booted && echo "up: $SERIAL" || echo "down" ;; + install) + [ $# -ge 2 ] || { echo "usage: $0 install " >&2; exit 2; } + "$ADB" -s "$SERIAL" install -r "$2" + ;; + shot) + name="${2:-shot}" + mkdir -p "$RUNS/screenshots" + "$ADB" -s "$SERIAL" exec-out screencap -p > "$RUNS/screenshots/$name.png" + echo "$RUNS/screenshots/$name.png" + ;; + *) echo "usage: $0 {start|stop|status|install |shot }" >&2; exit 2 ;; +esac