Files
castarr/tests/smoke.sh
be-nj ac90898a46
All checks were successful
Build TV app / build (push) Successful in 46s
Smoke test: truncate the demo server log per run
2026-08-26 14:34:18 +02:00

168 lines
6.1 KiB
Bash
Executable File

#!/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 <path> # 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 <path>] [--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'" <<XML || fail "Quelle konnte nicht gesetzt werden"
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="m3u_url">http://10.0.2.2:$DEMO_PORT/playlist.m3u</string>
<string name="epg_url">http://10.0.2.2:$DEMO_PORT/epg.xml</string>
</map>
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 <<EOF
OK — kein Absturz, App läuft.
Screenshots: $RUNS/screenshots/smoke-start.png, smoke-nav.png
Log: $LOG
Abgedeckt: Start mit gesetzter Quelle, Senderliste aus der Demo-Playlist,
D-Pad bis in den Player, Wiedereintritt. Nicht abgedeckt: echte Wiedergabe
(die Demo-Streams sind Attrappen), Onboarding am Handy, Kopplung und alles,
was ein Dispatcharr-Backend braucht.
EOF