Test the three bugs that reached the living room, and script the release
The rate limit, the version compare and the download check were all wrapped in Android — a Context, a socket, a file — so none of them had a test, and all three shipped broken: the remote thrown out after five reconnects (0.11.1), a truncated APK handed to the installer (0.11.5), and a version compare that would read 0.11.10 as older than 0.11.9 the moment we get there. They are now plain Kotlin in AttemptBudget and UpdateRules, with the clock injected, and 20 tests covering the failures themselves. AttemptBudget also prunes expired entries before evicting: dropping only empty queues let the map grow without bound while every tracked address held one fresh failure. tests/smoke.sh installs the debug APK on the headless emulator, walks the first screen with the D-pad and fails on a crash or ANR — the class of bug unit tests cannot see, and the reason 0.11.0 crashed on startup. Forty seconds end to end. tools/release.sh does the bump, tests, signed build, tag, apk branch and Gitea release in one command. It refuses an unsigned build and never writes the release notes itself; commit subjects are offered as a draft. tests/helpers/jdk.sh picks a JDK Gradle can run on: Ubuntu moved default-java to 25, which fails the build with a bare "IllegalArgumentException: 25.0.4". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
115
tests/smoke.sh
Executable file
115
tests/smoke.sh
Executable file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke test on the headless Google TV emulator.
|
||||
#
|
||||
# Builds the debug APK, installs it, walks the first screen with the D-pad
|
||||
# and fails on anything the unit tests cannot see: a crash on startup, a
|
||||
# crash while navigating, an ANR. 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"
|
||||
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"
|
||||
|
||||
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 "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"
|
||||
|
||||
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
|
||||
|
||||
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 "Einstellungen öffnen"
|
||||
"$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
|
||||
|
||||
Der Test deckt Start, D-Pad und Wiedereintritt ab. Alles hinter dem
|
||||
Onboarding (echte Quelle, Wiedergabe, Kopplung) braucht ein Backend und
|
||||
bleibt Handarbeit.
|
||||
EOF
|
||||
Reference in New Issue
Block a user