From 08129cfb0cd74fc83cd5ebf99a303053762e90a9 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Thu, 23 Jul 2026 01:33:13 -0300 Subject: [PATCH] fix(ci): merge-train --fast mirrors test:unit subdir allowlist (#7688) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fast bucket fed every changed tests/unit file to node:test; vitest-only subdirs (autoCombo) always fail under that runner and redden the train. The classifier now carries the same {api,auth,…} allowlist as package.json's test:unit (guarded by a sync test) and stops excluding ui/*.test.ts, which test:unit does run. --- .../merge-train-unit-subdir-allowlist.md | 4 ++++ scripts/release/merge-train.sh | 12 +++++++++++- tests/unit/merge-train-plan.test.ts | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 changelog.d/maintenance/merge-train-unit-subdir-allowlist.md diff --git a/changelog.d/maintenance/merge-train-unit-subdir-allowlist.md b/changelog.d/maintenance/merge-train-unit-subdir-allowlist.md new file mode 100644 index 0000000000..d9ba696337 --- /dev/null +++ b/changelog.d/maintenance/merge-train-unit-subdir-allowlist.md @@ -0,0 +1,4 @@ +- **chore(release):** merge-train `--fast` now classifies changed tests with the same + `tests/unit/{…}` subdir allowlist `test:unit` runs — vitest-only subdirs (e.g. + `tests/unit/autoCombo/`) are skipped instead of misrun under node:test, and + `tests/unit/ui/**/*.test.ts` (node:test) is no longer over-excluded. diff --git a/scripts/release/merge-train.sh b/scripts/release/merge-train.sh index 2841f17641..613ab53d7e 100755 --- a/scripts/release/merge-train.sh +++ b/scripts/release/merge-train.sh @@ -164,7 +164,10 @@ if [ "$FAST" = "1" ]; then # node:test files changed by the boarded PRs (tests/unit/**/*.test.{ts,mjs}; # tests/unit/ui/*.test.tsx belongs to the vitest-ui runner, not node:test). mapfile -t CHANGED < <(git -C "$WT" diff --name-only "origin/${BASE}" HEAD -- 'tests/unit' \ - | grep -E '\.test\.(ts|mjs)$' | grep -v '^tests/unit/ui/' || true) + | grep -E '\.test\.(ts|mjs)$' || true) + # Subdirs test:unit actually runs (package.json allowlist) — anything else under + # tests/unit// belongs to another runner (e.g. autoCombo -> vitest). + UNIT_SUBDIRS=",api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage," MAIN=() DASH=() SERIAL=() @@ -173,6 +176,13 @@ if [ "$FAST" = "1" ]; then case "$f" in tests/unit/dashboard/*) DASH+=("$f") ;; tests/unit/serial/*) SERIAL+=("$f") ;; + *.test.mjs) MAIN+=("$f") ;; # tests/unit/**/*.test.mjs runs from any subdir + tests/unit/*/*) + sub="${f#tests/unit/}"; sub="${sub%%/*}" + case "$UNIT_SUBDIRS" in + *",${sub},"*) MAIN+=("$f") ;; + *) echo "[merge-train] (fast) skip ${f} — subdir '${sub}' not in test:unit (other runner)" ;; + esac ;; *) MAIN+=("$f") ;; esac done diff --git a/tests/unit/merge-train-plan.test.ts b/tests/unit/merge-train-plan.test.ts index 52fb49877b..1468041c99 100644 --- a/tests/unit/merge-train-plan.test.ts +++ b/tests/unit/merge-train-plan.test.ts @@ -5,6 +5,7 @@ import { test } from "node:test"; import assert from "node:assert/strict"; import { execFile } from "node:child_process"; +import { readFile } from "node:fs/promises"; import { promisify } from "node:util"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; @@ -67,6 +68,21 @@ test("--plan --fast swaps the full unit suite for changed-tests, keeps static ga assert.ok(!stdout.includes("npm run test:unit"), "fast mode must not run the full unit suite"); }); +test("fast mode's UNIT_SUBDIRS allowlist mirrors package.json test:unit exactly", async () => { + // Regression for the 2026-07-18 train red: tests/unit/autoCombo/ (a vitest-only + // subdir) was fed to the node:test bucket because the fast filter had no subdir + // allowlist. The script must classify changed tests with the SAME subdir set + // test:unit runs, so files owned by another runner are skipped, not misrun. + const script = await readFile(SCRIPT, "utf8"); + const scriptList = script.match(/UNIT_SUBDIRS=",([^"]+),"/)?.[1]; + assert.ok(scriptList, "merge-train.sh must declare the UNIT_SUBDIRS allowlist"); + const pkg = JSON.parse(await readFile(new URL("../../package.json", import.meta.url), "utf8")); + const pkgList = pkg.scripts["test:unit"].match(/tests\/unit\/\{([^}]+)\}/)?.[1]; + assert.ok(pkgList, "package.json test:unit must carry the {subdir} allowlist glob"); + assert.equal(scriptList, pkgList, "merge-train.sh UNIT_SUBDIRS must equal test:unit's subdir set"); + assert.ok(!scriptList.split(",").includes("autoCombo"), "autoCombo belongs to vitest, not node:test"); +}); + test("rejects an unknown flag", async () => { const { code, stderr } = await run(["--nope", "release/v9.9.9", "111"]); assert.equal(code, 1);