fix(station): RuntimeDirectory-Race, Statusphase, off/ap_switch-Nachpruefung, Pi-Bestandskonfiguration
Fixrunde 1 zu Task 4: - C1 (kritisch): die Unit leerte /run/ninlight-station bei jedem systemctl restart, weil RuntimeDirectoryPreserve fehlte - genau der Neustart, mit dem migrate/recovery on den Dienst hochfahren, wischte die eben geschriebene Modus-/Steuerdatei wieder weg, und der Server fiel still auf den Recovery-Default zurueck. Unit bekommt RuntimeDirectoryPreserve=restart, und start_and_verify() prueft nach dem Neustart zusaetzlich ausdruecklich nach, dass Modus und Phase noch stimmen. - I1: status las die Phase aus status.json, die arm/listen (die den Dienst bewusst nicht neu starten) nie aktualisieren - jetzt kommt sie aus control.json. - I2: off pruefte nach dem Herunterfahren nichts nach und konnte "Station aus" melden, waehrend ein Profil wegen haengendem NetworkManager weiterhin aktiv war - ap_switch() verifiziert das jetzt auch im Aus-Fall. - I3: der Fehlerzweig von ap_switch (Mismatch nach dem Umschalten) war im bisherigen Test nie erreichbar; der nmcli-Stub kann das jetzt erzwingen. - I4: neue Pflichtschluessel blockierten jeden Unterbefehl auf einem bereits eingerichteten Pi. setup_pi.sh ergaenzt fehlende Schluessel in einer bestehenden Konfiguration jetzt einzeln, statt sie unangetastet zu lassen; dafuer musste das Skript per bash-s-Heredocs und ueberschreibbaren Pfadvariablen erst testbar gemacht werden (neuer test_setup_pi.sh). Dazu vier kleinere Punkte aus derselben Review: grep -Fqx statt grep -qx fuer Profilnamen, install-Stub durch tee+chmod ersetzt (POSIX, kein Sonderfall mehr fuer macOS), Rechtepruefung auf control.json in den arm/listen-Testfaellen, stderr sauber von stdout getrennt in den Warnungs-/Fehlerfaellen.
This commit is contained in:
Executable
+196
@@ -0,0 +1,196 @@
|
||||
#!/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; }
|
||||
|
||||
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"
|
||||
args=()
|
||||
for a in "\$@"; do
|
||||
case "\$a" in
|
||||
-*) continue ;;
|
||||
*:*) args+=("\${a#*:}") ;;
|
||||
*) args+=("\$a") ;;
|
||||
esac
|
||||
done
|
||||
exec cp "\${args[@]}"
|
||||
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"
|
||||
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"
|
||||
case "\$1" in
|
||||
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
|
||||
}
|
||||
|
||||
# $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"
|
||||
}
|
||||
|
||||
# Fuehrt setup_pi.sh mit gestubbtem PATH und auf die Sandbox umgebogenen
|
||||
# Systempfaden aus. $1 = state-Verzeichnis, $2 = stub-bin-Verzeichnis.
|
||||
run_setup() {
|
||||
local state=$1 stubbin=$2
|
||||
PATH="$stubbin:$PATH" \
|
||||
NINLIGHT_SSH_BIN="$stubbin/ssh" \
|
||||
NINLIGHT_SCP_BIN="$stubbin/scp" \
|
||||
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_NM_DIR="$state/etc/NetworkManager/system-connections" \
|
||||
"$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=$(stat -f '%Lp' "$CONF_OUT" 2>/dev/null || stat -c '%a' "$CONF_OUT" 2>/dev/null)
|
||||
[ "$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
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
echo "Alle Tests bestanden."
|
||||
else
|
||||
echo "Mindestens ein Test ist fehlgeschlagen." >&2
|
||||
fi
|
||||
exit "$FAILED"
|
||||
Reference in New Issue
Block a user