#!/usr/bin/env bash # ============================================================================ # check_regression.sh · OTO Enterprise OS DTP # ---------------------------------------------------------------------------- # Gate de FRAÎCHEUR de la matrice de régression (roadmap Sprint 8 · QA # « Regression tests exhaustifs » · CLAUDE.md #5 — zéro régression silencieuse). # Prouve, en une commande, que l'artefact d'EXÉCUTION commité # `qa/regression/out/regression_run.json` (totaux « 21 suites · 534 tests · # verdict PASS » cité partout dans la doc et les logs) reflète TOUJOURS la # réalité du disque — en le régénérant et en exigeant l'identité byte-for-byte. # # Pourquoi un gate DÉDIÉ (et non `check_artifacts.sh`) : ce dernier ne rejoue # QUE la sous-commande déterministe `build` (→ regression_plan.json) et exclut # explicitement les artefacts d'EXÉCUTION issus de `run`. Or `regression_run.json` # EST un artefact de `run` : il porte les compteurs réellement exécutés # (ran/passed/failures) que `build` ne connaît pas. Résultat : sans ce gate, la # matrice agrégée pouvait se périmer en silence (ex. un module + son job CI # ajoutés sans régénérer la matrice → « 21 suites » devenu faux) sans qu'aucune # suite `tests/` — qui teste des FONCTIONS, pas le fichier commité — ne l'attrape. # C'est exactement la dérive « demo 18→21 » déjà corrigée à la main : ce gate la # rend impossible à re-commettre. # # Déterminisme : `regression_run.json` ne contient AUCUN horodatage / hôte / # chemin absolu — deux `run` successifs sont byte-identiques (vérifié). La # comparaison exacte est donc licite et constitue la preuve la plus forte. # # Contrat vérifié : `regression_gen.py run` régénéré vers un tmp DOIT produire un # `regression_run.json` byte-identique au commité. Toute divergence (compteur, # suite ajoutée/retirée, verdict) échoue le gate avec l'ordre de régénérer. # En bonus : le verdict fraîchement calculé DOIT être « PASS » (une matrice # rouge ne doit jamais être commitée verte). # # Dépendances : bash, git, python3, diff, mktemp. Aucun réseau requis # (stdlib pur · CLAUDE.md #2 — runner Gitea sans pip). # ============================================================================ set -uo pipefail cd "$(git rev-parse --show-toplevel)" FAIL=0 report() { printf ' \033[31m✗\033[0m %s\n' "$1"; FAIL=1; } ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; } MOD="05_deliverables_mvp/qa/regression" GEN="regression_gen.py" ARTIFACT="out/regression_run.json" COMMITTED="$MOD/$ARTIFACT" echo "== Fraîcheur de la matrice de régression (run frais == commité ?) ==" if [[ ! -f "$MOD/$GEN" ]]; then report "$MOD/$GEN — générateur introuvable (module déplacé/renommé ?)" elif [[ ! -f "$COMMITTED" ]]; then report "$COMMITTED — artefact d'exécution absent du dépôt (lancer 'run -o out')" else tmp="$(mktemp -d)" if ! ( cd "$MOD" && python3 "$GEN" run -o "$tmp" ) >/dev/null 2>"$tmp/.err"; then report "$MOD — 'run' a échoué (matrice non ré-exécutable) :" sed 's/^/ /' "$tmp/.err" else fresh="$tmp/regression_run.json" if [[ ! -f "$fresh" ]]; then report "$MOD — 'run' n'a produit aucun regression_run.json (générateur cassé ?)" elif ! diff -q "$fresh" "$COMMITTED" >/dev/null 2>&1; then report "$ARTIFACT — DÉRIVE : le run frais diffère du commité (régénérer)" printf ' \033[90m# diff résumé (commité → frais) :\033[0m\n' diff <(python3 -c "import json,sys;print(json.dumps(json.load(open(sys.argv[1]))['totals'],indent=1,sort_keys=True))" "$COMMITTED" 2>/dev/null) \ <(python3 -c "import json,sys;print(json.dumps(json.load(open(sys.argv[1]))['totals'],indent=1,sort_keys=True))" "$fresh" 2>/dev/null) \ | sed 's/^/ /' else verdict="$(python3 -c "import json;print(json.load(open('$COMMITTED'))['verdict'])" 2>/dev/null)" totals="$(python3 -c "import json;t=json.load(open('$COMMITTED'))['totals'];print(f\"{t['suites']} suites · {t['passed']}/{t['ran']} tests\")" 2>/dev/null)" if [[ "$verdict" != "PASS" ]]; then report "$ARTIFACT — reproductible MAIS verdict=$verdict (matrice rouge commitée)" else ok "$ARTIFACT — reproductible à l'identique ($totals · PASS)" fi fi fi rm -rf "$tmp" fi echo if [[ "$FAIL" -eq 0 ]]; then echo -e "\033[32m✅ Matrice de régression à jour (run frais == commité · PASS).\033[0m" else echo -e "\033[31m❌ Matrice périmée/rouge — régénérer avec 'regression_gen.py run -o out'.\033[0m" fi exit "$FAIL"