[DTP-Worker] Sprint 8 · buffer L75 · Intégrité matrice régression : fix gate check_regression cassé en CI (run baseline commité) + invariant disque→CI (INV4 orphan_tests_dirs)
Défaut #1 (bug CI réel) : ci/check_regression.sh exigeait regression_run.json commité comme baseline, mais le fichier était .gitignore → absent en checkout propre → le job Gitea check-regression échouait sur CHAQUE run CI (ne passait qu'en local via un fichier non suivi). L'artefact run est byte-déterministe (aucun horodatage/hôte/durée · path relatif) → committer est licite. Fix : run désormais commité (baseline), .gitignore + README réécrits. Défaut #2 (invariant manquant) : coverage_report prouvait CI→disque mais pas l'inverse. Nouveau disk_test_modules() + orphan_tests_dirs dans INV4 : un module gagnant un tests/ sans job CI fait chuter la couverture (fini la sous-comptée silencieuse de la matrice). Schéma + 2 tests de morsure. Consommateur régénéré : qa/audit_4big/quality_report.json (README 3710→4236 o, 24→26 méthodes). Matrice 534/21/PASS inchangée (harnais self-exclu, INV3). 5 gates verts · 26+34 tests OK · YAML valide.
This commit is contained in:
@@ -47,6 +47,25 @@ def count_tests(tests_dir: str) -> tuple[int, int]:
|
||||
return (files, methods)
|
||||
|
||||
|
||||
def disk_test_modules() -> list[str]:
|
||||
"""Chemins de modules (relatifs à DELIVERABLES_ROOT, POSIX) portant un
|
||||
répertoire `tests/` sur DISQUE — indépendamment du CI.
|
||||
|
||||
Sert à prouver le sens INVERSE de la couverture : tout module qui gagne des
|
||||
tests sur disque DOIT être câblé au CI, sinon ses tests ne tournent jamais et
|
||||
n'entrent jamais dans la matrice (dérive silencieuse disque→CI). Faits de
|
||||
disque purs, ordonnés → déterministe."""
|
||||
mods: set[str] = set()
|
||||
for dirpath, dirnames, _ in os.walk(DELIVERABLES_ROOT):
|
||||
# On n'explore pas l'intérieur des caches/tests eux-mêmes.
|
||||
dirnames[:] = [d for d in dirnames if d != "__pycache__"]
|
||||
if os.path.basename(dirpath) == "tests":
|
||||
module_abs = os.path.dirname(dirpath)
|
||||
rel = os.path.relpath(module_abs, DELIVERABLES_ROOT)
|
||||
mods.add(rel.replace(os.sep, "/"))
|
||||
return sorted(mods)
|
||||
|
||||
|
||||
def discover_suites(spec: dict) -> list[dict]:
|
||||
"""Liste ordonnée des suites gated (hors self), enrichie des faits de disque.
|
||||
|
||||
@@ -90,10 +109,15 @@ def coverage_report(spec: dict, suites: list[dict]) -> dict:
|
||||
not_in_gate = sorted(s["path"] for s in suites if not s["in_gate"])
|
||||
self_present = any(s["path"] == self_module for s in suites)
|
||||
|
||||
# Sens inverse : tout module portant un `tests/` sur disque DOIT être gated
|
||||
# (sinon ses tests ne tournent jamais → dérive silencieuse disque→CI).
|
||||
orphan_tests_dirs = sorted(set(disk_test_modules()) - ci_paths)
|
||||
|
||||
ok = (
|
||||
not missing_tests_dir
|
||||
and not not_in_gate
|
||||
and not self_present
|
||||
and not orphan_tests_dirs
|
||||
and self_module in ci_paths # le harnais DOIT être gated lui aussi
|
||||
)
|
||||
return {
|
||||
@@ -104,4 +128,5 @@ def coverage_report(spec: dict, suites: list[dict]) -> dict:
|
||||
"self_module_gated": self_module in ci_paths,
|
||||
"missing_tests_dir": missing_tests_dir,
|
||||
"not_in_gate": not_in_gate,
|
||||
"orphan_tests_dirs": orphan_tests_dirs,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user