fix(station): ehrliche Abbruchmeldung, apt-update darf scheitern

Fixrunde 2 zu Task 6b.

- Die Vorbedingung "display.py liegt in der Ablage" steht jetzt VOR dem
  ersten sudo install. Vorher stand sie dahinter, und die Meldung "Nichts
  installiert" war schlicht unwahr: server.py, products.py, das CLI und
  die Stations-Unit lagen dann schon auf dem Pi. Ausgerechnet an dem
  Werkbank-Moment, fuer den die Pruefung gedacht ist, log das Skript den
  Bediener an. Die Ablage raeumt ein EXIT-Trap jetzt auf beiden Wegen aus
  dem Block ab, nicht nur im Erfolgsfall.
- "apt-get update" ist keine Voraussetzung mehr fuer "apt-get install":
  update meldet schon dann einen Fehler, wenn eine einzige Quelle nicht
  erreichbar ist, und ein Pi, auf dem das Paket aus dem Cache gekommen
  waere, bekam so still kein Pillow.
- Die Signalhandler beenden jetzt selbst (130/143/129). Ein Handler ohne
  exit laesst bash weiterlaufen - ein Strg-C konnte in "fertig, Exit 0"
  muenden. Die Begruendung im Kommentar war falsch und ist korrigiert:
  zum Aufraeumen haette der EXIT-Trap allein genuegt.
- test_host.sh ruft die Shell-Suiten aus einer Liste auf, und der
  Coverage-Test prueft diese Liste gegen das Dateisystem statt im
  Skripttext nach Dateinamen zu suchen - ein nachgestellter Kommentar
  hebelte die alte Regel aus. Er deckt jetzt ganz tools/ ab, nicht nur
  station/tests.
- Neue Szenarien: Wahrheitsgehalt der Abbruchmeldung, apt-update-Fall,
  und ein Strg-C, das aus den Trap-Zeilen des Skripts selbst geprueft
  wird.
This commit is contained in:
Michael Sauer
2026-09-14 00:45:04 +02:00
parent 7fdccbce72
commit 47532a6820
4 changed files with 189 additions and 45 deletions
+70
View File
@@ -501,6 +501,21 @@ set -e
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 ==="
@@ -613,6 +628,61 @@ echo "$ERR" | grep -q "systemctl enable ninlight-display" && pass "Warnung zu 'e
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"
: >"$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
if [ "$FAILED" -eq 0 ]; then
echo "Alle Tests bestanden."