"""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(): return sorted( p.resolve() for p in TOOLS_DIR.rglob("test_*.py") if not any(part in IGNORED_PARTS for part in p.parts) ) 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_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)" ) def test_vermin_is_installed(): """vermin fehlt -> harter Fehlschlag, kein Skip. Eine Wache, die sich beim Fehlen ihres Werkzeugs stillschweigend abschaltet, haette den Build-Bruch von damals genauso durchgelassen wie gar kein Test. Nachinstallieren: pip install vermin (oder pip3 install --user vermin). """ import vermin # noqa: F401