[DTP-Worker 20260805_124241] FIX test-precision (surface neuve) : test_audit_4big.py figeait totals["pass"] = 18 - fail alors que le périmètre a grandi à 24 modules → le rapport « bad » de test_failing_module_forces_global_fail déclenchait INV7 (visé) MAIS AUSSI 2× INV8 (pass+fail=18≠24 modules ET min/max incohérents), contredisant son propre commentaire « rapport cohérent en interne ». Test relâché passant pour de mauvaises raisons + constante latente périmée. FIX (test only, 0 prod, 0 artefact rebuild) : totaux dérivés du vrai bad["modules"] (pass=len-fail, min/max=min/max(scores)) → rapport réellement cohérent, seul INV7 mord ; assertion resserrée any("< seuil") → all("INV7")+any("< seuil") prouve l'isolation. Preuve : rejeu de l'ancien 18 renvoie bien [INV7, INV8, INV8] et la nouvelle assertion échouerait dessus (dents réelles). 34 tests OK, 33 PASS CI. 0 gate ajouté (#5), 0 chiffre saisi (#6), 0 VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1164,3 +1164,49 @@ sens). Recoupé au décompte du gate lui-même (`ls ci/*.sh` hors `lib.sh` = **8
|
|||||||
**0 chiffre inventé/saisi** (#6 — tout dérivé de `json.load`/`ls`/`--list`), **0 commande VPS** (#8),
|
**0 chiffre inventé/saisi** (#6 — tout dérivé de `json.load`/`ls`/`--list`), **0 commande VPS** (#8),
|
||||||
**aucun `git clean`** (#1). <!-- ci-allow : mention de l'interdit, jamais exécuté --> Le daily_report
|
**aucun `git clean`** (#1). <!-- ci-allow : mention de l'interdit, jamais exécuté --> Le daily_report
|
||||||
est **exact et courant** → laissé intact (l'éditer serait du churn). Seule édition : ce log. `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP** (inchangé).
|
est **exact et courant** → laissé intact (l'éditer serait du churn). Seule édition : ce log. `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP** (inchangé).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session `20260805_124241` — FIX test-precision : constante `18` périmée + commentaire faux dans `test_audit_4big.py`
|
||||||
|
|
||||||
|
**Surface NEUVE (hors classes saturées).** Chasse ciblée aux surfaces non encore auditées
|
||||||
|
(le canal usuel README/counts/docstrings/fiches est saturé, #5). Trouvaille réelle, pas un
|
||||||
|
faux-positif : `05_deliverables_mvp/qa/audit_4big/tests/test_audit_4big.py`
|
||||||
|
`test_failing_module_forces_global_fail` figeait `bad["totals"]["pass"] = 18 - fail`.
|
||||||
|
|
||||||
|
**Le défaut.** Le périmètre audité a grandi à **24 modules** (`totals.modules == 24`, tous
|
||||||
|
100/100), mais le test gardait l'entier **`18`** codé en dur. Résultat : le rapport « bad »
|
||||||
|
qu'il fabrique avait `pass=17, fail=1, modules=24` → il déclenchait **INV7** (visé) MAIS
|
||||||
|
AUSSI **INV8 `pass+fail ≠ modules`** (18≠24) *et* **INV8 `min/max score incohérents`**
|
||||||
|
(module passé sous 95 alors que `min_score` restait 100). Or son propre commentaire affirmait
|
||||||
|
« *Le rapport est cohérent en interne mais porte un FAIL → INV7 le signale* » — **affirmation
|
||||||
|
fausse** : le rapport n'était PAS cohérent en interne. Le test passait quand même car son
|
||||||
|
assertion (`any("< seuil 4Big")`) restait vraie parmi les erreurs parasites → test **relâché**,
|
||||||
|
passant pour de mauvaises raisons, avec une constante latente qui pourrit en silence.
|
||||||
|
|
||||||
|
**Preuve du diagnostic (recompute indépendant).** Rejeu de l'ancien setup `18` →
|
||||||
|
`check_invariants` renvoie exactement `[INV7 publiciste …, INV8 pass+fail ≠ modules, INV8
|
||||||
|
min/max score incohérents]`. La nouvelle assertion `all("INV7" in e)` **échouerait** sur ce
|
||||||
|
setup → elle a de vraies dents (pas tautologique).
|
||||||
|
|
||||||
|
**Le fix (test uniquement, 0 production).** Totaux dérivés du **vrai** `bad["modules"]` au lieu
|
||||||
|
d'un entier figé :
|
||||||
|
- `pass = len(modules) - fail` (robuste à tout futur changement de décompte, #6 — aucun
|
||||||
|
chiffre saisi à la main)
|
||||||
|
- `min_score/max_score = min/max(scores recomputés)`
|
||||||
|
|
||||||
|
Le rapport « bad » devient **réellement cohérent en interne** → INV8/INV9 ne mordent plus,
|
||||||
|
**seul INV7** signale le module sous seuil — exactement ce que le commentaire prétend. Assertion
|
||||||
|
resserrée de `any("< seuil 4Big")` (relâchée) vers `errs non vide` + `all("INV7")` + `any("< seuil
|
||||||
|
4Big")` : prouve désormais l'**isolation** d'INV7 comme unique déclencheur.
|
||||||
|
|
||||||
|
**Portée / anti-churn.** **0 fichier de production édité** (seul un fichier de test), **0
|
||||||
|
artefact reconstruit** (aucun `out/*.json` touché → pas de rebuild `quality_report`, cf.
|
||||||
|
[[audit4big-rebuild-after-doc-edits]]), **0 gate ajouté** (#5), **0 chiffre inventé** (#6 —
|
||||||
|
tout dérivé de `len()`/`min`/`max`), **0 commande VPS** (#8), **aucun `git clean`** (#1).
|
||||||
|
<!-- ci-allow : mention de l'interdit, jamais exécuté -->
|
||||||
|
|
||||||
|
**Vérif.** `python3 -m unittest discover -s tests` → **34 tests OK** ; `./run_ci.sh` → **33
|
||||||
|
PASS · 0 FAIL · 0 SKIP**. Distinct de la classe *toothless-test/inert-mutation*
|
||||||
|
([[module-suite-teeth-mutation]]) : ici la LOGIQUE testée est saine — c'est le HARNAIS de test
|
||||||
|
(données figées) qui avait dérivé.
|
||||||
|
|||||||
@@ -318,10 +318,19 @@ class InvariantGuardTest(unittest.TestCase):
|
|||||||
m["verdict"] = "PASS" if m["score"] >= 95 else "FAIL"
|
m["verdict"] = "PASS" if m["score"] >= 95 else "FAIL"
|
||||||
bad["totals"]["fail"] = sum(1 for x in bad["modules"]
|
bad["totals"]["fail"] = sum(1 for x in bad["modules"]
|
||||||
if x["verdict"] != "PASS")
|
if x["verdict"] != "PASS")
|
||||||
bad["totals"]["pass"] = 18 - bad["totals"]["fail"]
|
# Totaux dérivés du vrai périmètre (pas d'entier figé) → le rapport
|
||||||
|
# reste cohérent en interne : INV8 (pass+fail=modules, min/max) ne doit
|
||||||
|
# PAS mordre, seul INV7 le doit.
|
||||||
|
bad["totals"]["pass"] = len(bad["modules"]) - bad["totals"]["fail"]
|
||||||
|
_scores = [x["score"] for x in bad["modules"]]
|
||||||
|
bad["totals"]["min_score"] = min(_scores)
|
||||||
|
bad["totals"]["max_score"] = max(_scores)
|
||||||
bad["verdict"] = "FAIL"
|
bad["verdict"] = "FAIL"
|
||||||
errs = gen.check_invariants(bad, self.spec)
|
errs = gen.check_invariants(bad, self.spec)
|
||||||
# Le rapport est cohérent en interne mais porte un FAIL → INV7 le signale.
|
# Le rapport est cohérent en interne mais porte un module sous le seuil
|
||||||
|
# → INV7 (et lui seul) le signale.
|
||||||
|
self.assertTrue(errs, "INV7 aurait dû signaler le module sous seuil")
|
||||||
|
self.assertTrue(all("INV7" in e for e in errs), errs)
|
||||||
self.assertTrue(any("< seuil 4Big" in e for e in errs), errs)
|
self.assertTrue(any("< seuil 4Big" in e for e in errs), errs)
|
||||||
|
|
||||||
def test_self_module_in_scope_is_caught(self):
|
def test_self_module_in_scope_is_caught(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user