From bea5d49aab6224ae9ad462448b0655edd9b21929 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Thu, 6 Aug 2026 12:50:26 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker=2020260806=5F124544]=20V=C3=89RIF?= =?UTF-8?q?=20axe=20NEUF=20=C2=AB=20atteignabilit=C3=A9=20transitive=20d'u?= =?UTF-8?q?ne=20assertion=20=C2=BB=20=E2=86=92=20CLEAN=200/651=20(aucun=20?= =?UTF-8?q?test=20run-only=20faux-vert)=20+=201=20durcissement=20:=20test?= =?UTF-8?q?=5Fvalidate=5Fexits=5Fzero=20passait=20sans=20assertion=20expli?= =?UTF-8?q?cite=20(mordait=20implicitement=20via=20SystemExit)=20=E2=86=92?= =?UTF-8?q?=20assertEqual(main(["validate"]),0)=20strictement=20plus=20for?= =?UTF-8?q?t=20;=20d=C3=A9tecteur=20AST=20teeth-prouv=C3=A9,=20r=C3=A9solu?= =?UTF-8?q?tion=20transitive=20du=20graphe=20d'appels=20(=C3=A9vite=2040?= =?UTF-8?q?=20faux-positifs=20de=20d=C3=A9l=C3=A9gation=20=5Fexpect=5Ferro?= =?UTF-8?q?r/=5Fbreaks),=200=20logique=20de=20prod=20touch=C3=A9e,=20run?= =?UTF-8?q?=5Fci=2033=20PASS=200=20FAIL=200=20SKIP,=20pas=20de=20gate=20(#?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-08-06.md | 37 +++++++++++++++++++ .../qa/acceptance/tests/test_acceptance.py | 7 +++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/05_activity_log/2026-08-06.md b/05_activity_log/2026-08-06.md index af392d7..b3d7249 100644 --- a/05_activity_log/2026-08-06.md +++ b/05_activity_log/2026-08-06.md @@ -1079,3 +1079,40 @@ jsonschema ne masque AUCUN écart enum/const/schéma.** VPS** (#8). `./run_ci.sh` inchangé = **33 PASS · 0 FAIL · 0 SKIP**. Fichiers touchés : ce journal + 1 entrée mémoire (`schema-oracle-enum-const-vs-S-skip`). Sweep rejouable tel quel (jsonschema 3.2.0 local ; stdlib pur sinon ; sans réseau) — **NON promu en gate** (#5, occurrence-zéro). + +## Session `20260806_124544` — VÉRIF axe NEUF « atteignabilité transitive d'une assertion » (aucun test run-only faux-vert) → CLEAN 0/651 + 1 durcissement (implicite→explicite) + +**Choix de tâche.** `./run_ci.sh` au démarrage = **33 PASS · 0 FAIL · 0 SKIP**, arbre propre, roadmap +`ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée (Sprints 1→7 ; prod VPS / builds EAS / dépôts +ONAPI hors périmètre worker · #8). Registre `OPEN_DECISIONS_REGISTER.md` = 3 arbitrages produit +appartenant à Michel (non éditables). Objectif : un **axe teeth encore jamais balayé**. + +**Axe NEUF (absent de la mémoire).** `test-shadowing-sweep` couvre les tests **jamais exécutés** (dup de +nom/classe). Distinct et complémentaire : un test **exécuté** mais qui **n'atteint aucune assertion** +(run-only) reste vert même si le code casse = faux-vert. Détecteur AST (stdlib pur) : une méthode +`test*` est **édentée** si elle ne contient NI assertion directe (`self.assert*`/`self.fail`/ +`assertRaises`/`pytest.raises`), NI appel levant (`jsonschema.validate`, `_validate(...)`, +`check_output/call`, `raise`), NI délégation à un helper de sa classe lui-même édenté-négatif +(`self._expect_error`/`_breaks`…), NI appel à une fonction nommée assert/expect/breaks/reject/check. +Résolution **transitive** du graphe d'appels intra-classe (sinon 40 faux positifs sur les batteries +d'injections négatives qui délèguent à `_expect_error`/`_breaks`). + +**Dents prouvées.** Mutants : un `test_bad` qui ne fait que `print(compute(3))` → **MORD** (True) ; +`self.assertEqual(...)` → non signalé ; délégation helper (par set toothed OU par nom) → non signalé +(3/3 discriminés). + +**Résultat & durcissement.** Sur **651 méthodes** de test : **1 seul** candidat → +`qa/acceptance/tests/test_acceptance.py::TestCLI.test_validate_exits_zero`, qui appelait `gen.main( +["validate"])` **sans assertion explicite**. Investigation : ce n'était PAS un faux-vert réel — il mord +**implicitement** via `SystemExit` (`cmd_validate`→`_validate_or_die`→`sys.exit(1)` si erreurs). MAIS +fragilité latente : un refactor remplaçant `sys.exit(1)` par un `return 1` **silencieux** rendrait le +test édenté sans que rien ne rougisse. **Durcissement** (strictement plus fort, aucun inconvénient) : +`self.assertEqual(gen.main(["validate"]), 0)` — mord désormais AUSSI sur le chemin retour-non-zéro, et +rend les dents **visibles** (aligné sur le style `test_build_writes_outputs` voisin). `main` retourne 0 +en succès (`cmd_validate` → `return 0`), assertion correcte. + +**Conclusion.** **CLEAN 0/651** après durcissement (le détecteur re-passé confirme 0 édenté). Axe +teeth-prouvé. Édition minimale : **1 test durci** (pas de logique de production touchée · #8), suite +`qa-acceptance` OK, `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP**. Fichiers : ce journal + le test +durci + 1 entrée mémoire (`test-assertion-reachability-sweep`). Sweep **rejouable** (AST stdlib pur) — +**NON promu en gate** (#5, occurrence-quasi-zéro après fix). diff --git a/05_deliverables_mvp/qa/acceptance/tests/test_acceptance.py b/05_deliverables_mvp/qa/acceptance/tests/test_acceptance.py index 24f6541..17cd4b7 100644 --- a/05_deliverables_mvp/qa/acceptance/tests/test_acceptance.py +++ b/05_deliverables_mvp/qa/acceptance/tests/test_acceptance.py @@ -373,8 +373,11 @@ class TestCLI(unittest.TestCase): self.assertTrue(os.path.exists(os.path.join(tmp, "MANIFEST.json"))) def test_validate_exits_zero(self): - # cmd_validate ne lève pas sur le dépôt réel (sinon SystemExit). - gen.main(["validate"]) + # cmd_validate ne lève pas sur le dépôt réel (sinon SystemExit via + # _validate_or_die) ET retourne le code 0. On l'asserte explicitement : + # ainsi le test mord aussi si un refactor remplaçait sys.exit(1) par un + # `return 1` silencieux (le SystemExit ne serait alors plus levé). + self.assertEqual(gen.main(["validate"]), 0) if __name__ == "__main__":