fix(display): Freigabe-Ziel lesbar, Tests schriftunabhaengig
Sechs Review-Befunde an e2d836a/2aab1b0. C1: die Pixelschwellen in test_render.py waren gegen Pillow 12/Arial kalibriert und auf dem Pi wie auf Debian 13 (DejaVu) rot - "> 150 schwarze Pixel in Zeile 11" sind dort nur 143. Die Zusagen ueber den Kopfbalken sind jetzt Anteile und Symmetrien statt Pixelzahlen (mindestens 55 % des Balkens schwarz, weisse Schrift links UND rechts der Mitte, Balken ueber die volle Breite). Zwei weitere schriftabhaengige Schwellen ebenso: die grosse Zeile hebt sich ueber das VERHAELTNIS der Tintenhoehen vom Beiwerk ab, und die Randgrenze zaehlt Zeilenbaender statt Tinten-Cluster. I2: im Zustand "armed" stand "FREIGEGEBEN: <ziel>" in der Kopfzeile, wo 112 px Platz sind und der Text 182 px misst - gezeichnet wurde "FREIGEGEBE…", bei jedem Ziel gleich. Im Kopf steht jetzt nur noch das Label, der Zielname als eigene Beiwerkzeile ueber die volle Breite; die SSID-Zeile gibt dafuer in "armed" ihren Platz ab. Geprueft wird das gezeichnete Bild (Pixelvergleich gegen eine Referenzzeile), nicht lines(). I3: der Kommentar an display_handover() begruendete die Rueckgabewert-Durchreichung damit, dass der Handgriff bei stummem Panel rot werde. Das stimmt nicht - display.py beendet sich bei fehlendem Panel/SPI/Pillow mit 0. Der Kommentar sagt jetzt, dass man die Revision am Panel beurteilt und nicht am Exit-Code. I4: zwischen "systemctl stop" und "systemctl start" gab es keinen Signalhandler - ein Strg-C waehrend "display test" (der dokumentierte Weg, die Revision zu bestimmen, bei falscher Revision haengt das Panel) liess den Anzeigedienst gestoppt zurueck. trap auf INT/TERM/HUP, das Wiederanfahren in display_service_resume(), Handler beenden selbst mit 128 + Signalnummer. M2: HEADER_PHASE_MIN_WIDTH_PX bleibt als Sicherheitsnetz, ist jetzt aber getestet statt toter Code. M4: compose() baute je Aufruf fuenf Schriften neu. Prozess-Cache (mit ImageFont-Modul und FONT_SEARCH_PATHS im Schluessel, damit die Tests nicht verfaelscht werden) und faules Laden je Rolle: 25,0 ms -> 7,0 ms. Die Pixeltests laufen zusaetzlich unter Pillow 11.1 mit echtem DejaVu und unter fuenf weiteren Schriften. Sieben Mutationsproben angewandt, angesehen und zurueckgenommen. tools/test_host.sh gruen.
This commit is contained in:
@@ -198,11 +198,35 @@ setup_sandbox() {
|
||||
cp "$STATION_DIR/products.py" "$sandbox/lib/products.py"
|
||||
cat >"$sandbox/lib/display/display.py" <<EOF
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
with open("$sandbox/cmd.log", "a") as fh:
|
||||
fh.write("STUB display.py %s (EPD_REVISION=%s)\n"
|
||||
% (" ".join(sys.argv[1:]), os.environ.get("EPD_REVISION", "<unset>")))
|
||||
|
||||
# Testschalter fuer Fall D2c: stellt das Strg-C des Abnehmers nach, waehrend display.py
|
||||
# laeuft (bei falscher Panel-Revision kehrt ReadBusy() nie zurueck - das ist der
|
||||
# dokumentierte Weg, die Revision zu bestimmen, und damit der Regelfall fuer ein Strg-C).
|
||||
#
|
||||
# Nachgestellt wird, was das Terminal wirklich tut: das Signal geht an die GANZE
|
||||
# Vordergrund-Prozessgruppe, also an die ninlight-station-Shell UND an diesen Prozess.
|
||||
# Der Elternprozess ist dabei die Shell selbst ("sudo" und "env" sind Stub bzw.
|
||||
# exec-Aufruf, der Python-Prozess haengt also direkt unter ihr).
|
||||
#
|
||||
# Dass dieser Prozess AM SIGNAL STIRBT (Standardbehandlung) statt sich mit 128+n zu
|
||||
# beenden, ist kein Detail: eine Shell unterscheidet beides. Beendet sich das Kind
|
||||
# normal, verwirft /bin/sh (bash im sh-Modus, macOS) das inzwischen zugestellte SIGINT
|
||||
# und laeuft weiter - der Testfall waere dann auch ohne Signalhandler in
|
||||
# display_handover() gruen und wuerde gar nichts pruefen (beim Schreiben dieses Tests
|
||||
# genau so passiert).
|
||||
signal_name = os.environ.get("FAKE_DISPLAY_SIGNAL", "")
|
||||
if signal_name:
|
||||
number = getattr(signal, "SIG" + signal_name)
|
||||
os.kill(os.getppid(), number)
|
||||
signal.signal(number, signal.SIG_DFL)
|
||||
os.kill(os.getpid(), number)
|
||||
|
||||
sys.exit(int(os.environ.get("FAKE_DISPLAY_EXIT", "0")))
|
||||
EOF
|
||||
|
||||
@@ -672,6 +696,62 @@ set -e
|
||||
[ "$RC" -eq 1 ] && pass "Exit-Code 1 wird durchgereicht" || fail "Exit-Code $RC statt 1: $OUT"
|
||||
grep -Fq "STUB systemctl start ninlight-display" "$SBX/cmd.log" && pass "Anzeigedienst laeuft trotz Panel-Fehler wieder" || fail "Anzeigedienst blieb nach dem Fehler aus: $(cat "$SBX/cmd.log")"
|
||||
|
||||
echo
|
||||
echo "=== Fall D2c: Strg-C waehrend 'display test' laesst den Anzeigedienst NICHT gestoppt zurueck ==="
|
||||
# Der Ablauf aus der Abnahme: bei falscher Revision kehrt ReadBusy() nie zurueck
|
||||
# (display.py-Docstring), der Abnehmer bricht mit Strg-C ab. Ohne Signalhandler zwischen
|
||||
# "stop" und "start" stirbt display_handover mit - der Anzeigedienst bleibt aus, das
|
||||
# Panel schwarz, und das sieht mitten in der Lampenabnahme aus wie "Panel kaputt".
|
||||
# Geprueft wird fuer alle drei Signale, die das ausloesen koennen.
|
||||
for SIG_NAME in INT TERM HUP; do
|
||||
case "$SIG_NAME" in
|
||||
INT) EXPECT_RC=130 ;;
|
||||
TERM) EXPECT_RC=143 ;;
|
||||
HUP) EXPECT_RC=129 ;;
|
||||
esac
|
||||
SBX="$TMPROOT/tD2c$SIG_NAME"
|
||||
setup_sandbox "$SBX"
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(FAKE_DISPLAY_SIGNAL="$SIG_NAME" run_cli "$SBX" display test 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
grep -Fq "STUB systemctl stop ninlight-display" "$SBX/cmd.log" \
|
||||
&& pass "SIG$SIG_NAME: der Anzeigedienst war fuer den Handgriff gestoppt (Vorbedingung)" \
|
||||
|| fail "SIG$SIG_NAME: Vorbedingung fehlt, der Dienst wurde gar nicht erst gestoppt: $(cat "$SBX/cmd.log")"
|
||||
# GENAU einmal gestartet. Die Anzahl, nicht nur das Vorhandensein: der Handler muss
|
||||
# selbst beenden (in diesem Projekt schon einmal ein Befund, setup_pi.sh). Ohne "exit"
|
||||
# laeuft die Shell hinter dem unterbrochenen Kommando einfach weiter und faehrt den
|
||||
# Dienst ein zweites Mal an - der Exit-Code sieht dabei zufaellig richtig aus (er ist
|
||||
# dann der des am Signal gestorbenen Kindes), die doppelte Startzeile nicht. Genau
|
||||
# diese Mutation ("trap ohne exit") war gegen eine blosse Vorhandensein-Pruefung
|
||||
# gruen.
|
||||
START_COUNT=$(grep -c "STUB systemctl start ninlight-display" "$SBX/cmd.log" || true)
|
||||
[ "$START_COUNT" -eq 1 ] \
|
||||
&& pass "SIG$SIG_NAME: der Anzeigedienst wird trotz Abbruch genau einmal wieder gestartet" \
|
||||
|| fail "SIG$SIG_NAME: $START_COUNT Startversuche (1 erwartet) - Anzeigedienst bleibt aus oder der Handler beendet nicht selbst: $(cat "$SBX/cmd.log")"
|
||||
[ "$RC" -eq "$EXPECT_RC" ] \
|
||||
&& pass "SIG$SIG_NAME: Exit-Code $EXPECT_RC (128 + Signalnummer)" \
|
||||
|| fail "SIG$SIG_NAME: Exit-Code $RC statt $EXPECT_RC: $OUT"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "=== Fall D2d: war der Anzeigedienst aus, startet ihn auch ein Strg-C nicht ==="
|
||||
# Spiegelbild zu D2a: der Signalhandler darf den Zustand nicht anders behandeln als der
|
||||
# normale Ausgang - sonst schaltete ein abgebrochener Handgriff die Anzeige ein, die
|
||||
# 'off' zuvor bewusst ausgeschaltet hat.
|
||||
SBX="$TMPROOT/tD2d"
|
||||
setup_sandbox "$SBX"
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(FAKE_DISPLAY_SERVICE_ACTIVE_EXIT=3 FAKE_DISPLAY_SIGNAL=INT run_cli "$SBX" display test 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 130 ] && pass "Exit-Code 130 auch ohne laufenden Anzeigedienst" || fail "Exit-Code $RC statt 130: $OUT"
|
||||
grep -Fq "STUB systemctl start ninlight-display" "$SBX/cmd.log" \
|
||||
&& fail "der Signalhandler hat den Anzeigedienst eingeschaltet, obwohl er aus war: $(cat "$SBX/cmd.log")" \
|
||||
|| pass "kein 'start' fuer einen Dienst, der gar nicht lief"
|
||||
|
||||
echo
|
||||
echo "=== Fall D3 (Task 6b, Punkt 4): 'display off' stoppt den Dienst VOR dem Leeren des Panels ==="
|
||||
# Umgekehrte Reihenfolge waere wirkungslos: der Daemon fragt alle 5 s nach und
|
||||
|
||||
Reference in New Issue
Block a user