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.
143 lines
6.4 KiB
Python
143 lines
6.4 KiB
Python
"""Wache dagegen, dass eine Testdatei aus allen Suiten herausfaellt.
|
|
|
|
Genau das war schon einmal passiert: tools/test_host.sh rief pytest auf
|
|
tools/mkimage/tests und tools/station/tests auf - tools/mkimage/test_nl_mkimage.py
|
|
liegt aber DIREKT in tools/mkimage/ und lief damit in keiner Suite mehr mit. 19 Tests
|
|
waren still verschwunden, darunter test_stays_python38_compatible, die Wache fuer die
|
|
bindende 3.8-Randbedingung des Docker-Build-Containers (`str | None` in nl_mkimage.py
|
|
hatte den Build schon einmal gebrochen).
|
|
|
|
Eine stumm ausgefallene Testdatei ist schlimmer als eine fehlende: die Suite bleibt
|
|
gruen und behauptet damit eine Deckung, die es nicht gibt. Deshalb wird hier nicht ein
|
|
einzelner Pfad festgenagelt, sondern die Regel selbst - JEDE test_*.py unter tools/
|
|
muss von einem der Pfade erfasst sein, die tools/test_host.sh an pytest uebergibt.
|
|
Eine neue Testdatei an einer neuen Stelle faellt damit sofort auf, statt erst dann,
|
|
wenn jemand sie vermisst.
|
|
"""
|
|
import re
|
|
from pathlib import Path
|
|
|
|
TOOLS_DIR = Path(__file__).resolve().parents[2]
|
|
REPO_ROOT = TOOLS_DIR.parent
|
|
TEST_HOST = TOOLS_DIR / "test_host.sh"
|
|
|
|
# Verzeichnisse, die keine Tests DIESES Projekts enthalten: virtuelle Umgebungen und
|
|
# installierte Fremdpakete bringen eigene test_*.py mit, die hier nichts zu suchen haben.
|
|
IGNORED_PARTS = (".venv", "__pycache__", "site-packages", "node_modules", ".git")
|
|
|
|
|
|
def pytest_paths():
|
|
"""Die Pfade, die tools/test_host.sh an pytest uebergibt, als aufgeloeste Pfade."""
|
|
text = TEST_HOST.read_text()
|
|
match = re.search(r"^\s*python3 -m pytest (.+?)(?:\s+-\S+)*\s*$", text, re.M)
|
|
assert match, "in tools/test_host.sh ist kein 'python3 -m pytest'-Aufruf zu finden"
|
|
paths = []
|
|
for raw in match.group(1).split():
|
|
arg = raw.strip('"')
|
|
if arg.startswith("-"):
|
|
continue
|
|
paths.append(Path(arg.replace("$ROOT", str(REPO_ROOT))).resolve())
|
|
assert paths, "der pytest-Aufruf in tools/test_host.sh uebergibt keine Pfade"
|
|
return paths
|
|
|
|
|
|
def own_test_files(suffix=".py"):
|
|
return sorted(
|
|
p.resolve()
|
|
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 = []
|
|
for test_file in own_test_files():
|
|
if not any(test_file == r or r in test_file.parents for r in roots):
|
|
orphans.append(test_file.relative_to(REPO_ROOT))
|
|
assert not orphans, (
|
|
"diese Testdateien laufen in keiner Suite mit - tools/test_host.sh sammelt sie "
|
|
"nicht ein: {}. Entweder den Pfad in test_host.sh erweitern oder die Datei "
|
|
"dorthin verschieben, wo sie eingesammelt wird.".format(
|
|
", ".join(str(o) for o in orphans)
|
|
)
|
|
)
|
|
|
|
|
|
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.
|
|
"""
|
|
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
|
|
]
|
|
assert not 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)
|
|
)
|
|
)
|
|
|
|
|
|
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()
|
|
assert guard.exists(), "tools/mkimage/test_nl_mkimage.py fehlt"
|
|
roots = pytest_paths()
|
|
assert any(guard == r or r in guard.parents for r in roots), (
|
|
"tools/mkimage/test_nl_mkimage.py wird von tools/test_host.sh nicht eingesammelt "
|
|
"- darin steht test_stays_python38_compatible, die einzige Wache gegen erneute "
|
|
"3.10-Syntax in nl_mkimage.py (der Docker-Build-Container hat Python 3.8.10)"
|
|
)
|
|
|
|
|
|
# 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.
|