From 7dab6e44e2bd41eab2ae043e2e789c71281858a5 Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Fri, 10 Jul 2026 17:06:40 +0200 Subject: [PATCH] =?UTF-8?q?tooling(validate):=20sous-ensemble=20@data=20cl?= =?UTF-8?q?=C3=A9=20rapide=20par=20d=C3=A9faut=20+=20flag=20--full=20+=20b?= =?UTF-8?q?udgets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `bun run validate` finissait en timeout (@data complet >14min). Désormais : par défaut un SOUS-ENSEMBLE CLÉ de 8 scénarios @data (les couvertures des bugs terrain : inscription, désinscription, isolation, reconnexion, créateur, compteur, auth×2) en une invocation → finit en ~8m30. Flag `--full` pour toute la suite @data (budget élargi 35min). Rotation profil avant @data/@multibrowser, matrice + exit non-zéro. Baseline actuel : 8/8 @data clé VERTS (les fixes watchShape/optimiste/reconnexion/ anti-fork tiennent) ; polyfill:unit 123 ; rouges = SingletonLock (infra) + un @multibrowser (limite wallet-partagé A/B). Note : gate pre-push rapide (tsc+build+polyfill unit) ajouté dans .git/hooks/pre-push (local, non versionné — pour partage : script tracké + install, suivi). Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/validate.ts | 149 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 121 insertions(+), 28 deletions(-) diff --git a/scripts/validate.ts b/scripts/validate.ts index 5e760b8..195b349 100644 --- a/scripts/validate.ts +++ b/scripts/validate.ts @@ -1,23 +1,26 @@ #!/usr/bin/env bun /** - * validate.ts — Full validation matrix (broker-level tests only, no @ui/@e2e). + * validate.ts — Validation matrix (broker-level tests only, no @ui/@e2e). * - * Runs in sequence: + * Default run (no flags) — key subset only, fast: * (a) Polyfill unit tests (@ng-eventually/client — bun test) - * (b) Polyfill e2e real-broker (@ng-eventually/client — bun run test:e2e) - * (c) Festipod @data (cucumber --tags @data) + * (b) Polyfill e2e real-broker (@ng-eventually/client — bun run e2e/run.ts) + * (c) Festipod @data KEY SUBSET (cucumber --name regex covering terrain bugs) * (d) Festipod @multibrowser (cucumber --tags @multibrowser) * (e) Festipod @wip [informational only, non-blocking] * + * With --full flag: + * (c) becomes full @data suite (cucumber --tags @data) + * * Each step runs even if the previous one failed (--bail mode is OFF). * Exit code is non-zero if any non-informational step has failures. * - * Profile rotation: both Playwright profiles are rotated before the run - * when their size exceeds BLOAT_THRESHOLD_MB (default 50 MB), to avoid - * the sparql_query hang described in caveat_wallet-bloat-hang. + * Profile rotation: both Playwright profiles are rotated before @data and + * @multibrowser when their size exceeds BLOAT_THRESHOLD_MB (default 50 MB), + * to avoid the sparql_query hang described in caveat_wallet-bloat-hang. */ -import { spawnSync, execSync } from "child_process"; +import { spawnSync } from "child_process"; import * as fs from "fs"; import * as path from "path"; @@ -37,8 +40,41 @@ const POLYFILL_PROFILE = path.join( /** Rotate profile when it exceeds this many MB (caveat_wallet-bloat-hang). */ const BLOAT_THRESHOLD_MB = 50; -/** Per-step timeout in ms — generous for broker + headless wallet creation. */ -const STEP_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes +/** + * Per-step timeouts: + * - polyfill unit/e2e: short steps, keep 10 min + * - @data key subset: generous — BeforeAll + 8 scenarios, ~15 min margin + * - @data full: full suite, ~35 min margin + * - @multibrowser: 7 scenarios, ~10 min margin + * - @wip: informational, 10 min + */ +const TIMEOUT_POLYFILL_UNIT_MS = 10 * 60 * 1000; // 10 min +const TIMEOUT_POLYFILL_E2E_MS = 10 * 60 * 1000; // 10 min +const TIMEOUT_DATA_KEY_MS = 15 * 60 * 1000; // 15 min (key subset) +const TIMEOUT_DATA_FULL_MS = 35 * 60 * 1000; // 35 min (--full) +const TIMEOUT_MULTIBROWSER_MS = 10 * 60 * 1000; // 10 min +const TIMEOUT_WIP_MS = 10 * 60 * 1000; // 10 min + +/** + * Key-subset --name regex: matches exactly the 8 scenarios that cover the + * known terrain bugs (inscription, désinscription, isolation, reconnexion, + * compteur dérivé, créateur ne participe pas, auth vide, auth distinctes). + * + * Uses a single cucumber invocation so BeforeAll (broker login) runs once. + * + * French accent chars must be URL-safe in the regex — cucumber uses JS + * RegExp, which handles unicode natively; we pass the literal string. + */ +const DATA_KEY_NAME_REGEX = [ + "S'inscrire à un événement", + "Se désinscrire d'un événement$", + "Une identité fraîche ne voit pas la participation d'une autre", + "Une page fraîche pour la même identité relit ses propres données", + "Le créateur ne participe pas automatiquement à son événement", + "L'inscription fait converger le compteur dérivé du propriétaire", + "Un portefeuille connecté est vide par défaut", + "Les données du portefeuille sont distinctes des données par défaut", +].join("|"); // ─── Helpers ─────────────────────────────────────────────────────────────── @@ -102,12 +138,14 @@ function runStep( cmd: string, args: string[], cwd: string, + timeoutMs: number, extraEnv: Record = {}, ): StepResult { const t0 = Date.now(); console.log(`\n${"═".repeat(60)}`); console.log(`▶ ${label}`); console.log(` ${cmd} ${args.join(" ")} (cwd: ${cwd})`); + console.log(` timeout: ${Math.round(timeoutMs / 60000)}min`); console.log(`${"═".repeat(60)}`); const env = { ...process.env, ...extraEnv }; @@ -116,7 +154,7 @@ function runStep( cwd, env, encoding: "utf-8", - timeout: STEP_TIMEOUT_MS, + timeout: timeoutMs, maxBuffer: 20 * 1024 * 1024, // 20MB }); @@ -197,7 +235,7 @@ function extractFailures(output: string, label: string): string[] { } // Cucumber "N scenarios (M failed)" summary const summary = lines.find((l) => - /\d+ scenarios?.*(failed|undefined)/.test(l), + /\d+ sc[eé]narios?.*(failed|undefined)/.test(l), ); if (summary) failures.push(summary.trim()); // Individual scenario fail lines: "✗ Scenario name (features/...)" @@ -222,10 +260,16 @@ function fmtDuration(ms: number): string { function printMatrix( steps: StepResult[], wipResult: StepResult | null, + fullMode: boolean, ): void { console.log("\n"); console.log("╔══════════════════════════════════════════════════════════╗"); console.log("║ VALIDATION MATRIX ║"); + if (fullMode) { + console.log("║ (mode: --full, @data complet) ║"); + } else { + console.log("║ (mode: défaut, sous-ensemble clé) ║"); + } console.log("╚══════════════════════════════════════════════════════════╝"); console.log(""); @@ -267,12 +311,15 @@ function printMatrix( console.log(` 🔴 ${failed.length} STEP(S) FAILED: ${failed.map((s) => s.label).join(", ")}`); } console.log(` ⏱ Total: ${fmtDuration(totalMs)}`); + if (!fullMode) { + console.log(" ℹ️ Pour @data complet : bun run validate -- --full"); + } console.log(""); } // ─── Cucumber command builder ─────────────────────────────────────────────── -function cucumberArgs(tags: string): string[] { +function cucumberArgsByTags(tags: string): string[] { return [ "--import", "tsx/esm", @@ -284,16 +331,38 @@ function cucumberArgs(tags: string): string[] { ]; } +function cucumberArgsByName(nameRegex: string): string[] { + return [ + "--import", + "tsx/esm", + "node_modules/.bin/cucumber-js", + "--config", + "cucumber.json", + "--tags", + "@data", + "--name", + nameRegex, + ]; +} + // ─── Main ────────────────────────────────────────────────────────────────── async function main(): Promise { - console.log("🔍 Festipod — Full Validation Run"); + const args = process.argv.slice(2); + const fullMode = args.includes("--full"); + + if (fullMode) { + console.log("🔍 Festipod — Full Validation Run (--full : @data complet)"); + } else { + console.log("🔍 Festipod — Validation Run (sous-ensemble clé)"); + console.log(" Pour @data complet : bun run validate -- --full"); + } console.log(` Festipod: ${FESTIPOD_DIR}`); console.log(` Polyfill: ${POLYFILL_DIR}`); console.log(""); - // ── Profile rotation ───────────────────────────────────────────────────── - console.log("── Profile rotation check ──────────────────────────────────"); + // ── Profile rotation AVANT les étapes broker ────────────────────────────── + console.log("── Profile rotation check (avant @data et @multibrowser) ────"); rotateProfile(FESTIPOD_PROFILE, "festipod"); rotateProfile(POLYFILL_PROFILE, "polyfill-lib"); @@ -301,7 +370,13 @@ async function main(): Promise { // ── (a) Polyfill unit tests ─────────────────────────────────────────────── steps.push( - runStep("polyfill:unit", "bun", ["test"], POLYFILL_DIR), + runStep( + "polyfill:unit", + "bun", + ["test"], + POLYFILL_DIR, + TIMEOUT_POLYFILL_UNIT_MS, + ), ); // ── (b) Polyfill e2e real broker ────────────────────────────────────────── @@ -311,26 +386,43 @@ async function main(): Promise { "bun", ["run", "e2e/run.ts"], POLYFILL_DIR, + TIMEOUT_POLYFILL_E2E_MS, ), ); // ── (c) Festipod @data ──────────────────────────────────────────────────── - steps.push( - runStep( - "festipod:@data", - "node", - cucumberArgs("@data"), - FESTIPOD_DIR, - ), - ); + if (fullMode) { + // --full : lance tout @data + steps.push( + runStep( + "festipod:@data (complet)", + "node", + cucumberArgsByTags("@data"), + FESTIPOD_DIR, + TIMEOUT_DATA_FULL_MS, + ), + ); + } else { + // défaut : sous-ensemble clé en UNE invocation (BeforeAll partagé) + steps.push( + runStep( + "festipod:@data (clé)", + "node", + cucumberArgsByName(DATA_KEY_NAME_REGEX), + FESTIPOD_DIR, + TIMEOUT_DATA_KEY_MS, + ), + ); + } // ── (d) Festipod @multibrowser ──────────────────────────────────────────── steps.push( runStep( "festipod:@multibrowser", "node", - cucumberArgs("@multibrowser"), + cucumberArgsByTags("@multibrowser"), FESTIPOD_DIR, + TIMEOUT_MULTIBROWSER_MS, ), ); @@ -339,12 +431,13 @@ async function main(): Promise { const wipResult = runStep( "festipod:@wip", "node", - cucumberArgs("@wip"), + cucumberArgsByTags("@wip"), FESTIPOD_DIR, + TIMEOUT_WIP_MS, ); // ── Matrix ──────────────────────────────────────────────────────────────── - printMatrix(steps, wipResult); + printMatrix(steps, wipResult, fullMode); // ── Exit code ───────────────────────────────────────────────────────────── const anyFailed = steps.some((s) => s.status !== "passed");