#!/usr/bin/env bash # Stub-Tests fuer deploy.sh - ohne Pi ausfuehrbar. # # ssh/rsync werden im PATH durch Stubs ersetzt: ssh fuehrt das per Heredoc # uebergebene Remote-Skript lokal aus (statt es an einen echten Pi zu # schicken), rsync uebersetzt "host:pfad"-Ziele auf den blossen lokalen Pfad # und ruft dann den echten rsync auf. So laeuft die komplette Logik von # deploy.sh - inklusive des Installationsschritts, der normalerweise per SSH # auf dem Pi passiert - tatsaechlich, aber vollstaendig gegen ein Sandbox- # Verzeichnis (NINLIGHT_STATION_ROOT/…) statt gegen echte Systempfade. # # Aufruf: tools/station/tests/test_deploy.sh set -euo pipefail HERE=$(cd "$(dirname "$0")" && pwd) STATION_DIR=$(cd "$HERE/.." && pwd) REAL_RSYNC=$(command -v rsync) TMPROOT=$(mktemp -d) trap 'rm -rf "$TMPROOT"' EXIT FAILED=0 pass() { echo "ok - $1"; } fail() { echo "FEHLER - $1" >&2; FAILED=1; } # --- Sandbox-Repo aufbauen: tools/station/deploy.sh + tools/mkimage/nl_mkimage.py + build/ --- setup_sandbox() { local sandbox=$1 rm -rf "$sandbox" mkdir -p "$sandbox/tools/station" "$sandbox/tools/mkimage" "$sandbox/build" cp "$STATION_DIR/deploy.sh" "$sandbox/tools/station/deploy.sh" chmod +x "$sandbox/tools/station/deploy.sh" # Inhalt irrelevant, die Dateien muessen nur existieren (werden "uebertragen"). echo "# dummy" >"$sandbox/tools/station/server.py" echo "# dummy" >"$sandbox/tools/station/ninlight-station" echo "# dummy" >"$sandbox/tools/station/ninlight-station.service" # Fake nl_mkimage.py --verify: verhaelt sich wie das echte Tool (eine Zeile # " " auf stdout bei Erfolg, Exit 0; eine Zeile auf # stderr bei Fehlschlag, Exit 1), aber steuerbar per Umgebungsvariable # MKIMAGE_FAIL_SUBSTR: enthaelt der zu pruefende Dateiname diesen Teilstring, # schlaegt die Pruefung fehl. cat >"$sandbox/tools/mkimage/nl_mkimage.py" <<'PY' import os import sys args = sys.argv[1:] f = args[args.index("--verify") + 1] target = args[args.index("--expect-device") + 1] fail_substr = os.environ.get("MKIMAGE_FAIL_SUBSTR", "") if fail_substr and fail_substr in f: print(f"Fehler: {f} besteht die Trailer-Pruefung nicht (Test-Stub)", file=sys.stderr) sys.exit(1) size = os.path.getsize(f) print(f"{target} 1.0.0 {size}") PY } # --- Stub-Kommandos fuer ssh/rsync im PATH --- setup_stubbin() { local bindir=$1 log=$2 mkdir -p "$bindir" cat >"$bindir/ssh" <>"$log" host=\$1; shift "\$@" EOF cat >"$bindir/rsync" <>"$log" args=() for a in "\$@"; do case "\$a" in -*) args+=("\$a") ;; *:*) args+=("\${a#*:}") ;; *) args+=("\$a") ;; esac done exec "$REAL_RSYNC" "\${args[@]}" EOF cat >"$bindir/scp" <>"$log" exit 1 EOF cat >"$bindir/sudo" <>"$log" exec "\$@" EOF cat >"$bindir/systemctl" <>"$log" case "\$1" in is-active) exit "\${FAKE_SYSTEMCTL_ACTIVE_EXIT:-1}" ;; restart) exit "\${FAKE_SYSTEMCTL_RESTART_EXIT:-0}" ;; *) exit 0 ;; esac EOF chmod +x "$bindir"/ssh "$bindir"/rsync "$bindir"/scp "$bindir"/sudo "$bindir"/systemctl } run_deploy() { # Fuehrt deploy.sh mit stubbtem PATH aus; Ausgabe landet in $OUT, Exit-Code in $?. local sandbox=$1; shift # Auf dem echten Pi existieren /usr/local/bin und /etc/systemd/system schon; # in der Sandbox muessen sie fuer "install" eigens angelegt werden. mkdir -p "$SANDBOX_STATE/usr/local/bin" "$SANDBOX_STATE/etc/systemd/system" PATH="$STUBBIN:$PATH" \ NINLIGHT_STATION_ROOT="$SANDBOX_STATE/opt/ninlight-station" \ NINLIGHT_STATION_BIN="$SANDBOX_STATE/usr/local/bin/ninlight-station" \ NINLIGHT_STATION_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-station.service" \ "$sandbox/tools/station/deploy.sh" "$@" } echo "=== Szenario 1: Image besteht die Trailer-Pruefung nicht -> nichts uebertragen ===" SANDBOX="$TMPROOT/s1"; STUBBIN="$TMPROOT/bin1"; LOG="$TMPROOT/s1.log"; SANDBOX_STATE="$TMPROOT/s1-state" setup_sandbox "$SANDBOX" setup_stubbin "$STUBBIN" "$LOG" mkdir -p "$SANDBOX/build/foo" echo "dummy" >"$SANDBOX/build/foo/ninlight_foo_ota.img" : >"$LOG" set +e OUT=$(MKIMAGE_FAIL_SUBSTR=foo run_deploy "$SANDBOX" pi-test foo 2>&1) RC=$? set -e if [ "$RC" -ne 0 ]; then pass "Exit-Code != 0 ($RC)"; else fail "Exit-Code war 0"; fi if echo "$OUT" | grep -q "besteht die Trailer-Pruefung nicht"; then pass "Fehlermeldung zur Trailer-Pruefung vorhanden" else fail "Fehlermeldung zur Trailer-Pruefung fehlt" fi if ! grep -q "STUB ssh\|STUB rsync\|STUB scp" "$LOG"; then pass "kein ssh/rsync/scp aufgerufen - nichts wurde uebertragen" else fail "ssh/rsync/scp wurde trotz fehlgeschlagener Pruefung aufgerufen: $(cat "$LOG")" fi echo echo "=== Szenario 2: keine Images unter build/ gefunden ===" SANDBOX="$TMPROOT/s2"; STUBBIN="$TMPROOT/bin2"; LOG="$TMPROOT/s2.log"; SANDBOX_STATE="$TMPROOT/s2-state" setup_sandbox "$SANDBOX" setup_stubbin "$STUBBIN" "$LOG" : >"$LOG" set +e OUT=$(run_deploy "$SANDBOX" pi-test 2>&1) RC=$? set -e if [ "$RC" -ne 0 ]; then pass "Exit-Code != 0 ($RC)"; else fail "Exit-Code war 0"; fi if echo "$OUT" | grep -q "keine Images unter"; then pass "Fehlermeldung zu fehlenden Images vorhanden" else fail "Fehlermeldung zu fehlenden Images fehlt" fi if ! grep -q "STUB" "$LOG" 2>/dev/null; then pass "kein Transfer-Kommando aufgerufen" else fail "ein Transfer-Kommando wurde trotz fehlender Images aufgerufen: $(cat "$LOG")" fi echo echo "=== Szenario 3: gezielter Deploy eines Geraetetyps laesst andere Images auf dem Ziel unangetastet (Critical 2) ===" SANDBOX="$TMPROOT/s3"; STUBBIN="$TMPROOT/bin3"; LOG="$TMPROOT/s3.log"; SANDBOX_STATE="$TMPROOT/s3-state" setup_sandbox "$SANDBOX" setup_stubbin "$STUBBIN" "$LOG" mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build/bar" echo "foo-image-neu" >"$SANDBOX/build/foo/ninlight_foo_ota.img" echo "bar-image" >"$SANDBOX/build/bar/ninlight_bar_ota.img" # Zielverzeichnis simuliert einen vorherigen Voll-Deploy: Images fuer foo UND bar liegen schon da. mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images" echo "foo-image-alt" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img" echo "bar-image" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_bar_ota.img" : >"$LOG" set +e OUT=$(run_deploy "$SANDBOX" pi-test foo 2>&1) RC=$? set -e if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi if echo "$OUT" | grep -q "andere Geraetetypen im Zielverzeichnis bleiben erhalten"; then pass "Ausgabe kuendigt gezielten Deploy ohne --delete an" else fail "Ausgabe erwaehnt den gezielten Deploy nicht" fi if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_bar_ota.img" ]; then pass "Image des NICHT ausgewaehlten Geraetetyps (bar) blieb erhalten" else fail "Image des nicht ausgewaehlten Geraetetyps (bar) wurde geloescht - Critical 2 nicht behoben" fi if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img" ] \ && [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img")" = "foo-image-neu" ]; then pass "Image des ausgewaehlten Geraetetyps (foo) wurde aktualisiert" else fail "Image des ausgewaehlten Geraetetyps (foo) wurde nicht aktualisiert" fi echo echo "=== Szenario 4: Voll-Deploy (kein target) entfernt veraltete Images - zur Abgrenzung von Szenario 3 ===" SANDBOX="$TMPROOT/s4"; STUBBIN="$TMPROOT/bin4"; LOG="$TMPROOT/s4.log"; SANDBOX_STATE="$TMPROOT/s4-state" setup_sandbox "$SANDBOX" setup_stubbin "$STUBBIN" "$LOG" mkdir -p "$SANDBOX/build/foo" echo "foo-image-neu" >"$SANDBOX/build/foo/ninlight_foo_ota.img" mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images" echo "veraltet" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_veraltet_ota.img" : >"$LOG" set +e OUT=$(run_deploy "$SANDBOX" pi-test 2>&1) RC=$? set -e if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi if [ ! -f "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_veraltet_ota.img" ]; then pass "veraltetes Image wurde beim Voll-Deploy entfernt (--delete wirkt weiterhin, wenn gewollt)" else fail "veraltetes Image wurde beim Voll-Deploy NICHT entfernt" fi echo echo "=== Szenario 5: Dienst laeuft, Neustart nach dem Deploy schlaegt fehl -> Exit != 0 (Minor) ===" SANDBOX="$TMPROOT/s5"; STUBBIN="$TMPROOT/bin5"; LOG="$TMPROOT/s5.log"; SANDBOX_STATE="$TMPROOT/s5-state" setup_sandbox "$SANDBOX" setup_stubbin "$STUBBIN" "$LOG" mkdir -p "$SANDBOX/build/foo" "$SANDBOX_STATE/usr/local/bin" "$SANDBOX_STATE/etc/systemd/system" echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img" : >"$LOG" set +e OUT=$(FAKE_SYSTEMCTL_ACTIVE_EXIT=0 FAKE_SYSTEMCTL_RESTART_EXIT=1 \ PATH="$STUBBIN:$PATH" \ NINLIGHT_STATION_ROOT="$SANDBOX_STATE/opt/ninlight-station" \ NINLIGHT_STATION_BIN="$SANDBOX_STATE/usr/local/bin/ninlight-station" \ NINLIGHT_STATION_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-station.service" \ "$SANDBOX/tools/station/deploy.sh" pi-test foo 2>&1) RC=$? set -e if [ "$RC" -ne 0 ]; then pass "Exit-Code != 0, wenn der Neustart fehlschlaegt ($RC)"; else fail "Exit-Code war 0, obwohl der Neustart fehlschlug"; fi if echo "$OUT" | grep -q "Neustart nach dem Deploy ist aber fehlgeschlagen"; then pass "Fehlermeldung unterscheidet Neustart-Fehlschlag von 'Dienst laeuft nicht'" else fail "Fehlermeldung fehlt oder ist mehrdeutig" fi if echo "$OUT" | grep -q "Dienst laeuft nicht"; then fail "faelschlich 'Dienst laeuft nicht' gemeldet, obwohl der Dienst lief" else pass "'Dienst laeuft nicht' wird nicht faelschlich gemeldet" fi echo if [ "$FAILED" -eq 0 ]; then echo "Alle Tests bestanden." else echo "Mindestens ein Test ist fehlgeschlagen." >&2 fi exit "$FAILED"