1057 lines
55 KiB
Bash
Executable File
1057 lines
55 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Stub-Tests fuer setup_pi.sh - ohne Pi ausfuehrbar.
|
|
#
|
|
# ssh/scp werden im PATH durch Stubs ersetzt (Muster wie tests/test_deploy.sh):
|
|
# ssh fuehrt das per Heredoc uebergebene Remote-Skript lokal aus, scp uebersetzt
|
|
# "host:pfad"-Ziele auf den blossen lokalen Pfad und ruft dann "cp" auf. Die
|
|
# System-Zielpfade (/etc/ninlight-station.conf, /opt/ninlight-station, ...) sind
|
|
# per Umgebungsvariable auf ein Sandbox-Verzeichnis umgebogen (dasselbe Prinzip
|
|
# wie NINLIGHT_STATION_ROOT/_BIN/_UNIT in deploy.sh) - setup_pi.sh fasst damit
|
|
# nichts auf dem Testrechner selbst an ausser dem transienten /tmp, das schon
|
|
# deploy.sh fuer denselben Zweck benutzt.
|
|
#
|
|
# Schwerpunkt: Fixrunde 1, I4 - eine bestehende Konfiguration muss um fehlende
|
|
# Schluessel ergaenzt werden, ohne vorhandene Werte anzutasten.
|
|
#
|
|
# Aufruf: tools/station/tests/test_setup_pi.sh
|
|
set -euo pipefail
|
|
|
|
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
STATION_DIR=$(cd "$HERE/.." && pwd)
|
|
|
|
TMPROOT=$(mktemp -d)
|
|
trap 'rm -rf "$TMPROOT"' EXIT
|
|
|
|
FAILED=0
|
|
pass() { echo "ok - $1"; }
|
|
fail() { echo "FEHLER - $1" >&2; FAILED=1; }
|
|
|
|
file_mode() {
|
|
# $1 = Pfad. GNU-stat (Linux/Pi) muss zuerst probiert werden, nicht BSD-stat
|
|
# (macOS): "stat -f '%Lp' datei" deutet auf GNU-coreutils "-f" als
|
|
# "Dateisystemstatus" und '%Lp' als zweites Dateiargument, scheitert dabei
|
|
# zwar (rc != 0), schreibt aber vorher schon mehrere Zeilen Dateisystem-Infos
|
|
# auf stdout - der "||"-Zweig haengt danach zusaetzlich "600"/"644" an, und
|
|
# der Aufrufer sieht Muell statt der reinen Zahl (Fixrunde 2, N1). BSD-"stat
|
|
# -c" bricht dagegen sofort mit einem Fehler auf stderr ab, ganz ohne
|
|
# Ausgabe auf stdout.
|
|
stat -c '%a' "$1" 2>/dev/null || stat -f '%Lp' "$1" 2>/dev/null
|
|
}
|
|
|
|
setup_stubbin() {
|
|
local bindir=$1 log=$2
|
|
mkdir -p "$bindir"
|
|
|
|
cat >"$bindir/ssh" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB ssh \$*" >>"$log"
|
|
host=\$1; shift
|
|
"\$@"
|
|
EOF
|
|
|
|
cat >"$bindir/scp" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB scp \$*" >>"$log"
|
|
# Testschalter (Task 6b F1, I1): laesst die Uebertragung des Anzeigeverzeichnisses
|
|
# scheitern, ohne dass scp selbst einen Fehler meldet - genau das Bild eines
|
|
# abgebrochenen oder falsch verschachtelten "scp -r".
|
|
for a in "\$@"; do
|
|
case "\$a" in
|
|
*ninlight-display) [ -n "\${FAKE_SCP_SKIP_DIR:-}" ] && exit 0 ;;
|
|
*ninlight-button) [ -n "\${FAKE_SCP_SKIP_BUTTON_DIR:-}" ] && exit 0 ;;
|
|
esac
|
|
done
|
|
args=()
|
|
for a in "\$@"; do
|
|
case "\$a" in
|
|
# "-r" muss durchgereicht werden (als cp -R): setup_pi.sh uebertraegt das
|
|
# Anzeigeverzeichnis als Ganzes, und ein Stub, der das Flag verschluckt, laesst
|
|
# "cp" an einem Verzeichnis scheitern - der Test wuerde dann den Stub anzeigen,
|
|
# nicht das Skript. Alle anderen Optionen (-q) bleiben weiterhin draussen.
|
|
-r|-R) args+=("-R") ;;
|
|
-*) continue ;;
|
|
*:*) args+=("\${a#*:}") ;;
|
|
*) args+=("\$a") ;;
|
|
esac
|
|
done
|
|
exec cp "\${args[@]}"
|
|
EOF
|
|
|
|
# apt-get gibt es auf dem Entwicklungsrechner nicht - und selbst auf einem Debian
|
|
# duerfte ein Test es nie wirklich aufrufen. Der Stub protokolliert nur.
|
|
cat >"$bindir/apt-get" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB apt-get \$*" >>"$log"
|
|
# Getrennt schaltbar: der Fall, den I4 beschreibt, ist "Paketlisten lassen sich
|
|
# auffrischen, das Paket ist trotzdem nicht zu holen" (404). Ein Stub, der schon
|
|
# "update" scheitern laesst, kaeme bei "install" nie an.
|
|
case "\$1" in
|
|
update) exit "\${FAKE_APT_UPDATE_EXIT:-0}" ;;
|
|
install) exit "\${FAKE_APT_EXIT:-0}" ;;
|
|
esac
|
|
exit 0
|
|
EOF
|
|
|
|
# Steht fuer das python3 DES PI, nicht fuer das des Testrechners: nur die
|
|
# Pillow-Probe wird beantwortet (steuerbar per FAKE_PIL_MISSING - auf diesem Rechner
|
|
# ist Pillow sonst je nach Installation zufaellig vorhanden oder nicht, und der
|
|
# Paketschritt waere damit gar nicht pruefbar). Jeder andere Aufruf laeuft durch das
|
|
# echte python3.
|
|
cat >"$bindir/python3" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB python3 \$*" >>"$log"
|
|
for a in "\$@"; do
|
|
case "\$a" in
|
|
*"import PIL"*) exit "\${FAKE_PIL_MISSING:-0}" ;;
|
|
esac
|
|
done
|
|
exec "$(command -v python3)" "\$@"
|
|
EOF
|
|
|
|
# "sudo install -o root -g root ..." (im NetworkManager-Keyfile-Schritt) braucht
|
|
# echtes Root, um den Eigentuemer zu setzen - hat der Testrechner (anders als der
|
|
# Pi) nicht. Der Stub streicht -o/-g nur fuer "install", der Rest laeuft wie auf
|
|
# dem Pi ueber ein exec des ungestubbten Kommandos.
|
|
cat >"$bindir/sudo" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB sudo \$*" >>"$log"
|
|
# Testschalter (Task 6b F1, I2): laesst ein bestimmtes "sudo install" scheitern -
|
|
# gebraucht, um den Abbruchpfad in write_profile() zu erreichen, auf dem die
|
|
# Temp-Datei mit dem WLAN-Passwort sonst liegenbliebe.
|
|
if [ -n "\${FAKE_SUDO_INSTALL_FAIL:-}" ]; then
|
|
case "\$*" in
|
|
install*"\$FAKE_SUDO_INSTALL_FAIL"*) echo "install: simulierter Fehler" >&2; exit 1 ;;
|
|
esac
|
|
fi
|
|
if [ "\$1" = "install" ]; then
|
|
args=()
|
|
skip=0
|
|
for a in "\$@"; do
|
|
if [ "\$skip" -eq 1 ]; then skip=0; continue; fi
|
|
case "\$a" in
|
|
-o|-g) skip=1 ;;
|
|
*) args+=("\$a") ;;
|
|
esac
|
|
done
|
|
exec "\${args[@]}"
|
|
fi
|
|
exec "\$@"
|
|
EOF
|
|
|
|
cat >"$bindir/systemctl" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB systemctl \$*" >>"$log"
|
|
# Testschalter: laesst "enable"/"restart" scheitern (beides darf setup_pi.sh nur
|
|
# warnen lassen, nie abbrechen). Der Taster hat eigene Schalter (FAKE_BUTTON_*), damit
|
|
# ein Szenario NUR ihn scheitern lassen kann, ohne die Anzeige mitzureissen - sonst
|
|
# liesse sich nicht zeigen, dass ein fehlgeschlagenes enable/start des Tasters fuer
|
|
# sich genommen keinen Abbruch ausloest.
|
|
case "\$*" in
|
|
*ninlight-button*)
|
|
case "\$1" in
|
|
enable) exit "\${FAKE_BUTTON_ENABLE_EXIT:-0}" ;;
|
|
restart) exit "\${FAKE_BUTTON_RESTART_EXIT:-0}" ;;
|
|
*) exit 0 ;;
|
|
esac
|
|
;;
|
|
esac
|
|
case "\$1" in
|
|
enable) exit "\${FAKE_SYSTEMCTL_ENABLE_EXIT:-0}" ;;
|
|
restart) exit "\${FAKE_SYSTEMCTL_RESTART_EXIT:-0}" ;;
|
|
is-active) exit "\${FAKE_SYSTEMCTL_ACTIVE_EXIT:-0}" ;;
|
|
*) exit 0 ;;
|
|
esac
|
|
EOF
|
|
|
|
cat >"$bindir/nmcli" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB nmcli \$*" >>"$log"
|
|
if [ "\$1" = "-t" ] && [ "\$2" = "-f" ] && [ "\$3" = "DEVICE,STATE" ]; then
|
|
echo "wlan0:connected"
|
|
exit 0
|
|
fi
|
|
exit 0
|
|
EOF
|
|
|
|
chmod +x "$bindir"/ssh "$bindir"/scp "$bindir"/sudo "$bindir"/systemctl "$bindir"/nmcli \
|
|
"$bindir"/apt-get "$bindir"/python3
|
|
}
|
|
|
|
# $1 = Sandbox-Wurzel fuer die "Systempfade"
|
|
setup_state() {
|
|
local state=$1
|
|
rm -rf "$state"
|
|
mkdir -p "$state/etc" "$state/opt" "$state/usr/local/bin" "$state/etc/systemd/system" \
|
|
"$state/etc/NetworkManager/system-connections" "$state/tmp" \
|
|
"$state/boot/firmware" "$state/dev"
|
|
# $state/boot/firmware/config.txt und $state/boot/config.txt existieren absichtlich
|
|
# NICHT von selbst (ein Szenario legt sie an, wenn es sie braucht) - so deckt der
|
|
# Standardzustand genau den Fall "kein Boot-Verfahren gefunden" ab, ohne dass jedes
|
|
# Szenario das extra anfordern muesste.
|
|
}
|
|
|
|
# Fuehrt setup_pi.sh mit gestubbtem PATH und auf die Sandbox umgebogenen
|
|
# Systempfaden aus. $1 = state-Verzeichnis, $2 = stub-bin-Verzeichnis.
|
|
#
|
|
# NINLIGHT_STAGE_DIR biegt auch die per scp "uebertragenen" Zwischendateien auf
|
|
# die Sandbox um (Fixrunde 2, N4): ohne das wuerden scp/ssh-Stub sie unter dem
|
|
# echten /tmp des Entwicklungsrechners ablegen und am Ende per "rm -f" wieder
|
|
# loeschen - mit denselben, leicht kollidierenden Namen wie
|
|
# "ninlight-station.conf.example" oder "server.py".
|
|
run_setup() {
|
|
local state=$1 stubbin=$2
|
|
# TMPDIR in die Sandbox: write_profile() legt seine Temp-Dateien (mit dem WLAN-
|
|
# Passwort darin) per "mktemp -d" unter TMPDIR an. Nur so kann Szenario 12 danach
|
|
# nachsehen, ob wirklich nichts liegengeblieben ist - und nebenbei fasst der Test
|
|
# damit auch dafuer nicht das echte /tmp des Entwicklungsrechners an.
|
|
mkdir -p "$state/tmpdir"
|
|
PATH="$stubbin:$PATH" \
|
|
TMPDIR="$state/tmpdir" \
|
|
NINLIGHT_SSH_BIN="$stubbin/ssh" \
|
|
NINLIGHT_SCP_BIN="$stubbin/scp" \
|
|
NINLIGHT_STAGE_DIR="$state/tmp" \
|
|
NINLIGHT_STATION_CONF="$state/etc/ninlight-station.conf" \
|
|
NINLIGHT_STATION_ROOT="$state/opt/ninlight-station" \
|
|
NINLIGHT_STATION_BIN="$state/usr/local/bin/ninlight-station" \
|
|
NINLIGHT_STATION_UNIT="$state/etc/systemd/system/ninlight-station.service" \
|
|
NINLIGHT_DISPLAY_UNIT="$state/etc/systemd/system/ninlight-display.service" \
|
|
NINLIGHT_BUTTON_UNIT="$state/etc/systemd/system/ninlight-button.service" \
|
|
NINLIGHT_NM_DIR="$state/etc/NetworkManager/system-connections" \
|
|
NINLIGHT_BOOT_CONFIG_NEW="$state/boot/firmware/config.txt" \
|
|
NINLIGHT_BOOT_CONFIG_OLD="$state/boot/config.txt" \
|
|
NINLIGHT_SPIDEV_PATH="$state/dev/spidev0.0" \
|
|
"$STATION_DIR/setup_pi.sh" pi-test
|
|
}
|
|
|
|
echo "=== Szenario 1: frischer Pi ohne Konfiguration -> Vorlage wird komplett angelegt ==="
|
|
STATE="$TMPROOT/s1-state"; STUBBIN="$TMPROOT/bin1"; LOG="$TMPROOT/s1.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
if diff -q "$STATION_DIR/station.conf.example" "$STATE/etc/ninlight-station.conf" >/dev/null 2>&1; then
|
|
pass "Konfiguration wurde 1:1 aus der Vorlage angelegt"
|
|
else
|
|
fail "Konfiguration weicht von der Vorlage ab oder fehlt"
|
|
fi
|
|
[ -x "$STATE/opt/ninlight-station/server.py" ] && pass "server.py installiert" || fail "server.py fehlt"
|
|
[ -f "$STATE/opt/ninlight-station/products.py" ] && pass "products.py installiert" || fail "products.py fehlt (arm braucht es)"
|
|
[ -x "$STATE/usr/local/bin/ninlight-station" ] && pass "CLI installiert" || fail "ninlight-station CLI fehlt"
|
|
[ -f "$STATE/etc/systemd/system/ninlight-station.service" ] && pass "Unit installiert" || fail "Unit fehlt"
|
|
[ -d "$STATE/opt/ninlight-station/images" ] && pass "images/-Verzeichnis angelegt" || fail "images/-Verzeichnis fehlt"
|
|
|
|
echo
|
|
echo "=== Szenario 2 (Fixrunde 1, I4): bestehende Konfiguration ohne die neuen Schluessel wird ergaenzt, nicht ersetzt ==="
|
|
STATE="$TMPROOT/s2-state"; STUBBIN="$TMPROOT/bin2"; LOG="$TMPROOT/s2.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
cat >"$STATE/etc/ninlight-station.conf" <<'CONF'
|
|
SSID='eigene-ssid'
|
|
PSK='eigenes-passwort'
|
|
IMAGE_DIR='/opt/ninlight-station/images'
|
|
HOME_CONNECTION='preconfigured'
|
|
AP_CONNECTION='ninlight-recovery'
|
|
CONF
|
|
chmod 0600 "$STATE/etc/ninlight-station.conf"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
echo "$OUT" | grep -q "ergaenzt" && pass "Ausgabe kuendigt das Ergaenzen fehlender Schluessel an" || fail "Ausgabe erwaehnt 'ergaenzt' nicht: $OUT"
|
|
CONF_OUT="$STATE/etc/ninlight-station.conf"
|
|
if grep -Fqx "SSID='eigene-ssid'" "$CONF_OUT" && grep -Fqx "PSK='eigenes-passwort'" "$CONF_OUT"; then
|
|
pass "vorhandene Werte (SSID/PSK) blieben unveraendert"
|
|
else
|
|
fail "vorhandene Werte wurden ueberschrieben: $(cat "$CONF_OUT")"
|
|
fi
|
|
for KEY in MIGRATE_AP_CONNECTION MIGRATE_SSID MIGRATE_PSK EPD_REVISION; do
|
|
if grep -Eq "^${KEY}=" "$CONF_OUT"; then
|
|
pass "fehlender Schluessel $KEY wurde ergaenzt"
|
|
else
|
|
fail "Schluessel $KEY fehlt weiterhin in der Konfiguration"
|
|
fi
|
|
done
|
|
MODE=$(file_mode "$CONF_OUT")
|
|
[ "$MODE" = "600" ] && pass "Konfigurationsdatei bleibt 0600" || fail "Konfigurationsdatei hat falsche Rechte: $MODE"
|
|
|
|
echo
|
|
echo "=== Szenario 3: erneuter Lauf auf einer bereits vollstaendigen Konfiguration ist ein echtes No-Op ==="
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
echo "$OUT" | grep -q "alle Schluessel vorhanden" && pass "meldet 'alle Schluessel vorhanden' statt erneut zu ergaenzen" || fail "Ausgabe: $OUT"
|
|
DUP=$(grep -c '^EPD_REVISION=' "$CONF_OUT")
|
|
[ "$DUP" -eq 1 ] && pass "EPD_REVISION steht nur einmal in der Datei (kein doppeltes Anhaengen)" || fail "EPD_REVISION steht $DUP mal in der Datei"
|
|
|
|
echo
|
|
echo "=== Szenario 4 (Fixrunde 2, N3): ein eingerueckter Bestandswert gilt nicht als fehlend ==="
|
|
STATE="$TMPROOT/s4-state"; STUBBIN="$TMPROOT/bin4"; LOG="$TMPROOT/s4.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
# Fuehrendes Leerzeichen vor PSK= - gueltiges ". datei"-Shell, aber eine rein
|
|
# zeilenverankerte Erkennung ("^PSK=") wuerde das als "fehlt" werten und die
|
|
# Vorlagenzeile anhaengen. Da das CLI die Datei per ". $CONF" sourct, gewinnt
|
|
# dann die LETZTE Zuweisung - das eigene Passwort waere durch den oeffentlichen
|
|
# Default aus der Vorlage ersetzt, ohne dass die Datei das irgendwo anzeigt.
|
|
cat >"$STATE/etc/ninlight-station.conf" <<'CONF'
|
|
SSID='eigene-ssid'
|
|
PSK='eigenes-eingeruecktes-passwort'
|
|
IMAGE_DIR='/opt/ninlight-station/images'
|
|
HOME_CONNECTION='preconfigured'
|
|
AP_CONNECTION='ninlight-recovery'
|
|
CONF
|
|
chmod 0600 "$STATE/etc/ninlight-station.conf"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
CONF_OUT4="$STATE/etc/ninlight-station.conf"
|
|
PSK_LINES=$(grep -cE '^[[:space:]]*PSK=' "$CONF_OUT4")
|
|
[ "$PSK_LINES" -eq 1 ] && pass "PSK steht weiterhin nur einmal in der Datei (nicht dupliziert)" || fail "PSK steht $PSK_LINES mal in der Datei: $(cat "$CONF_OUT4")"
|
|
if grep -Fq "eigenes-eingeruecktes-passwort" "$CONF_OUT4"; then
|
|
pass "der eingerueckte Bestandswert von PSK blieb erhalten"
|
|
else
|
|
fail "der eingerueckte Bestandswert von PSK wurde ersetzt: $(cat "$CONF_OUT4")"
|
|
fi
|
|
if grep -Fqx "PSK='ninlight-recovery'" "$CONF_OUT4"; then
|
|
fail "der oeffentliche Vorlagen-Default fuer PSK wurde zusaetzlich angehaengt - eigenes Passwort waere beim Sourcen ueberschrieben worden"
|
|
else
|
|
pass "der oeffentliche Vorlagen-Default fuer PSK wurde NICHT angehaengt"
|
|
fi
|
|
for KEY in MIGRATE_AP_CONNECTION MIGRATE_SSID MIGRATE_PSK EPD_REVISION; do
|
|
grep -Eq "^${KEY}=" "$CONF_OUT4" && pass "fehlender Schluessel $KEY wurde trotzdem ergaenzt" || fail "Schluessel $KEY fehlt weiterhin"
|
|
done
|
|
|
|
echo
|
|
echo "=== Szenario 5 (Task 6b, Punkt 1): Anzeige wird ausgeliefert, installiert und aktiviert ==="
|
|
STATE="$TMPROOT/s5-state"; STUBBIN="$TMPROOT/bin5"; LOG="$TMPROOT/s5.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
DISP="$STATE/opt/ninlight-station/display"
|
|
# Die Panel-Treiber und der Herkunftsnachweis gehoeren mit auf den Pi, nicht nur ins
|
|
# Repo: HERKUNFT.md ist der Nachweis, woher der uebernommene Waveshare-Code stammt.
|
|
for F in display.py render.py epdconfig.py epd2in13_V2.py epd2in13_V3.py HERKUNFT.md; do
|
|
[ -f "$DISP/$F" ] && pass "display/$F ausgeliefert" || fail "display/$F fehlt unter $DISP"
|
|
done
|
|
[ -x "$DISP/display.py" ] && pass "display.py ist ausfuehrbar (Handbetrieb 'display test')" || fail "display.py ist nicht ausfuehrbar"
|
|
MODE=$(file_mode "$DISP/render.py")
|
|
[ "$MODE" = "644" ] && pass "importierter Code ist 0644" || fail "render.py hat Rechte $MODE statt 644"
|
|
[ ! -d "$DISP/__pycache__" ] && pass "kein __pycache__ auf dem Pi gelandet" || fail "__pycache__ wurde mitkopiert"
|
|
[ -f "$STATE/etc/systemd/system/ninlight-display.service" ] && pass "ninlight-display.service installiert" || fail "Anzeige-Unit fehlt - die Unit waere weiter toter Text im Repo"
|
|
# Ohne "enable" startet die Anzeige nach einem Stromausfall nicht wieder mit.
|
|
grep -Fq "systemctl enable ninlight-display.service" "$LOG" && pass "Anzeigedienst wurde aktiviert" || fail "kein 'systemctl enable ninlight-display' im Log"
|
|
grep -Fq "systemctl restart ninlight-display.service" "$LOG" && pass "Anzeigedienst wurde gestartet" || fail "kein 'systemctl restart ninlight-display' im Log"
|
|
# Die Station selbst bleibt nach dem Einrichten absichtlich AUS - nur die Anzeige
|
|
# laeuft. Ein versehentliches "enable ninlight-station" waere ein Sicherheitsfehler:
|
|
# der Zugangspunkt kaeme nach jedem Boot von allein hoch.
|
|
grep -Fq "systemctl enable ninlight-station" "$LOG" && fail "setup_pi.sh aktiviert den Stationsdienst - der Zugangspunkt kaeme nach jedem Boot von allein hoch" || pass "Stationsdienst bleibt deaktiviert (Station ist nach dem Einrichten aus)"
|
|
|
|
echo
|
|
echo "=== Szenario 6 (Task 6b, Punkt 1): python3-pil wird nachgezogen, wenn Pillow fehlt ==="
|
|
# Ohne Pillow beendet sich der Anzeigedienst still mit 0 (Absicht) - das faellt
|
|
# nirgends auf, also muss das Paket hier kommen.
|
|
STATE="$TMPROOT/s6-state"; STUBBIN="$TMPROOT/bin6"; LOG="$TMPROOT/s6.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(FAKE_PIL_MISSING=1 run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
grep -Fq "apt-get install -y python3-pil" "$LOG" && pass "python3-pil wird per apt nachinstalliert" || fail "kein 'apt-get install -y python3-pil' im Log"
|
|
|
|
echo
|
|
echo "=== Szenario 7: ist Pillow schon da, wird apt gar nicht erst bemueht ==="
|
|
STATE="$TMPROOT/s7-state"; STUBBIN="$TMPROOT/bin7"; LOG="$TMPROOT/s7.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
grep -Fq "apt-get" "$LOG" && fail "apt-get wurde trotz vorhandenem Pillow aufgerufen" || pass "kein apt-Aufruf, wenn Pillow schon vorhanden ist"
|
|
|
|
echo
|
|
echo "=== Szenario 8 (Task 6b, Punkt 2): das Profil fuer den Migrations-Zugangspunkt wird angelegt ==="
|
|
# Ohne dieses zweite Profil scheitert "ninlight-station migrate on" auf einem frisch
|
|
# eingerichteten Pi an need_profile "$MIGRATE_AP_CONNECTION".
|
|
#
|
|
# Geprueft wird der Zustand aus Szenario 7 (ein vollstaendiger Lauf auf einem frischen
|
|
# Pi, $STATE/$LOG zeigen noch dorthin) - deshalb hier kein eigener Lauf.
|
|
NMDIR="$STATE/etc/NetworkManager/system-connections"
|
|
MIGRATE_FILE="$NMDIR/qconnex-firmware.nmconnection"
|
|
[ -f "$MIGRATE_FILE" ] && pass "qconnex-firmware.nmconnection angelegt" || fail "Migrationsprofil fehlt in $NMDIR: $(ls "$NMDIR")"
|
|
[ -f "$NMDIR/ninlight-recovery.nmconnection" ] && pass "Rettungsprofil weiterhin angelegt" || fail "Rettungsprofil fehlt"
|
|
# SSID und PSK sind von der alten Klyqa-Firmware fest vorgegeben - weicht eines ab,
|
|
# findet keine alte Lampe die Migrationsstation.
|
|
grep -Fqx "ssid=qconnex-firmware" "$MIGRATE_FILE" && pass "SSID ist die von der Klyqa-Firmware vorgegebene" || fail "falsche SSID: $(grep '^ssid=' "$MIGRATE_FILE")"
|
|
grep -Fqx "psk=qconnex-firmware" "$MIGRATE_FILE" && pass "PSK ist der von der Klyqa-Firmware vorgegebene" || fail "falscher PSK"
|
|
grep -Fqx "mode=ap" "$MIGRATE_FILE" && pass "Profil ist ein Zugangspunkt" || fail "mode=ap fehlt"
|
|
grep -Fqx "method=shared" "$MIGRATE_FILE" && pass "ipv4.method ist 'shared'" || fail "method=shared fehlt"
|
|
# Dieselbe Adresse wie das Rettungsprofil - genau deshalb schliessen sich beide aus,
|
|
# und genau darauf verlaesst sich ap_switch() in ninlight-station.
|
|
grep -Fqx "address1=192.168.8.1/24" "$MIGRATE_FILE" && pass "Adresse 192.168.8.1/24 (wie das Rettungsprofil)" || fail "falsche Adresse: $(grep '^address1=' "$MIGRATE_FILE")"
|
|
grep -Fqx "autoconnect=false" "$MIGRATE_FILE" && pass "kein Selbststart beim Booten" || fail "autoconnect ist nicht false - der Migrations-AP kaeme nach jedem Boot von allein hoch"
|
|
MODE=$(file_mode "$MIGRATE_FILE")
|
|
[ "$MODE" = "600" ] && pass "Keyfile ist 0600 (das Passwort steht darin)" || fail "Keyfile hat Rechte $MODE statt 600"
|
|
# Das Passwort darf nie als Kommandozeilenargument an nmcli gehen (in "ps" sichtbar).
|
|
grep -Fq "nmcli connection load" "$LOG" && pass "Profil wird per 'connection load' eingelesen" || fail "kein 'nmcli connection load' im Log"
|
|
grep -Eq "STUB nmcli .*(psk|wifi-sec)" "$LOG" && fail "das Passwort wurde als nmcli-Argument uebergeben - in 'ps' fuer jeden lokalen Benutzer sichtbar" || pass "kein Passwort in einer nmcli-Kommandozeile"
|
|
|
|
echo
|
|
echo "=== Szenario 9 (Task 6b, Punkt 2): ein bestehendes Migrationsprofil bleibt unangetastet ==="
|
|
STATE="$TMPROOT/s9-state"; STUBBIN="$TMPROOT/bin9"; LOG="$TMPROOT/s9.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
NMDIR9="$STATE/etc/NetworkManager/system-connections"
|
|
# Von Hand angelegt, mit handjustierten Feldern (anderer Kanal), die dieses Skript
|
|
# gar nicht kennt.
|
|
cat >"$NMDIR9/qconnex-firmware.nmconnection" <<'NM'
|
|
[connection]
|
|
id=qconnex-firmware
|
|
uuid=11111111-2222-3333-4444-555555555555
|
|
type=wifi
|
|
[wifi]
|
|
mode=ap
|
|
ssid=qconnex-firmware
|
|
channel=11
|
|
NM
|
|
chmod 0600 "$NMDIR9/qconnex-firmware.nmconnection"
|
|
BEFORE=$(cat "$NMDIR9/qconnex-firmware.nmconnection")
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
if [ "$BEFORE" = "$(cat "$NMDIR9/qconnex-firmware.nmconnection")" ]; then
|
|
pass "bestehendes Migrationsprofil wurde Byte fuer Byte unveraendert gelassen"
|
|
else
|
|
fail "bestehendes Migrationsprofil wurde ueberschrieben: $(cat "$NMDIR9/qconnex-firmware.nmconnection")"
|
|
fi
|
|
echo "$OUT" | grep -q "unveraendert gelassen" && pass "Ausgabe sagt, dass das Profil unveraendert blieb" || fail "Ausgabe erwaehnt das nicht: $OUT"
|
|
# Das Rettungsprofil dagegen wird bewusst neu geschrieben (SSID/PSK koennen sich
|
|
# geaendert haben) - aber unter Beibehaltung der UUID.
|
|
[ -f "$NMDIR9/ninlight-recovery.nmconnection" ] && pass "Rettungsprofil trotzdem angelegt" || fail "Rettungsprofil fehlt"
|
|
|
|
echo
|
|
echo "=== Szenario 10: ein leerer Konfigurationswert legt kein kaputtes Profil an ==="
|
|
STATE="$TMPROOT/s10-state"; STUBBIN="$TMPROOT/bin10"; LOG="$TMPROOT/s10.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
cat >"$STATE/etc/ninlight-station.conf" <<'CONF'
|
|
SSID='eigene-ssid'
|
|
PSK='eigenes-passwort'
|
|
IMAGE_DIR='/opt/ninlight-station/images'
|
|
HOME_CONNECTION='preconfigured'
|
|
AP_CONNECTION='ninlight-recovery'
|
|
MIGRATE_SSID='qconnex-firmware'
|
|
MIGRATE_PSK=''
|
|
MIGRATE_AP_CONNECTION='qconnex-firmware'
|
|
EPD_REVISION='V3'
|
|
CONF
|
|
chmod 0600 "$STATE/etc/ninlight-station.conf"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -ne 0 ] && pass "Exit-Code != 0 bei leerem PSK" || fail "Exit-Code 0 trotz leerem Migrations-PSK: $OUT"
|
|
echo "$OUT" | grep -qi "leer" && pass "Fehlermeldung nennt den leeren Wert" || fail "Fehlermeldung fehlt: $OUT"
|
|
NMDIR10="$STATE/etc/NetworkManager/system-connections"
|
|
if [ -f "$NMDIR10/qconnex-firmware.nmconnection" ]; then
|
|
fail "trotz leerem PSK wurde ein Migrationsprofil geschrieben"
|
|
else
|
|
pass "kein Migrationsprofil mit leerem PSK angelegt"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 11 (Task 6b F1, I1): Reststand in der Ablage fuehrt nicht zu einer stillen Nullinstallation ==="
|
|
# Ein frueherer Lauf ist nach dem scp abgebrochen (sudo-Timeout, Platte voll, Strg-C),
|
|
# die Aufraeumzeile lief nicht. "scp -r quelle ziel" kopiert dann IN das vorhandene Ziel
|
|
# hinein (…/ninlight-display/display/), die Installationsschleife ueberspringt
|
|
# Verzeichnisse und findet nichts - der Lauf meldete frueher Erfolg und aktivierte einen
|
|
# Dienst, dessen ExecStart-Datei gar nicht existiert (203/EXEC im 5-Sekunden-Takt).
|
|
STATE="$TMPROOT/s11-state"; STUBBIN="$TMPROOT/bin11"; LOG="$TMPROOT/s11.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$STATE/tmp/ninlight-display"
|
|
echo "rest" >"$STATE/tmp/ninlight-display/uebrig.py"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
DISP11="$STATE/opt/ninlight-station/display"
|
|
[ -f "$DISP11/display.py" ] && pass "display.py trotz Reststand installiert" || fail "Nullinstallation: $DISP11 enthaelt $(ls -A "$DISP11" 2>/dev/null | tr '\n' ' ')"
|
|
[ -f "$DISP11/epd2in13_V3.py" ] && pass "Panel-Treiber trotz Reststand installiert" || fail "Panel-Treiber fehlt"
|
|
[ ! -d "$DISP11/display" ] && pass "kein verschachteltes display/display/ entstanden" || fail "verschachteltes Verzeichnis angelegt"
|
|
[ ! -f "$DISP11/uebrig.py" ] && pass "der Reststand des Vorlaufs wurde nicht mitinstalliert" || fail "uebrig.py aus dem Reststand landete auf dem Pi"
|
|
|
|
echo
|
|
echo "=== Szenario 12 (Task 6b F1, I1): scheitert die Uebertragung, wird laut abgebrochen statt still 'installiert' gemeldet ==="
|
|
STATE="$TMPROOT/s12-state"; STUBBIN="$TMPROOT/bin12"; LOG="$TMPROOT/s12.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(FAKE_SCP_SKIP_DIR=1 run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -ne 0 ] && pass "Exit-Code != 0" || fail "Exit-Code 0 trotz fehlgeschlagener Uebertragung: $OUT"
|
|
echo "$OUT" | grep -q "display.py fehlt" && pass "Fehlermeldung nennt die fehlende Datei" || fail "Fehlermeldung fehlt: $OUT"
|
|
# Das Schlimmste waere ein aktivierter Dienst ohne ExecStart-Datei.
|
|
grep -Fq "systemctl enable ninlight-display" "$LOG" && fail "der Anzeigedienst wurde trotz fehlender Dateien aktiviert - 203/EXEC im Neustart-Takt" || pass "kein aktivierter Dienst ohne Dateien"
|
|
# WAHRHEITSGEHALT der Meldung, nicht ihr Wortlaut (Task 6b F2, N1): "Nichts installiert"
|
|
# muss auch stimmen. Vorher stand die Pruefung hinter vier "sudo install", und auf dem Pi
|
|
# lagen server.py, products.py, das CLI und die Stations-Unit bereits - ausgerechnet an
|
|
# dem Werkbank-Moment, fuer den die Pruefung gedacht ist, log das Skript den Bediener an.
|
|
echo "$OUT" | grep -q "Nichts installiert" && pass "die Meldung behauptet, nichts installiert zu haben" || fail "Meldung ohne diese Zusage: $OUT"
|
|
for F in "$STATE/opt/ninlight-station/server.py" "$STATE/opt/ninlight-station/products.py" \
|
|
"$STATE/usr/local/bin/ninlight-station" \
|
|
"$STATE/etc/systemd/system/ninlight-station.service" \
|
|
"$STATE/etc/systemd/system/ninlight-display.service"; do
|
|
[ ! -e "$F" ] && pass "wirklich nicht installiert: $(basename "$F")" || fail "die Meldung luegt - $F liegt auf dem Pi"
|
|
done
|
|
# Und die Ablage darf nicht liegenbleiben: sonst ist sie beim naechsten Lauf genau der
|
|
# Reststand, gegen den Szenario 11 absichert.
|
|
REST=$(find "$STATE/tmp" -mindepth 1 2>/dev/null | head -n5)
|
|
[ -z "$REST" ] && pass "die Ablage wurde auch im Fehlerfall aufgeraeumt" || fail "Reststand in der Ablage: $REST"
|
|
|
|
echo
|
|
echo "=== Szenario 13 (Task 6b F1, I1): eine im Repo geloeschte Datei verschwindet auch auf dem Pi ==="
|
|
# Dasselbe Argument wie in deploy.sh: display/ ist reiner Code aus dem Repo, ein
|
|
# zurueckgebliebenes altes Modul wuerde weiter mit importiert.
|
|
STATE="$TMPROOT/s13-state"; STUBBIN="$TMPROOT/bin13"; LOG="$TMPROOT/s13.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$STATE/opt/ninlight-station/display"
|
|
echo "alt" >"$STATE/opt/ninlight-station/display/veraltet.py"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
[ ! -f "$STATE/opt/ninlight-station/display/veraltet.py" ] && pass "veraltete Datei entfernt" || fail "veraltet.py blieb liegen"
|
|
[ -f "$STATE/opt/ninlight-station/display/render.py" ] && pass "der aktuelle Code ist trotzdem da" || fail "render.py fehlt"
|
|
|
|
echo
|
|
echo "=== Szenario 14 (Task 6b F1, I2): scheitert das Schreiben eines Profils, bleibt keine Temp-Datei mit dem Passwort liegen ==="
|
|
# Zwischen "mktemp" und "install" steht das WLAN-Passwort in einer Datei unter TMPDIR.
|
|
# Ein Abbruch genau dort (set -e) darf sie nicht zuruecklassen - dafuer gibt es den Trap
|
|
# um das gemeinsame mktemp-Verzeichnis. Ohne ihn faellt das sonst nirgends auf.
|
|
STATE="$TMPROOT/s14-state"; STUBBIN="$TMPROOT/bin14"; LOG="$TMPROOT/s14.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
cat >"$STATE/etc/ninlight-station.conf" <<'CONF'
|
|
SSID='eigene-ssid'
|
|
PSK='streng-geheimes-passwort'
|
|
IMAGE_DIR='/opt/ninlight-station/images'
|
|
HOME_CONNECTION='preconfigured'
|
|
AP_CONNECTION='ninlight-recovery'
|
|
MIGRATE_SSID='qconnex-firmware'
|
|
MIGRATE_PSK='qconnex-firmware'
|
|
MIGRATE_AP_CONNECTION='qconnex-firmware'
|
|
EPD_REVISION='V3'
|
|
CONF
|
|
chmod 0600 "$STATE/etc/ninlight-station.conf"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(FAKE_SUDO_INSTALL_FAIL=.nmconnection run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -ne 0 ] && pass "Exit-Code != 0 (das Profil liess sich nicht schreiben)" || fail "Exit-Code 0 trotz fehlgeschlagenem install: $OUT"
|
|
LEFTOVER=$(find "$STATE/tmpdir" -type f 2>/dev/null | head -n5)
|
|
[ -z "$LEFTOVER" ] && pass "keine Temp-Datei unter TMPDIR zurueckgeblieben" || fail "liegengeblieben: $LEFTOVER"
|
|
# Der eigentliche Schaden waere das Passwort in einer Datei, die niemand mehr aufraeumt.
|
|
if grep -rlq "streng-geheimes-passwort" "$STATE/tmpdir" 2>/dev/null; then
|
|
fail "das WLAN-Passwort steht in einer liegengebliebenen Datei unter $STATE/tmpdir"
|
|
else
|
|
pass "das WLAN-Passwort steht in keiner liegengebliebenen Datei"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 15 (Task 6b F1, I4): scheitert apt, ist die Station trotzdem vollstaendig eingerichtet ==="
|
|
# Eine Migrationsstation steht typischerweise nicht am Netz. Ein harter Abbruch hier
|
|
# hiesse: gar keine Station statt einer Station ohne Panel.
|
|
STATE="$TMPROOT/s15-state"; STUBBIN="$TMPROOT/bin15"; LOG="$TMPROOT/s15.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(FAKE_PIL_MISSING=1 FAKE_APT_EXIT=100 run_setup "$STATE" "$STUBBIN" 2>"$TMPROOT/s15.err")
|
|
RC=$?
|
|
set -e
|
|
ERR=$(cat "$TMPROOT/s15.err")
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz gescheitertem apt" || fail "Exit-Code $RC - kein Netz haette die ganze Einrichtung verhindert: $ERR"
|
|
echo "$ERR" | grep -q "Warnung" && pass "Warnung (keine Fehlermeldung) auf stderr" || fail "keine Warnung auf stderr: $ERR"
|
|
[ -x "$STATE/opt/ninlight-station/server.py" ] && pass "server.py installiert" || fail "server.py fehlt"
|
|
[ -x "$STATE/usr/local/bin/ninlight-station" ] && pass "CLI installiert" || fail "CLI fehlt"
|
|
[ -f "$STATE/etc/systemd/system/ninlight-station.service" ] && pass "Stations-Unit installiert" || fail "Stations-Unit fehlt"
|
|
[ -f "$STATE/etc/systemd/system/ninlight-display.service" ] && pass "Anzeige-Unit installiert" || fail "Anzeige-Unit fehlt"
|
|
[ -f "$STATE/opt/ninlight-station/display/display.py" ] && pass "Anzeigecode installiert" || fail "Anzeigecode fehlt"
|
|
NMDIR15="$STATE/etc/NetworkManager/system-connections"
|
|
[ -f "$NMDIR15/ninlight-recovery.nmconnection" ] && pass "Rettungsprofil angelegt" || fail "Rettungsprofil fehlt"
|
|
[ -f "$NMDIR15/qconnex-firmware.nmconnection" ] && pass "Migrationsprofil angelegt" || fail "Migrationsprofil fehlt"
|
|
# Reihenfolge: apt kommt NACH der Installation, sonst haette ein Fehlschlag alles davor
|
|
# mitgerissen.
|
|
INST_LINE=$({ grep -n "install .*ninlight-station.service" "$LOG" | head -n1 | cut -d: -f1; } || true)
|
|
APT_LINE=$({ grep -n "apt-get install" "$LOG" | head -n1 | cut -d: -f1; } || true)
|
|
if [ -n "$INST_LINE" ] && [ -n "$APT_LINE" ] && [ "$INST_LINE" -lt "$APT_LINE" ]; then
|
|
pass "der Paketschritt laeuft nach der Installation (inst=$INST_LINE, apt=$APT_LINE)"
|
|
else
|
|
fail "der Paketschritt laeuft weiterhin vor der Installation (inst=$INST_LINE, apt=$APT_LINE)"
|
|
fi
|
|
# Und der Anzeigedienst startet erst nach dem Paketschritt - sonst liefe er beim
|
|
# allerersten Zeichnen ohne Pillow und beendete sich still mit 0.
|
|
RESTART_LINE=$({ grep -n "systemctl restart ninlight-display" "$LOG" | head -n1 | cut -d: -f1; } || true)
|
|
if [ -n "$RESTART_LINE" ] && [ "$APT_LINE" -lt "$RESTART_LINE" ]; then
|
|
pass "der Anzeigedienst startet nach dem Paketschritt (apt=$APT_LINE, restart=$RESTART_LINE)"
|
|
else
|
|
fail "der Anzeigedienst startet vor dem Paketschritt (apt=$APT_LINE, restart=$RESTART_LINE)"
|
|
fi
|
|
grep -Fq "apt-get update" "$LOG" && pass "die Paketlisten werden vorher aufgefrischt" || fail "kein 'apt-get update' vor dem install"
|
|
|
|
echo
|
|
echo "=== Szenario 16: ein fehlgeschlagenes enable/restart des Anzeigedienstes warnt nur ==="
|
|
STATE="$TMPROOT/s16-state"; STUBBIN="$TMPROOT/bin16"; LOG="$TMPROOT/s16.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(FAKE_SYSTEMCTL_ENABLE_EXIT=1 FAKE_SYSTEMCTL_RESTART_EXIT=1 run_setup "$STATE" "$STUBBIN" 2>"$TMPROOT/s16.err")
|
|
RC=$?
|
|
set -e
|
|
ERR=$(cat "$TMPROOT/s16.err")
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlgeschlagenem enable/restart" || fail "Exit-Code $RC: $ERR"
|
|
echo "$ERR" | grep -q "systemctl enable ninlight-display" && pass "Warnung zu 'enable' auf stderr" || fail "keine enable-Warnung: $ERR"
|
|
echo "$ERR" | grep -q "systemctl restart ninlight-display" && pass "Warnung zu 'restart' auf stderr" || fail "keine restart-Warnung: $ERR"
|
|
[ -f "$STATE/etc/NetworkManager/system-connections/qconnex-firmware.nmconnection" ] && pass "der Lauf ging danach normal weiter (Profile angelegt)" || fail "der Lauf brach nach der Warnung ab"
|
|
|
|
echo
|
|
echo "=== Szenario 17 (Task 6b F2, N2): ein gescheitertes 'apt-get update' verhindert die Installation von Pillow nicht ==="
|
|
# "apt-get update" liefert schon dann einen Fehler, wenn eine einzige Quelle nicht
|
|
# erreichbar ist (veraltete Drittanbieter-Liste, teilweises Netz). Mit "update &&
|
|
# install" bekaeme ein Pi, auf dem "install" aus dem Cache muehelos durchgelaufen waere,
|
|
# still kein Pillow - und genau das Stillsein war der Grund, das Paket ueberhaupt hier
|
|
# nachzuziehen.
|
|
STATE="$TMPROOT/s17-state"; STUBBIN="$TMPROOT/bin17"; LOG="$TMPROOT/s17.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
# SPI schon aktiv: dieses Szenario prueft ausschliesslich den Paketschritt, eine
|
|
# fehlende Boot-Konfiguration wuerde eine zweite, hier voellig unbeteiligte Warnung
|
|
# erzeugen (Szenario 26 deckt genau die ab).
|
|
printf 'dtparam=spi=on\n' >"$STATE/boot/firmware/config.txt"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(FAKE_PIL_MISSING=1 FAKE_APT_UPDATE_EXIT=100 run_setup "$STATE" "$STUBBIN" 2>"$TMPROOT/s17.err")
|
|
RC=$?
|
|
set -e
|
|
ERR=$(cat "$TMPROOT/s17.err")
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC: $ERR"
|
|
grep -Fq "apt-get install -y python3-pil" "$LOG" && pass "'install' wird trotz gescheitertem 'update' versucht" || fail "'install' wurde nach dem gescheiterten 'update' gar nicht mehr versucht: $(grep apt-get "$LOG")"
|
|
echo "$OUT" | grep -q "python3-pil installiert" && pass "Pillow wurde installiert" || fail "Ausgabe meldet die Installation nicht: $OUT"
|
|
echo "$ERR" | grep -q "Warnung" && fail "es wurde gewarnt, obwohl das Paket installiert werden konnte: $ERR" || pass "keine unnoetige Warnung"
|
|
|
|
echo
|
|
echo "=== Szenario 18 (Task 6b F2, N3): ein Strg-C waehrend des Profilschreibens endet nicht als 'erfolgreich' ==="
|
|
# Ein INT/TERM-Handler, der nicht selbst beendet, laesst bash NACH dem Handler
|
|
# weiterlaufen - der Lauf liefe bis "fertig" durch und endete mit 0, obwohl er
|
|
# abgebrochen wurde. (Der EXIT-Trap allein wuerde zum Aufraeumen genuegen; hier geht es
|
|
# um den ehrlichen Exit-Code.)
|
|
#
|
|
# Die Trap-Zeilen werden aus setup_pi.sh HERAUSGELESEN, nicht hier nachgebaut: sonst
|
|
# prueft der Test seine eigene Kopie und bleibt gruen, wenn das Skript sie aendert.
|
|
TRAPS=$(grep -E "^trap '.*PROFILE_TMPDIR.*' (EXIT|INT|TERM|HUP)$" "$STATION_DIR/setup_pi.sh")
|
|
TRAP_COUNT=$(printf '%s\n' "$TRAPS" | grep -c . || true)
|
|
[ "$TRAP_COUNT" -ge 2 ] && pass "die Trap-Zeilen liessen sich aus setup_pi.sh auslesen ($TRAP_COUNT Stueck)" || fail "keine Trap-Zeilen in setup_pi.sh gefunden - Szenario 18 prueft sonst nichts"
|
|
SIGPROBE="$TMPROOT/sigprobe.sh"
|
|
{
|
|
echo '#!/usr/bin/env bash'
|
|
echo 'set -eu'
|
|
echo 'PROFILE_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/ninlight-sigprobe.XXXXXX")'
|
|
printf '%s\n' "$TRAPS"
|
|
echo 'printf "%s" "$PROFILE_TMPDIR" >"$1"'
|
|
echo 'touch "$PROFILE_TMPDIR/profile.geheim"'
|
|
# Das Signal an den eigenen Prozess: deterministisch, ohne Wartezeiten.
|
|
echo 'kill -INT $$'
|
|
echo 'echo WEITERGELAUFEN'
|
|
} >"$SIGPROBE"
|
|
chmod +x "$SIGPROBE"
|
|
set +e
|
|
PROBE_OUT=$(TMPDIR="$TMPROOT" "$SIGPROBE" "$TMPROOT/sigprobe.dir" 2>&1)
|
|
PROBE_RC=$?
|
|
set -e
|
|
PROBE_DIR=$(cat "$TMPROOT/sigprobe.dir" 2>/dev/null || true)
|
|
echo "$PROBE_OUT" | grep -q WEITERGELAUFEN && fail "nach dem Strg-C lief das Skript weiter - der Lauf koennte mit 'fertig' und Exit 0 enden" || pass "das Skript laeuft nach dem Strg-C nicht weiter"
|
|
[ "$PROBE_RC" -eq 130 ] && pass "Exit-Code 130 (128+SIGINT), nicht 0" || fail "Exit-Code $PROBE_RC statt 130"
|
|
[ -n "$PROBE_DIR" ] && [ ! -d "$PROBE_DIR" ] && pass "das Temp-Verzeichnis mit dem Passwort wurde dabei aufgeraeumt" || fail "Temp-Verzeichnis blieb liegen: $PROBE_DIR"
|
|
|
|
echo
|
|
echo "=== Szenario 19 (Hardware-Abnahme): der Anzeigedienst bekommt ein beschreibbares Arbeitsverzeichnis und ein gesetztes HOME ==="
|
|
# Befund vom echten Pi: unter systemd ist HOME leer und das Arbeitsverzeichnis "/" -
|
|
# lgpio legt seine Benachrichtigungs-Pipe unter "$HOME/.lgd-nfy0" an, das wird
|
|
# "//.lgd-nfy0", und ProtectSystem=strict (s.u.) macht "/" schreibgeschuetzt. lgpio
|
|
# scheitert, gpiozero faellt bis zur experimentellen NativeFactory durch, und die
|
|
# scheitert ihrerseits - das Panel bleibt schwarz, obwohl die Hardware in Ordnung ist.
|
|
# Kein systemd auf diesem (macOS-)Testrechner, also keine echte Namensraum-Pruefung -
|
|
# aber die Unit ist eine Textdatei, und "RuntimeDirectory=/WorkingDirectory=/
|
|
# Environment=HOME= sind gesetzt" laesst sich daran pruefen, ob nun im Repo direkt
|
|
# oder (hier) in der von setup_pi.sh ausgelieferten Kopie - Szenario 12 bewacht
|
|
# schon, dass beide je identisch sind.
|
|
STATE="$TMPROOT/s19-state"; STUBBIN="$TMPROOT/bin19"; LOG="$TMPROOT/s19.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
INSTALLED_DISPLAY_UNIT="$STATE/etc/systemd/system/ninlight-display.service"
|
|
if [ -f "$INSTALLED_DISPLAY_UNIT" ]; then
|
|
grep -Eq '^RuntimeDirectory=ninlight-display$' "$INSTALLED_DISPLAY_UNIT" \
|
|
&& pass "RuntimeDirectory=ninlight-display gesetzt (eigenes Verzeichnis, nicht das der Hauptunit)" \
|
|
|| fail "RuntimeDirectory=ninlight-display fehlt - lgpio findet kein beschreibbares HOME unter systemd"
|
|
grep -Eq '^WorkingDirectory=/run/ninlight-display$' "$INSTALLED_DISPLAY_UNIT" \
|
|
&& pass "WorkingDirectory=/run/ninlight-display gesetzt" \
|
|
|| fail "WorkingDirectory=/run/ninlight-display fehlt"
|
|
grep -Eq '^Environment=HOME=/run/ninlight-display$' "$INSTALLED_DISPLAY_UNIT" \
|
|
&& pass "Environment=HOME=/run/ninlight-display gesetzt" \
|
|
|| fail "Environment=HOME=/run/ninlight-display fehlt - lgpio legt seine Pipe sonst unter //.lgd-nfy0 an (schreibgeschuetzt durch ProtectSystem=strict)"
|
|
else
|
|
fail "ninlight-display.service wurde nicht installiert - Szenario 19 kann nichts pruefen"
|
|
fi
|
|
|
|
echo "=== Szenario 20 (Hardware-Abnahme, Pi 2): Wants= zieht den Stationsdienst nicht mehr mit hoch ==="
|
|
# Befund am Geraet: ninlight-station ist absichtlich disabled (die Station ist per
|
|
# Vorgabe aus), wurde aber bei jedem Boot trotzdem gestartet, weil dieser (enabled)
|
|
# Anzeigedienst per Wants=ninlight-station.service die andere Unit AKTIVIERT hat - Wants=
|
|
# ordnet nicht nur, es startet die genannte Unit mit. Ohne Zugangspunkt kann server.py
|
|
# 192.168.8.1 nicht binden, stirbt, wird von Restart=always mehrfach neu versucht und
|
|
# bleibt auf "failed" haengen (flutet das Journal bei jedem Boot). Gemeint war reine
|
|
# Reihenfolge - dafuer reicht After= allein, das nur ordnet, FALLS beide Units ohnehin
|
|
# starten, und aktiviert nichts von sich aus.
|
|
if [ -f "$INSTALLED_DISPLAY_UNIT" ]; then
|
|
grep -Eq '^Wants=ninlight-station\.service$' "$INSTALLED_DISPLAY_UNIT" \
|
|
&& fail "Wants=ninlight-station.service steht noch in der ausgelieferten Unit - das zieht die (absichtlich disabled) Station bei jedem Boot mit hoch" \
|
|
|| pass "kein Wants=ninlight-station.service mehr in der ausgelieferten Unit"
|
|
grep -Eq '^After=ninlight-station\.service$' "$INSTALLED_DISPLAY_UNIT" \
|
|
&& pass "After=ninlight-station.service bleibt (reine Reihenfolge, falls beide ohnehin starten)" \
|
|
|| fail "After=ninlight-station.service fehlt - die Startreihenfolge waere nicht mehr garantiert"
|
|
else
|
|
fail "ninlight-display.service wurde nicht installiert - Szenario 20 kann nichts pruefen"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 21 (Hardware-Abnahme, SPI): fehlende dtparam=spi-Zeile wird ergaenzt ==="
|
|
# Auf einem frischen Pi ist SPI aus, es gibt aber schon eine config.txt (mit anderem
|
|
# Inhalt) ohne jede dtparam=spi-Zeile - der haeufigste Fall auf einem frisch
|
|
# geflashten Image.
|
|
STATE="$TMPROOT/s21-state"; STUBBIN="$TMPROOT/bin21"; LOG="$TMPROOT/s21.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
CONFIG21="$STATE/boot/firmware/config.txt"
|
|
cat >"$CONFIG21" <<'CFG'
|
|
# For more options and information see
|
|
# http://rptl.io/configtxt
|
|
dtparam=i2c_arm=on
|
|
dtparam=audio=on
|
|
CFG
|
|
BEFORE21=$(cat "$CONFIG21")
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
grep -Fqx "dtparam=spi=on" "$CONFIG21" && pass "dtparam=spi=on wurde ergaenzt" || fail "dtparam=spi=on fehlt weiterhin: $(cat "$CONFIG21")"
|
|
grep -Fq "dtparam=i2c_arm=on" "$CONFIG21" && pass "der Rest der Datei blieb erhalten" || fail "vorhandener Inhalt ging verloren"
|
|
[ -f "${CONFIG21}.bak-ninlight" ] && pass "Sicherung angelegt" || fail "keine Sicherung unter ${CONFIG21}.bak-ninlight"
|
|
[ "$(cat "${CONFIG21}.bak-ninlight")" = "$BEFORE21" ] && pass "Sicherung entspricht dem Originalzustand" || fail "Sicherung weicht vom Original ab"
|
|
echo "$OUT" | grep -q "WICHTIG: SPI wurde soeben erst eingeschaltet" && pass "Neustart-Hinweis erscheint" || fail "kein Neustart-Hinweis in der Ausgabe: $OUT"
|
|
echo "$OUT" | grep -q "sudo reboot" && pass "der Hinweis nennt den Neustart-Befehl" || fail "kein 'sudo reboot' im Hinweis"
|
|
|
|
echo
|
|
echo "=== Szenario 22 (Hardware-Abnahme, SPI): eine auskommentierte Zeile wird aktiviert ==="
|
|
STATE="$TMPROOT/s22-state"; STUBBIN="$TMPROOT/bin22"; LOG="$TMPROOT/s22.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
CONFIG22="$STATE/boot/firmware/config.txt"
|
|
cat >"$CONFIG22" <<'CFG'
|
|
dtparam=audio=on
|
|
#dtparam=spi=on
|
|
dtparam=i2c_arm=on
|
|
CFG
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
grep -Fqx "dtparam=spi=on" "$CONFIG22" && pass "die Zeile ist jetzt aktiv" || fail "dtparam=spi=on steht nicht aktiv in der Datei: $(cat "$CONFIG22")"
|
|
COUNT22=$(grep -c 'dtparam=spi=on' "$CONFIG22")
|
|
[ "$COUNT22" -eq 1 ] && pass "die Zeile wurde ersetzt, nicht verdoppelt" || fail "dtparam=spi=on steht $COUNT22 mal in der Datei"
|
|
[ -f "${CONFIG22}.bak-ninlight" ] && pass "Sicherung angelegt" || fail "keine Sicherung angelegt"
|
|
echo "$OUT" | grep -q "WICHTIG: SPI wurde soeben erst eingeschaltet" && pass "Neustart-Hinweis erscheint" || fail "kein Neustart-Hinweis: $OUT"
|
|
|
|
echo
|
|
echo "=== Szenario 23 (Hardware-Abnahme, SPI): 'dtparam=spi=off' wird umgeschaltet ==="
|
|
# An eine explizit deaktivierte Zeile ist genauso zu denken wie an eine
|
|
# auskommentierte - beide sind "nicht an", beide muessen normalisiert werden.
|
|
STATE="$TMPROOT/s23-state"; STUBBIN="$TMPROOT/bin23"; LOG="$TMPROOT/s23.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
CONFIG23="$STATE/boot/firmware/config.txt"
|
|
printf 'dtparam=spi=off\n' >"$CONFIG23"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
grep -Fqx "dtparam=spi=on" "$CONFIG23" && pass "'off' wurde auf 'on' umgeschaltet" || fail "Zeile weiterhin nicht aktiv: $(cat "$CONFIG23")"
|
|
echo "$OUT" | grep -q "WICHTIG: SPI wurde soeben erst eingeschaltet" && pass "Neustart-Hinweis erscheint" || fail "kein Neustart-Hinweis: $OUT"
|
|
|
|
echo
|
|
echo "=== Szenario 24 (Hardware-Abnahme, SPI): schon aktiv -> echtes No-Op, kein Neustart-Hinweis ==="
|
|
STATE="$TMPROOT/s24-state"; STUBBIN="$TMPROOT/bin24"; LOG="$TMPROOT/s24.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
CONFIG24="$STATE/boot/firmware/config.txt"
|
|
cat >"$CONFIG24" <<'CFG'
|
|
dtparam=audio=on
|
|
dtparam=spi=on
|
|
CFG
|
|
BEFORE24=$(cat "$CONFIG24")
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
[ "$(cat "$CONFIG24")" = "$BEFORE24" ] && pass "die Datei blieb byteweise unveraendert" || fail "die Datei wurde trotz bereits aktivem SPI angefasst"
|
|
[ ! -f "${CONFIG24}.bak-ninlight" ] && pass "keine Sicherung angelegt (es gab nichts zu sichern)" || fail "eine Sicherung wurde angelegt, obwohl nichts geaendert wurde"
|
|
echo "$OUT" | grep -q "bereits eingeschaltet" && pass "meldet ruhig, dass SPI schon an ist" || fail "keine Bestaetigung in der Ausgabe: $OUT"
|
|
echo "$OUT" | grep -q "WICHTIG: SPI wurde soeben erst eingeschaltet" && fail "Neustart-Hinweis erscheint, obwohl nichts geaendert wurde" || pass "kein Neustart-Hinweis"
|
|
|
|
echo
|
|
echo "=== Szenario 25 (Hardware-Abnahme, SPI): /dev/spidev0.0 existiert schon -> SPI gilt als an, egal was in config.txt steht ==="
|
|
# Kann per Overlay oder einem anderen Mechanismus aktiviert worden sein - die Datei
|
|
# ist dann nicht die Wahrheit.
|
|
STATE="$TMPROOT/s25-state"; STUBBIN="$TMPROOT/bin25"; LOG="$TMPROOT/s25.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$(dirname "$STATE/dev/spidev0.0")"
|
|
: >"$STATE/dev/spidev0.0"
|
|
CONFIG25="$STATE/boot/firmware/config.txt"
|
|
printf '#dtparam=spi=on\n' >"$CONFIG25"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
grep -Fqx "#dtparam=spi=on" "$CONFIG25" && pass "config.txt blieb unangetastet (spidev0.0 war entscheidend, nicht die Datei)" || fail "config.txt wurde trotz vorhandenem spidev0.0 geaendert: $(cat "$CONFIG25")"
|
|
[ ! -f "${CONFIG25}.bak-ninlight" ] && pass "keine Sicherung angelegt" || fail "eine Sicherung wurde angelegt, obwohl spidev0.0 schon da war"
|
|
echo "$OUT" | grep -q "SPI ist bereits aktiv" && pass "meldet, dass SPI ueber das Geraet schon aktiv ist" || fail "keine passende Meldung: $OUT"
|
|
echo "$OUT" | grep -q "WICHTIG: SPI wurde soeben erst eingeschaltet" && fail "Neustart-Hinweis erscheint, obwohl nichts geaendert wurde" || pass "kein Neustart-Hinweis"
|
|
|
|
echo
|
|
echo "=== Szenario 26 (Hardware-Abnahme, SPI): weder config.txt noch config.txt (alt) vorhanden -> Warnung, kein Abbruch ==="
|
|
# Kein Raspberry Pi OS oder ein anderes Boot-Verfahren - die Anzeige ist nirgends
|
|
# Voraussetzung fuer eine funktionierende Station, dasselbe Prinzip wie beim
|
|
# apt-Schritt.
|
|
STATE="$TMPROOT/s26-state"; STUBBIN="$TMPROOT/bin26"; LOG="$TMPROOT/s26.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>"$TMPROOT/s26.err")
|
|
RC=$?
|
|
set -e
|
|
ERR=$(cat "$TMPROOT/s26.err")
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlender Boot-Konfiguration" || fail "Exit-Code $RC: $ERR"
|
|
echo "$ERR" | grep -qi "Warnung" && pass "Warnung auf stderr" || fail "keine Warnung: $ERR"
|
|
echo "$ERR" | grep -q "weder" && pass "Warnung nennt beide gesuchten Pfade" || fail "Warnung erwaehnt die Pfade nicht: $ERR"
|
|
[ -x "$STATE/opt/ninlight-station/server.py" ] && pass "die Station wurde trotzdem vollstaendig eingerichtet" || fail "server.py fehlt - ein fehlendes Boot-Verfahren haette die Einrichtung nicht verhindern duerfen"
|
|
echo "$OUT" | grep -q "WICHTIG: SPI wurde soeben erst eingeschaltet" && fail "Neustart-Hinweis erscheint, obwohl SPI gar nicht geprueft werden konnte" || pass "kein Neustart-Hinweis"
|
|
|
|
echo
|
|
echo "=== Szenario 27 (Hardware-Abnahme, SPI): eine bestehende Sicherung wird nicht ueberschrieben ==="
|
|
# Ein zweiter Lauf, nachdem SPI zwischendurch von Hand wieder ausgeschaltet wurde -
|
|
# ohne diese Absicherung waere der allererste Originalzustand nach dem zweiten Lauf
|
|
# weg.
|
|
STATE="$TMPROOT/s27-state"; STUBBIN="$TMPROOT/bin27"; LOG="$TMPROOT/s27.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
CONFIG27="$STATE/boot/firmware/config.txt"
|
|
printf '#dtparam=spi=on\n' >"$CONFIG27"
|
|
printf 'urspruenglicher-originalzustand\n' >"${CONFIG27}.bak-ninlight"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
grep -Fqx "dtparam=spi=on" "$CONFIG27" && pass "die Zeile wurde trotzdem aktiviert" || fail "dtparam=spi=on fehlt: $(cat "$CONFIG27")"
|
|
[ "$(cat "${CONFIG27}.bak-ninlight")" = "urspruenglicher-originalzustand" ] && pass "die bestehende Sicherung blieb unangetastet" || fail "die Sicherung wurde ueberschrieben: $(cat "${CONFIG27}.bak-ninlight")"
|
|
echo "$OUT" | grep -q "existiert bereits - wird nicht ueberschrieben" && pass "Ausgabe erwaehnt, dass die Sicherung nicht ueberschrieben wurde" || fail "keine entsprechende Meldung: $OUT"
|
|
|
|
echo
|
|
echo "=== Szenario 28 (Task 8b): Taster wird ausgeliefert, installiert und aktiviert ==="
|
|
STATE="$TMPROOT/s28-state"; STUBBIN="$TMPROOT/bin28"; LOG="$TMPROOT/s28.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
BTN28="$STATE/opt/ninlight-station/button"
|
|
[ -f "$BTN28/button.py" ] && pass "button/button.py ausgeliefert" || fail "button/button.py fehlt unter $BTN28"
|
|
[ -x "$BTN28/button.py" ] && pass "button.py ist ausfuehrbar" || fail "button.py ist nicht ausfuehrbar"
|
|
[ ! -d "$BTN28/__pycache__" ] && pass "kein __pycache__ auf dem Pi gelandet" || fail "__pycache__ wurde mitkopiert"
|
|
[ -f "$STATE/etc/systemd/system/ninlight-button.service" ] && pass "ninlight-button.service installiert" || fail "Taster-Unit fehlt - sie waere weiter toter Text im Repo"
|
|
grep -Fq "systemctl enable ninlight-button.service" "$LOG" && pass "Tasterdienst wurde aktiviert" || fail "kein 'systemctl enable ninlight-button' im Log"
|
|
grep -Fq "systemctl restart ninlight-button.service" "$LOG" && pass "Tasterdienst wurde gestartet" || fail "kein 'systemctl restart ninlight-button' im Log"
|
|
# Die Station selbst bleibt weiterhin aus - derselbe Sicherheitsfehler waere hier
|
|
# genauso schwerwiegend wie in Szenario 5.
|
|
grep -Fq "systemctl enable ninlight-station" "$LOG" && fail "setup_pi.sh aktiviert den Stationsdienst" || pass "Stationsdienst bleibt deaktiviert"
|
|
|
|
echo
|
|
echo "=== Szenario 29 (Task 8b): Reststand in der Ablage fuehrt beim Taster nicht zu einer stillen Nullinstallation ==="
|
|
# Dasselbe Muster wie Szenario 11 fuer die Anzeige, hier fuer button/: ein
|
|
# liegengebliebenes "…/ninlight-button/" laesst ein "scp -r" hineinkopieren statt
|
|
# darauf, die Installationsschleife faende dann nichts.
|
|
STATE="$TMPROOT/s29-state"; STUBBIN="$TMPROOT/bin29"; LOG="$TMPROOT/s29.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$STATE/tmp/ninlight-button"
|
|
echo "rest" >"$STATE/tmp/ninlight-button/uebrig.py"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
BTN29="$STATE/opt/ninlight-station/button"
|
|
[ -f "$BTN29/button.py" ] && pass "button.py trotz Reststand installiert" || fail "Nullinstallation: $BTN29 enthaelt $(ls -A "$BTN29" 2>/dev/null | tr '\n' ' ')"
|
|
[ ! -d "$BTN29/button" ] && pass "kein verschachteltes button/button/ entstanden" || fail "verschachteltes Verzeichnis angelegt"
|
|
[ ! -f "$BTN29/uebrig.py" ] && pass "der Reststand des Vorlaufs wurde nicht mitinstalliert" || fail "uebrig.py aus dem Reststand landete auf dem Pi"
|
|
|
|
echo
|
|
echo "=== Szenario 30 (Task 8b): scheitert die Uebertragung des Tastercodes, wird laut abgebrochen ==="
|
|
STATE="$TMPROOT/s30-state"; STUBBIN="$TMPROOT/bin30"; LOG="$TMPROOT/s30.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(FAKE_SCP_SKIP_BUTTON_DIR=1 run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -ne 0 ] && pass "Exit-Code != 0" || fail "Exit-Code 0 trotz fehlgeschlagener Uebertragung: $OUT"
|
|
echo "$OUT" | grep -q "button.py fehlt" && pass "Fehlermeldung nennt die fehlende Datei" || fail "Fehlermeldung fehlt: $OUT"
|
|
grep -Fq "systemctl enable ninlight-button" "$LOG" && fail "der Tasterdienst wurde trotz fehlender Dateien aktiviert" || pass "kein aktivierter Tasterdienst ohne Dateien"
|
|
for F in "$STATE/opt/ninlight-station/server.py" "$STATE/opt/ninlight-station/products.py" \
|
|
"$STATE/usr/local/bin/ninlight-station" \
|
|
"$STATE/etc/systemd/system/ninlight-station.service" \
|
|
"$STATE/etc/systemd/system/ninlight-display.service" \
|
|
"$STATE/etc/systemd/system/ninlight-button.service"; do
|
|
[ ! -e "$F" ] && pass "wirklich nicht installiert: $(basename "$F")" || fail "die Meldung luegt - $F liegt auf dem Pi"
|
|
done
|
|
REST30=$(find "$STATE/tmp" -mindepth 1 2>/dev/null | head -n5)
|
|
[ -z "$REST30" ] && pass "die Ablage wurde auch im Fehlerfall aufgeraeumt" || fail "Reststand in der Ablage: $REST30"
|
|
|
|
echo
|
|
echo "=== Szenario 31 (Task 8b): eine im Repo geloeschte Datei verschwindet auch aus button/ auf dem Pi ==="
|
|
STATE="$TMPROOT/s31-state"; STUBBIN="$TMPROOT/bin31"; LOG="$TMPROOT/s31.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$STATE/opt/ninlight-station/button"
|
|
echo "alt" >"$STATE/opt/ninlight-station/button/veraltet.py"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
[ ! -f "$STATE/opt/ninlight-station/button/veraltet.py" ] && pass "veraltete Datei entfernt" || fail "veraltet.py blieb liegen"
|
|
[ -f "$STATE/opt/ninlight-station/button/button.py" ] && pass "der aktuelle Code ist trotzdem da" || fail "button.py fehlt"
|
|
|
|
echo
|
|
echo "=== Szenario 32 (Task 8b): ein fehlgeschlagenes enable/restart NUR des Tasterdienstes warnt und bricht nicht ab ==="
|
|
# Eigene Schalter (FAKE_BUTTON_*), damit die Anzeige dabei unbeteiligt bleibt - das
|
|
# zeigt, dass der Taster fuer sich genommen keine Voraussetzung ist.
|
|
STATE="$TMPROOT/s32-state"; STUBBIN="$TMPROOT/bin32"; LOG="$TMPROOT/s32.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(FAKE_BUTTON_ENABLE_EXIT=1 FAKE_BUTTON_RESTART_EXIT=1 run_setup "$STATE" "$STUBBIN" 2>"$TMPROOT/s32.err")
|
|
RC=$?
|
|
set -e
|
|
ERR=$(cat "$TMPROOT/s32.err")
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlgeschlagenem enable/restart des Tasters" || fail "Exit-Code $RC: $ERR"
|
|
echo "$ERR" | grep -q "systemctl enable ninlight-button" && pass "Warnung zu 'enable' auf stderr" || fail "keine enable-Warnung: $ERR"
|
|
echo "$ERR" | grep -q "systemctl restart ninlight-button" && pass "Warnung zu 'restart' auf stderr" || fail "keine restart-Warnung: $ERR"
|
|
grep -Fq "systemctl enable ninlight-display.service" "$LOG" && pass "die Anzeige wurde trotzdem aktiviert - der Taster reisst sie nicht mit" || fail "die Anzeige-Aktivierung blieb aus"
|
|
[ -f "$STATE/etc/NetworkManager/system-connections/qconnex-firmware.nmconnection" ] && pass "der Lauf ging danach normal weiter (Profile angelegt)" || fail "der Lauf brach nach der Warnung ab"
|
|
|
|
echo
|
|
echo "=== Szenario 33 (Task 8b): ein zweiter Lauf ist ein echtes No-Op ==="
|
|
# Idempotenz ist die Kopfzusage von setup_pi.sh (siehe Kommentar am Dateianfang) - hier
|
|
# konkret fuer den neu hinzugekommenen Taster geprueft: ein zweiter Lauf auf einem
|
|
# bereits eingerichteten Pi darf button/ und dessen Unit nicht veraendern.
|
|
STATE="$TMPROOT/s33-state"; STUBBIN="$TMPROOT/bin33"; LOG="$TMPROOT/s33.log"
|
|
setup_state "$STATE"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$LOG"
|
|
set +e
|
|
run_setup "$STATE" "$STUBBIN" >/dev/null 2>&1
|
|
RC1=$?
|
|
set -e
|
|
[ "$RC1" -eq 0 ] || fail "erster Lauf schon fehlgeschlagen (rc=$RC1)"
|
|
SUM_BEFORE=$(find "$STATE/opt/ninlight-station/button" "$STATE/etc/systemd/system/ninlight-button.service" -type f -exec cksum {} \; | sort)
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
|
RC2=$?
|
|
set -e
|
|
[ "$RC2" -eq 0 ] && pass "zweiter Lauf: Exit-Code 0" || fail "zweiter Lauf: Exit-Code $RC2, Ausgabe: $OUT"
|
|
SUM_AFTER=$(find "$STATE/opt/ninlight-station/button" "$STATE/etc/systemd/system/ninlight-button.service" -type f -exec cksum {} \; | sort)
|
|
[ "$SUM_BEFORE" = "$SUM_AFTER" ] && pass "button/ und die Taster-Unit sind nach dem zweiten Lauf byte-identisch" || fail "der zweite Lauf hat etwas an button/ veraendert"
|
|
grep -Fq "systemctl enable ninlight-button.service" "$LOG" && pass "der zweite Lauf aktiviert den Tasterdienst erneut (idempotent, kein Fehler)" || fail "kein erneutes 'systemctl enable ninlight-button' im zweiten Lauf"
|
|
echo "$OUT" | grep -q "veraltete Datei entfernt: button/" && fail "der zweite Lauf meldet eine veraltete Datei, obwohl nichts geloescht wurde" || pass "keine falsche 'veraltete Datei entfernt'-Meldung im zweiten Lauf"
|
|
|
|
echo
|
|
if [ "$FAILED" -eq 0 ]; then
|
|
echo "Alle Tests bestanden."
|
|
else
|
|
echo "Mindestens ein Test ist fehlgeschlagen." >&2
|
|
fi
|
|
exit "$FAILED"
|