#!/usr/bin/env bash # Smoke test on the headless Google TV emulator. # # Builds the debug APK, seeds the demo playlist as its source, installs it # and walks from the channel list into the player with the D-pad. Fails on # anything the unit tests cannot see: a crash on startup, a crash while # navigating, an ANR, or a channel list that stayed empty. The 0.11.0 # startup crash and the rail crash on "Verein hinzufügen" would both have # been caught here. # # tests/smoke.sh # build, run, leave the emulator up # tests/smoke.sh --apk # skip the build, test this APK # tests/smoke.sh --stop # stop the emulator when done set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" HELPERS="$ROOT/tests/helpers" RUNS="$ROOT/tests/runs" SERIAL="${CASTARR_SERIAL:-emulator-5554}" ADB="${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}/platform-tools/adb" PKG="dev.castarr.tv" DEMO_PORT="${CASTARR_DEMO_PORT:-8099}" ACTIVITY="$PKG/.MainActivity" APK="" STOP_AFTER=0 while [ $# -gt 0 ]; do case "$1" in --apk) APK="$2"; shift 2 ;; --stop) STOP_AFTER=1; shift ;; *) echo "usage: $0 [--apk ] [--stop]" >&2; exit 2 ;; esac done mkdir -p "$RUNS/screenshots" LOG="$RUNS/smoke.log" : > "$LOG" # The player retries the fake demo stream for as long as it is on screen, so # without this the request log grows by a few hundred KB per run. : > "$RUNS/http.log" step() { printf '\n== %s\n' "$1" | tee -a "$LOG"; } fail() { printf '\nFAIL: %s\n' "$1" | tee -a "$LOG" >&2; exit 1; } if [ -z "$APK" ]; then step "Debug-APK bauen" # shellcheck source=tests/helpers/jdk.sh . "$HELPERS/jdk.sh" (cd "$ROOT" && ./gradlew assembleDebug --no-daemon -q) >>"$LOG" 2>&1 || fail "Build fehlgeschlagen, siehe $LOG" APK="$ROOT/app/build/outputs/apk/debug/app-debug.apk" fi [ -f "$APK" ] || fail "APK nicht gefunden: $APK" step "Emulator starten" "$HELPERS/emulator.sh" start | tee -a "$LOG" step "Demo-Quelle bereitstellen" python3 "$ROOT/tests/demo/make-demo-data.py" >>"$LOG" 2>&1 || fail "Demo-Daten fehlgeschlagen" # The emulator reaches the host at 10.0.2.2, which is the address baked into # the demo playlist. # A leftover server from an earlier run happily answers on this port while # serving a directory that no longer exists — every request a 404, and the # wait below would spin forever. if ss -ltn "sport = :$DEMO_PORT" 2>/dev/null | grep -q LISTEN; then fail "Port $DEMO_PORT ist belegt: $(ss -ltnp "sport = :$DEMO_PORT" 2>/dev/null | tail -1)" fi # 10.0.2.2 inside the emulator is the host loopback, so binding there is enough. (cd "$ROOT/tests/demo" && exec python3 -m http.server "$DEMO_PORT" --bind 127.0.0.1) \ >>"$RUNS/http.log" 2>&1 & DEMO_PID=$! trap 'kill "$DEMO_PID" 2>/dev/null || true' EXIT for _ in $(seq 1 50); do curl -sf "http://127.0.0.1:$DEMO_PORT/playlist.m3u" -o /dev/null && break sleep 0.2 done curl -sf "http://127.0.0.1:$DEMO_PORT/playlist.m3u" -o /dev/null || fail "Demo-Server antwortet nicht auf Port $DEMO_PORT" step "Installieren" # A debug build over a signed release needs the old one gone first. "$ADB" -s "$SERIAL" uninstall "$PKG" >/dev/null 2>&1 || true "$ADB" -s "$SERIAL" install -r "$APK" >>"$LOG" 2>&1 || fail "Installation fehlgeschlagen" # Onboarding runs on a phone (ADR-0007), which a test has no way to be, so # the source is seeded straight into the preferences the first run reads. # Only a debug build allows this, which is the build under test. "$ADB" -s "$SERIAL" shell run-as "$PKG" sh -c "'mkdir -p shared_prefs && cat > shared_prefs/source.xml'" < http://10.0.2.2:$DEMO_PORT/playlist.m3u http://10.0.2.2:$DEMO_PORT/epg.xml XML step "Starten" "$ADB" -s "$SERIAL" logcat -c "$ADB" -s "$SERIAL" shell am start -W -n "$ACTIVITY" >>"$LOG" 2>&1 || fail "am start fehlgeschlagen" # The first frame is not the point — the crash usually lands a moment later, # when state loads. sleep 6 running() { [ -n "$("$ADB" -s "$SERIAL" shell pidof "$PKG" | tr -d '\r')" ]; } crashed() { "$ADB" -s "$SERIAL" logcat -d -b crash,main 2>/dev/null | grep -E "FATAL EXCEPTION|ANR in $PKG|Process $PKG .* has died" | head -20 } check() { local where="$1" trace trace="$(crashed || true)" if [ -n "$trace" ]; then printf '%s\n' "$trace" >>"$LOG" printf '%s\n' "$trace" | head -5 fail "Absturz $where — vollständig in $LOG" fi running || fail "Prozess weg $where (kein Stacktrace im Log)" } check "beim Start" "$HELPERS/emulator.sh" shot smoke-start >/dev/null # Reaching the channel list is the point: it is the screen the demo source # feeds, and a blank one means the playlist never arrived. step "Senderliste prüfen" DUMP="$RUNS/ui-dump.xml" "$ADB" -s "$SERIAL" shell uiautomator dump /sdcard/ui.xml >>"$LOG" 2>&1 || true "$ADB" -s "$SERIAL" pull /sdcard/ui.xml "$DUMP" >>"$LOG" 2>&1 || true if grep -q "Blau TV HD" "$DUMP" 2>/dev/null; then echo " Sender aus der Demo-Playlist sichtbar" else fail "Senderliste zeigt die Demo-Playlist nicht — Dump in $DUMP" fi step "D-Pad-Navigation" # Down/right walks the rail and opens whatever has focus; back returns. for key in DPAD_DOWN DPAD_RIGHT DPAD_RIGHT DPAD_DOWN DPAD_CENTER BACK DPAD_UP; do "$ADB" -s "$SERIAL" shell input keyevent "$key" sleep 1 done sleep 2 check "bei der Navigation" "$HELPERS/emulator.sh" shot smoke-nav >/dev/null step "Wiedereintritt" "$ADB" -s "$SERIAL" shell am start -n "$ACTIVITY" >/dev/null 2>&1 sleep 2 check "nach dem Wiedereintritt" if [ "$STOP_AFTER" = 1 ]; then step "Emulator stoppen" "$HELPERS/emulator.sh" stop | tee -a "$LOG" fi cat <