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
+50 -17
View File
@@ -41,14 +41,42 @@ def pytest_paths():
return paths
def own_test_files():
def own_test_files(suffix=".py"):
return sorted(
p.resolve()
for p in TOOLS_DIR.rglob("test_*.py")
for p in TOOLS_DIR.rglob("test_*" + suffix)
if not any(part in IGNORED_PARTS for part in p.parts)
)
def listed_shell_suites():
"""Die Eintraege des SHELL_SUITES-Feldes in tools/test_host.sh, aufgeloest.
Ausdruecklich gegen das FELD geprueft und nicht per Textsuche nach Dateinamen: eine
Textsuche ist schon durch einen nachgestellten Kommentar ("echo x # test_deploy.sh"),
ein ": \"test_deploy.sh\"" oder einen Heredoc-Inhalt erfuellt - sie belegt nur, dass
der Name irgendwo vorkommt, nicht dass die Suite laeuft (Task 6b F2).
"""
text = TEST_HOST.read_text()
block = re.search(r"^SHELL_SUITES=\((.*?)^\)", text, re.M | re.S)
assert block, (
"in tools/test_host.sh gibt es kein 'SHELL_SUITES=(...)'-Feld mehr - die "
"Vollstaendigkeitspruefung der Shell-Suiten haengt daran"
)
# Und das Feld muss auch wirklich abgearbeitet werden, sonst ist es nur Deko.
assert re.search(r'for\s+\w+\s+in\s+"\$\{SHELL_SUITES\[@\]\}"', text), (
"tools/test_host.sh arbeitet SHELL_SUITES nicht in einer Schleife ab - die "
"aufgefuehrten Suiten wuerden gar nicht laufen"
)
paths = []
for raw in block.group(1).split():
arg = raw.strip('"')
if not arg or arg.startswith("#"):
continue
paths.append(Path(arg.replace("$ROOT", str(REPO_ROOT))).resolve())
return paths
def test_every_test_file_is_reached_by_test_host():
roots = pytest_paths()
orphans = []
@@ -72,23 +100,28 @@ def test_every_shell_suite_is_called_by_test_host():
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("#")
listed = listed_shell_suites()
# Symmetrisch zur .py-Regel: das GANZE tools/ wird durchsucht, nicht nur
# tools/station/tests - eine kuenftige Shell-Suite an einer neuen Stelle faellt sonst
# weiterhin durch (Task 6b F2).
# tools/test_host.sh heisst selbst "test_*.sh", ist aber der Aufrufer und keine
# Suite - es kann sich schlecht selbst auffuehren.
missing = [
s.relative_to(REPO_ROOT)
for s in own_test_files(".sh")
if s != TEST_HOST.resolve() and s not in listed
]
missing = []
for suite in sorted((TOOLS_DIR / "station" / "tests").glob("test_*.sh")):
# So, wie test_host.sh sie aufruft: "$ROOT/tools/station/tests/<name>"
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))
"diese Shell-Suiten stehen nicht in SHELL_SUITES von tools/test_host.sh, sie "
"laufen also nur bei Handaufruf: {}".format(", ".join(str(m) for m in missing))
)
# Und andersherum: ein Eintrag, den es nicht mehr gibt, laesst test_host.sh beim
# naechsten Lauf hart scheitern - das faellt hier schon vorher auf.
verschwunden = [str(p) for p in listed if not p.exists()]
assert not verschwunden, (
"SHELL_SUITES in tools/test_host.sh nennt Dateien, die es nicht gibt: {}".format(
", ".join(verschwunden)
)
)