#!/usr/bin/env python3 """Cherche les CONTRÔLES QUI NE CONTRÔLENT RIEN. La question, posée à chaque assertion : **qu'aurait-elle attrapé si elle avait échoué ?** S'il n'y a pas de réponse, elle ne contrôle rien — et le voyant vert ment. Deux motifs, tous deux rencontrés en vrai : 1. ASSERTION CONDITIONNELLE DANS UNE BOUCLE. Si la condition n'est jamais vraie, l'assertion ne s'exécute pas et le test passe. Vu le 2026-09-01 : « sans mesure, pas de `command` » n'avait AUCUNE charge à examiner, et la garde retirée le test passait toujours. Parade : compter les cas examinés, échouer si le compte est nul. 2. IDENTITÉ QUI SE REFERME SUR ELLE-MÊME. Quand les deux membres viennent de la même source, l'identité est vraie par construction et rassure là où rien ne protège. Vu le 2026-09-03 : `authorisedW − committedW == remainingW` relisait l'affectation d'une seule ligne. ⚠️ Toutes les identités auto-référentielles ne sont pas vaines : celle du BUDGET croise `remainingSurplusW`, filé le long de la cascade, avec `allocatedW` accumulé en trois sites distincts. Le motif signale, il ne juge pas — c'est à la lecture de trancher. Cet outil ne remplace pas la question, il la pose là où elle se pose souvent. """ import re from pathlib import Path ASSERT = r'(?:QCOMPARE|QVERIFY2?|QTRY_COMPARE|QTRY_VERIFY2?)' def fonctions(txt): for m in re.finditer(r'\nvoid\s+\w+::(\w+)\(\)\s*\n\{', txt): deb = m.end(); prof, i = 1, deb while i < len(txt) and prof: if txt[i] == '{': prof += 1 elif txt[i] == '}': prof -= 1 i += 1 yield m.group(1), txt[deb:i], txt[:m.start()].count('\n')+2 cond, miroir = [], [] for f in Path('tests/auto').rglob('*.cpp'): if 'mock' in str(f) or 'experience' in str(f): continue txt = f.read_text(encoding='utf-8') for nom, corps, ligne in fonctions(txt): if nom.endswith('_data'): continue # MOTIF 1 — assertion sous un `if` DANS une boucle : si la condition n'est jamais # vraie, l'assertion ne s'exécute pas et le test passe sans rien vérifier. for m in re.finditer(r'for\s*\([^)]*\)\s*\{(.{0,900}?)\n\s*\}', corps, re.S): bloc = m.group(1) if re.search(r'\n\s*if\s*\([^)]*\)[^;{]*\n?\s*' + ASSERT, bloc): garde = re.search(r'\b(examin|vus|compte|trouv)\w*\b', corps, re.I) cond.append((f, ligne, nom, "gardé" if garde else "NON GARDÉ")) break # MOTIF 2 — comparaison dont les deux membres partent de la MÊME racine. for m in re.finditer(r'QCOMPARE\(\s*([^,]+?)\s*,\s*([^;]+?)\s*\)\s*;', corps): a, b = m.group(1), m.group(2) ra = re.match(r'\s*(?:qRound\()?\s*([A-Za-z_][\w]*)', a) rb = re.match(r'\s*(?:qRound\()?\s*([A-Za-z_][\w]*)', b) if ra and rb and ra.group(1) == rb.group(1) and '.' in a and '.' in b: miroir.append((f, ligne, nom, m.group(0)[:96])) print("=== MOTIF 1 — assertion conditionnelle dans une boucle : %d ===" % len(cond)) print(" Si la condition n'est jamais vraie, rien ne s'exécute et le test PASSE.") for f, l, n, g in cond: print(" [%s] %s:%d %s" % (g, f, l, n)) print() print("=== MOTIF 2 — les deux membres viennent de la MÊME racine : %d ===" % len(miroir)) print(" L'identité se referme sur elle-même : vraie par construction.") for f, l, n, t in miroir: print(" %s:%d %s\n %s" % (f, l, n, t))