diff --git a/tools/mkimage/tests/test_suite_coverage.py b/tools/mkimage/tests/test_suite_coverage.py index 11580d5..dfd53a8 100644 --- a/tools/mkimage/tests/test_suite_coverage.py +++ b/tools/mkimage/tests/test_suite_coverage.py @@ -64,6 +64,34 @@ def test_every_test_file_is_reached_by_test_host(): ) +def test_every_shell_suite_is_called_by_test_host(): + """Dieselbe Regel fuer die Shell-Suiten (Task 6b F1, I3). + + test_deploy.sh war in tools/test_host.sh nicht aufgefuehrt und lief damit nur bei + Handaufruf - 95 Zusagen, die niemand mehr sah. Die .py-Regel oben deckt genau diese + Haelfte nicht ab, weil die Shell-Suiten nicht ueber pytest laufen, sondern einzeln + aufgerufen werden. + """ + # Nur AUSFUEHRBARE Zeilen zaehlen, keine Kommentare: der Kommentarblock ueber den + # Aufrufen nennt test_deploy.sh beim Namen, und eine blosse Textsuche waere damit + # auch dann erfuellt, wenn die Aufrufzeile fehlt - der Test waere gruen, ohne etwas + # zu pruefen (genau die Falle, die er verhindern soll). + lines = [ + line.strip() + for line in TEST_HOST.read_text().splitlines() + if line.strip() and not line.strip().startswith("#") + ] + missing = [] + for suite in sorted((TOOLS_DIR / "station" / "tests").glob("test_*.sh")): + # So, wie test_host.sh sie aufruft: "$ROOT/tools/station/tests/" + if not any(suite.name in line for line in lines): + missing.append(suite.relative_to(REPO_ROOT)) + assert not missing, ( + "diese Shell-Suiten ruft tools/test_host.sh nicht auf, sie laufen also nur bei " + "Handaufruf: {}".format(", ".join(str(m) for m in missing)) + ) + + def test_python38_guard_is_among_them(): """Die konkrete Datei, deren Ausfall den Build-Bruch damals durchgelassen haette.""" guard = (TOOLS_DIR / "mkimage" / "test_nl_mkimage.py").resolve() @@ -76,11 +104,6 @@ def test_python38_guard_is_among_them(): ) -def test_vermin_is_installed(): - """vermin fehlt -> harter Fehlschlag, kein Skip. - - Eine Wache, die sich beim Fehlen ihres Werkzeugs stillschweigend abschaltet, haette - den Build-Bruch von damals genauso durchgelassen wie gar kein Test. Nachinstallieren: - pip install vermin (oder pip3 install --user vermin). - """ - import vermin # noqa: F401 +# Kein eigener "ist vermin installiert"-Test hier: test_nl_mkimage.py importiert vermin +# ohnehin hart (kein Skip), und ein fehlendes Paket faellt dort mit derselben Deutlichkeit +# auf. Zwei Tests fuer dieselbe Zusage sind nur zwei Stellen zum Pflegen. diff --git a/tools/station/setup_pi.sh b/tools/station/setup_pi.sh index fbab5d4..a6a0f3f 100755 --- a/tools/station/setup_pi.sh +++ b/tools/station/setup_pi.sh @@ -77,24 +77,21 @@ fi rm -f "$TEMPLATE" REMOTE -echo "== Pakete ==" -# Pillow kommt auf dem Pi bewusst aus dem Distributionspaket (python3-pil), nicht per -# pip: tools/station/ laeuft dort unter dem System-Python 3.13, und Debian 13 laesst ein -# "pip install" ins System-Python gar nicht mehr zu (PEP 668). Ohne Pillow zeichnet die -# Anzeige nicht - display.py faengt den ImportError ab und beendet sich still mit 0 -# (Absicht, damit ein fehlendes Panel nie eine Migration verhindert). Genau deshalb muss -# das Paket HIER kommen: sonst faellt es nirgends auf. +echo "== Server, CLI und Units installieren ==" +# Reststand der Ablage wegraeumen, BEVOR das scp laeuft (Task 6b F1, I1). "scp -r quelle +# ziel" kopiert das Quellverzeichnis IN ein bereits vorhandenes Ziel hinein - aus +# einem liegengebliebenen "…/ninlight-display/" wird dann "…/ninlight-display/display/", +# die Installationsschleife unten ueberspringt Verzeichnisse und findet gar nichts mehr. +# Ergebnis waere ein Lauf, der Erfolg meldet, den Anzeigedienst aktiviert und ein leeres +# $ROOT_DIR/display hinterlaesst: 203/EXEC im 5-Sekunden-Takt, Panel schwarz. Genau so +# ein Reststand entsteht, wenn ein frueherer Lauf nach dem scp abbricht (sudo-Timeout, +# Platte voll, Strg-C) - die Aufraeumzeile unten laeuft dann nicht mehr. Idempotent +# heisst, dass auch dieser Fall glattgeht (siehe Kopf dieser Datei). "$SSH_BIN" "$HOST" bash -s <<'REMOTE' set -e -if python3 -c 'import PIL' 2>/dev/null; then - echo " python3-pil ist bereits vorhanden" -else - sudo apt-get install -y python3-pil - echo " python3-pil installiert" -fi +STAGE_DIR=${NINLIGHT_STAGE_DIR:-/tmp} +rm -rf "$STAGE_DIR/ninlight-display" REMOTE - -echo "== Server, CLI und Units installieren ==" "$SCP_BIN" -q "$HERE/server.py" "$HERE/products.py" "$HERE/ninlight-station" \ "$HERE/ninlight-station.service" "$HERE/ninlight-display.service" "$HOST:$STAGE_DIR/" # Das Anzeigeverzeichnis als Ganzes: render.py, display.py, epdconfig.py, die beiden @@ -120,6 +117,15 @@ sudo install -m 0644 "$STAGE_DIR/ninlight-station.service" "$UNIT_PATH" # Anzeige: display.py ist ausfuehrbar (die Unit ruft es zwar ueber /usr/bin/python3 auf, # "ninlight-station display test" aber auch von Hand), der Rest ist importierter Code. rm -rf "$STAGE_DIR/ninlight-display/__pycache__" +# Harte Vorbedingung (Task 6b F1, I1): ohne display.py in der Ablage hat das scp nicht +# geliefert, was es sollte - abgebrochen, falsch verschachtelt oder gar nicht gelaufen. +# Die Schleife darunter wuerde das stillschweigend als "nichts zu tun" verbuchen, der +# Lauf meldete Erfolg und der aktivierte Anzeigedienst liefe gegen eine nicht +# existierende Datei. Lieber hier laut abbrechen. +if [ ! -f "$STAGE_DIR/ninlight-display/display.py" ]; then + echo "Fehler: $STAGE_DIR/ninlight-display/display.py fehlt - die Uebertragung des Anzeigeverzeichnisses ist fehlgeschlagen. Nichts installiert." >&2 + exit 1 +fi for f in "$STAGE_DIR"/ninlight-display/*; do [ -f "$f" ] || continue case "$(basename "$f")" in @@ -127,21 +133,76 @@ for f in "$STAGE_DIR"/ninlight-display/*; do *) sudo install -m 0644 "$f" "$ROOT_DIR/display/$(basename "$f")" ;; esac done +# Was im Repo geloescht wurde, muss auch hier verschwinden - dasselbe Argument wie in +# deploy.sh (dort per "rsync --delete"): display/ ist reiner Code aus dem Repo, es gibt +# keinen Bestand, der einen Lauf ueberleben muesste, und ein zurueckgebliebenes altes +# Modul wuerde weiter mit importiert. rsync steht hier nicht zur Verfuegung (setup_pi.sh +# setzt ausdruecklich nur ssh/scp voraus), also von Hand. +for f in "$ROOT_DIR"/display/*; do + [ -e "$f" ] || continue + if [ ! -e "$STAGE_DIR/ninlight-display/$(basename "$f")" ]; then + sudo rm -rf "$f" + echo " veraltete Datei entfernt: display/$(basename "$f")" + fi +done sudo install -m 0644 "$STAGE_DIR/ninlight-display.service" "$DISPLAY_UNIT_PATH" rm -rf "$STAGE_DIR/server.py" "$STAGE_DIR/products.py" "$STAGE_DIR/ninlight-station" \ "$STAGE_DIR/ninlight-station.service" "$STAGE_DIR/ninlight-display.service" \ "$STAGE_DIR/ninlight-display" sudo systemctl daemon-reload +echo " installiert" +REMOTE + +echo "== Pakete ==" +# Pillow kommt auf dem Pi bewusst aus dem Distributionspaket (python3-pil), nicht per +# pip: tools/station/ laeuft dort unter dem System-Python 3.13, und Debian 13 laesst ein +# "pip install" ins System-Python gar nicht mehr zu (PEP 668). Ohne Pillow zeichnet die +# Anzeige nicht - display.py faengt den ImportError ab und beendet sich still mit 0 +# (Absicht, damit ein fehlendes Panel nie eine Migration verhindert). Genau deshalb muss +# das Paket ueberhaupt hier kommen: sonst faellt es nirgends auf. +# +# Aber NACH der Installation und nur als Warnung (Task 6b F1, I4): eine Migrationsstation +# steht typischerweise nicht am Netz, und mit veralteten Paketlisten oder ohne Internet +# scheitert apt. Unter "set -e" vor der Installation haette das den Lauf abgebrochen, +# bevor server.py, das CLI, beide Units und beide NetworkManager-Profile installiert sind +# - man haette dann GAR KEINE Station statt einer Station ohne Panel. Das waere die +# einzige harte Stelle in einer sonst durchgehend weichen Kette gewesen (display.py +# beendet sich mit 0, deploy.sh warnt nur, enable/restart unten warnen nur). +"$SSH_BIN" "$HOST" bash -s <<'REMOTE' +set -e +if python3 -c 'import PIL' 2>/dev/null; then + echo " python3-pil ist bereits vorhanden" +else + # "update" zuerst: auf einem laenger nicht gepflegten Pi zeigen die Paketlisten auf + # Versionen, die es im Archiv nicht mehr gibt, und "install" scheitert mit 404. + # Beide Aufrufe duerfen nur warnen - siehe oben. + if sudo apt-get update >/dev/null 2>&1 && sudo apt-get install -y python3-pil; then + echo " python3-pil installiert" + else + echo " Warnung: python3-pil liess sich nicht installieren (kein Netz? veraltete Paketlisten?)." >&2 + echo " Die Station ist vollstaendig eingerichtet, das E-Paper bleibt aber leer," >&2 + echo " bis 'sudo apt install python3-pil' durchlaeuft und der Anzeigedienst neu startet." >&2 + fi +fi +REMOTE + +echo "== Anzeigedienst aktivieren ==" # Die Anzeige wird aktiviert (startet also beim Booten mit) UND jetzt gestartet - anders # als die Station selbst, die absichtlich aus bleibt: die Anzeige sendet nichts, sie # zeigt nur an, und "Station aus" ist genau das, was nach dem Einrichten auf dem Panel -# stehen soll. Fehlt Panel oder Pillow, beendet sich der Dienst still mit 0 (siehe -# display.py) - deshalb "|| true": ein fehlendes Panel darf setup_pi.sh nicht abbrechen. +# stehen soll. Erst hier, nach dem Paketschritt: so startet der Dienst bereits mit +# vorhandenem Pillow, statt sich beim ersten Zeichnen still mit 0 zu beenden und bis zum +# naechsten manuellen Neustart nichts anzuzeigen. +# +# Fehlt Panel oder Pillow, beendet sich der Dienst still mit 0 (siehe display.py) - +# deshalb nur Warnungen: ein fehlendes Panel darf setup_pi.sh nicht abbrechen. +"$SSH_BIN" "$HOST" bash -s <<'REMOTE' +set -e sudo systemctl enable ninlight-display.service >/dev/null 2>&1 || \ - echo " Warnung: 'systemctl enable ninlight-display' fehlgeschlagen" >&2 + echo " Warnung: 'systemctl enable ninlight-display' fehlgeschlagen - die Anzeige startet nach einem Neustart des Pi nicht von allein mit" >&2 sudo systemctl restart ninlight-display.service >/dev/null 2>&1 || \ - echo " Warnung: 'systemctl restart ninlight-display' fehlgeschlagen" >&2 -echo " installiert (Anzeigedienst aktiviert)" + echo " Warnung: 'systemctl restart ninlight-display' fehlgeschlagen - journalctl -u ninlight-display" >&2 +echo " Anzeigedienst aktiviert" REMOTE echo "== NetworkManager-Profile ==" @@ -171,8 +232,20 @@ eval "$(sudo cat "$CONF")" # "$tmp" ist "local" - beim Auswerten des Traps am Skriptende gaebe es die # Variable gar nicht mehr. Deshalb ein gemeinsames Verzeichnis mit genau einem # Trap darum herum (0700, mktemp -d legt es so an). -PROFILE_TMPDIR=$(mktemp -d) -trap 'rm -rf "$PROFILE_TMPDIR"' EXIT +# +# Auch auf INT/TERM/HUP, nicht nur EXIT: bash fuehrt den EXIT-Trap zwar nach einem +# abgefangenen Signal aus, aber nur, wenn das Signal ueberhaupt abgefangen wird - ohne +# eigenen Handler beendet ein Strg-C (INT) oder ein abbrechendes ssh (HUP) die Shell +# sofort, und der EXIT-Trap laeuft nie. Genau dann bliebe die Datei liegen. +# +# Mit ausdruecklicher Vorlage unter ${TMPDIR:-/tmp}, nicht als blankes "mktemp -d": +# BSD-mktemp (macOS, wo diese Suite auch laeuft) beachtet TMPDIR ohne Vorlage gar nicht +# und legt unabhaengig davon unter /var/folders/... an, GNU-mktemp (der Pi) dagegen +# schon. Ohne Vorlage landet das Verzeichnis also je nach Plattform woanders - ein Test, +# der nachsieht, ob wirklich nichts liegengeblieben ist, sucht dann auf macOS an der +# falschen Stelle und ist gruen, ohne etwas geprueft zu haben. +PROFILE_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/ninlight-profile.XXXXXX") +trap 'rm -rf "$PROFILE_TMPDIR"' EXIT INT TERM HUP # $1 = Profilname, $2 = SSID, $3 = PSK, $4 = "update" (bestehendes Profil neu schreiben, # UUID behalten) oder "keep" (bestehendes Profil unangetastet lassen). diff --git a/tools/station/tests/test_cli.sh b/tools/station/tests/test_cli.sh index fbd70e6..2651214 100755 --- a/tools/station/tests/test_cli.sh +++ b/tools/station/tests/test_cli.sh @@ -136,6 +136,15 @@ case "\$1" in # RuntimeDirectoryPreserve=restart in der Unit leert systemd das # RuntimeDirectory bei JEDEM Stop/Restart, nicht nur beim allerersten Start, # und legt es leer wieder an. + # + # ACHTUNG, Stub-Erfindung (Task 6b F1): die Regel wird hier auf JEDE + # Unit angewandt, also auch auf ninlight-display - die traegt aber gar kein + # RuntimeDirectory=, ein "stop ninlight-display" raeumt in Wirklichkeit nichts + # weg. \$UNIT_FILE ist immer ninlight-station.service, unabhaengig davon, welche + # Unit im Aufruf steht. Heute folgenlos (kein Testfall stoppt die Anzeige und + # erwartet danach noch Dateien in RUN_DIR), aber niemand darf sich darauf + # verlassen: wer kuenftig prueft, was ein "display off" mit RUN_DIR macht, muss + # den Stub vorher nach Unit unterscheiden lassen. if ! grep -q '^RuntimeDirectoryPreserve=restart' "\$UNIT_FILE" 2>/dev/null; then rm -rf "\$RUN_DIR" mkdir -p "\$RUN_DIR" diff --git a/tools/station/tests/test_setup_pi.sh b/tools/station/tests/test_setup_pi.sh index 2d9ec1b..7e3f410 100755 --- a/tools/station/tests/test_setup_pi.sh +++ b/tools/station/tests/test_setup_pi.sh @@ -52,6 +52,14 @@ EOF cat >"$bindir/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 ;; + esac +done args=() for a in "\$@"; do case "\$a" in @@ -73,7 +81,14 @@ EOF cat >"$bindir/apt-get" <>"$log" -exit "\${FAKE_APT_EXIT:-0}" +# 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 @@ -99,6 +114,14 @@ EOF cat >"$bindir/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 @@ -117,7 +140,11 @@ EOF cat >"$bindir/systemctl" <>"$log" +# Testschalter: laesst "enable"/"restart" scheitern (beides darf setup_pi.sh nur +# warnen lassen, nie abbrechen). 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 @@ -155,7 +182,13 @@ setup_state() { # "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" \ @@ -430,6 +463,156 @@ 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" + +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 if [ "$FAILED" -eq 0 ]; then echo "Alle Tests bestanden." diff --git a/tools/test_host.sh b/tools/test_host.sh index edefecc..d6b9696 100755 --- a/tools/test_host.sh +++ b/tools/test_host.sh @@ -24,5 +24,11 @@ else echo " tools/mkimage/test_nl_mkimage.py::test_stays_python38_compatible)" >&2 fi +# Alle Shell-Suiten unter tools/station/tests/ - test_deploy.sh fehlte hier und lief +# damit nur bei Handaufruf (Task 6b F1, I3): dieselbe Klasse von Luecke wie die aus +# keiner Suite gefallene test_nl_mkimage.py, nur eine Ebene hoeher. Mit ihr liefen 95 +# Zusagen nirgends mit, darunter die komplette Deploy-Haelfte der Anzeige-Auslieferung. +# tools/mkimage/tests/test_suite_coverage.py haelt fest, dass hier keine Suite fehlt. "$ROOT/tools/station/tests/test_cli.sh" +"$ROOT/tools/station/tests/test_deploy.sh" "$ROOT/tools/station/tests/test_setup_pi.sh"