test(station): Ausfallpfad der Anzeige in allen vier Modi festhalten
Die Zusage "fehlt Panel, SPI, Pillow oder Schrift: genau eine Protokollzeile und sys.exit(0)" war ungetestet - das Abschlussreview hat sys.exit(0) durch sys.exit(1) ersetzt und alle 225 Tests blieben gruen. Die vorhandenen Tests pruefen nur, dass die richtigen Ausnahmetypen bis main() durchkommen, niemand hat main() selbst in den except-Zweig getrieben. Acht neue Faelle: DisplayUnavailable und ImportError, je fuer --test, --clear, --final und den Daemon. Mutationsproben: exit 0 -> 1 macht alle acht rot, die Ausnahmeliste auf DisplayUnavailable verengt macht die vier ImportError-Faelle rot.
This commit is contained in:
@@ -1988,3 +1988,79 @@ def test_display_main_passes_the_final_kind_through(monkeypatch):
|
|||||||
assert len(drawn) == 1
|
assert len(drawn) == 1
|
||||||
expected = render.compose({"mode": "final", "final": kind})
|
expected = render.compose({"mode": "final", "final": kind})
|
||||||
assert list(drawn[0].getdata()) == list(expected.getdata()), kind
|
assert list(drawn[0].getdata()) == list(expected.getdata()), kind
|
||||||
|
|
||||||
|
|
||||||
|
# --- Die Entbehrlichkeitszusage der Anzeige (Abschlussreview, I1) --------------------
|
||||||
|
#
|
||||||
|
# Zusage aus dem Modul-Docstring, ueber fuenf Fixrunden erkaempft: fehlt Panel, SPI,
|
||||||
|
# Pillow oder Schrift, gibt es GENAU EINE Protokollzeile und sys.exit(0). Der Grund ist
|
||||||
|
# Restart=on-failure mit RestartSec=5 in ninlight-display.service - ein Exit != 0 macht
|
||||||
|
# daraus auf dem Zero W eine Neustartschleife, die das Journal flutet, und der Sinn der
|
||||||
|
# Zusage ("ein Fehler in der Anzeige darf niemals eine Migration stoeren") faellt still
|
||||||
|
# weg.
|
||||||
|
#
|
||||||
|
# Das Abschlussreview hat belegt, dass diese Zusage ungetestet war: sys.exit(0) ->
|
||||||
|
# sys.exit(1) liess alle 225 Tests gruen. Die vorhandenen Tests pruefen nur, dass die
|
||||||
|
# richtigen Ausnahmetypen bis main() DURCHKOMMEN - niemand hat main() selbst in den
|
||||||
|
# except-Zweig getrieben und auf den Rueckgabewert gesehen. Beim Tasterdienst ist
|
||||||
|
# dasselbe laengst getestet (tests/test_button.py, test_missing_gpiozero_...).
|
||||||
|
#
|
||||||
|
# Alle vier Modi, weil jeder sein eigenes run_*() hat: --test, --clear, --final und der
|
||||||
|
# Daemon. Alle vier bauen ihr Panel selbst, ein Panel(), das schon im Konstruktor wirft,
|
||||||
|
# deckt daher jeden der vier Einstiege ab.
|
||||||
|
|
||||||
|
_DISPLAY_MODES = [
|
||||||
|
pytest.param(["display.py", "--test"], id="test"),
|
||||||
|
pytest.param(["display.py", "--clear"], id="clear"),
|
||||||
|
pytest.param(["display.py", "--final", "poweroff"], id="final"),
|
||||||
|
pytest.param(["display.py"], id="daemon"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _panel_raising(exc):
|
||||||
|
class _Panel:
|
||||||
|
def __init__(self, revision):
|
||||||
|
raise exc
|
||||||
|
return _Panel
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("argv", _DISPLAY_MODES)
|
||||||
|
def test_missing_panel_logs_exactly_one_line_and_exits_zero(argv, monkeypatch, caplog):
|
||||||
|
"""Kein Panel/SPI -> DisplayUnavailable -> eine Zeile, Exit 0 - in JEDEM Modus."""
|
||||||
|
monkeypatch.setattr(display, "Panel",
|
||||||
|
_panel_raising(epdconfig.DisplayUnavailable("kein SPI-Geraet")))
|
||||||
|
monkeypatch.setattr(sys, "argv", argv)
|
||||||
|
monkeypatch.setenv("EPD_REVISION", "V3")
|
||||||
|
|
||||||
|
with caplog.at_level("INFO", logger="ninlight-display"):
|
||||||
|
with pytest.raises(SystemExit) as excinfo:
|
||||||
|
display.main()
|
||||||
|
|
||||||
|
assert excinfo.value.code == 0, (
|
||||||
|
f"{argv[1:] or ['daemon']}: Exit {excinfo.value.code} statt 0 - "
|
||||||
|
"Restart=on-failure macht daraus eine Neustartschleife")
|
||||||
|
assert len(caplog.records) == 1, \
|
||||||
|
f"erwartet genau eine Protokollzeile, bekommen: {[r.getMessage() for r in caplog.records]}"
|
||||||
|
assert "kein SPI-Geraet" in caplog.records[0].getMessage()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("argv", _DISPLAY_MODES)
|
||||||
|
def test_missing_pillow_logs_exactly_one_line_and_exits_zero(argv, monkeypatch, caplog):
|
||||||
|
"""Der zweite Ast derselben Zusage (Task 6a, C4): Pillow kommt auf dem Ziel-Pi erst
|
||||||
|
per "apt install python3-pil" nach, render.compose() importiert es erst beim
|
||||||
|
Zeichnen. Ein ModuleNotFoundError (ImportError-Unterklasse) muss deshalb genauso
|
||||||
|
enden wie ein fehlendes Panel - und nicht als Absturz."""
|
||||||
|
monkeypatch.setattr(display, "Panel",
|
||||||
|
_panel_raising(ModuleNotFoundError("No module named 'PIL'")))
|
||||||
|
monkeypatch.setattr(sys, "argv", argv)
|
||||||
|
monkeypatch.setenv("EPD_REVISION", "V3")
|
||||||
|
|
||||||
|
with caplog.at_level("INFO", logger="ninlight-display"):
|
||||||
|
with pytest.raises(SystemExit) as excinfo:
|
||||||
|
display.main()
|
||||||
|
|
||||||
|
assert excinfo.value.code == 0, (
|
||||||
|
f"{argv[1:] or ['daemon']}: Exit {excinfo.value.code} statt 0")
|
||||||
|
assert len(caplog.records) == 1, \
|
||||||
|
f"erwartet genau eine Protokollzeile, bekommen: {[r.getMessage() for r in caplog.records]}"
|
||||||
|
assert "PIL" in caplog.records[0].getMessage()
|
||||||
|
|||||||
Reference in New Issue
Block a user