Files
fusion/scripts/__tests__/ci-test-shard.test.mjs
gsxdsm 5bebc3cfe0 perf(ci): duration-based shard balancing with dashboard lane distribution
- shards weighted by scripts/test-timings.json durations (median-duration fallback + warning for untimed)
- dashboard no longer --shard-sliced (broken across its script chain): 14 lanes enumerated from package.json and scheduled as separately-weighted units, each exactly once
- engine/core keep --shard slicing, duration-weighted
- estimated critical-path shard: ~235s -> ~159s (spread 96.7% -> 0.06%)
- 30-day staleness warning + --check-timings-staleness for a scheduled refresh job; --dry-run mode
2026-06-03 18:35:26 -07:00

620 lines
26 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
// A throwaway project root that contains only a stale timing snapshot, used to
// exercise the `--check-timings-staleness` exit-1 path without mutating the
// committed snapshot. The script resolves the snapshot relative to cwd.
const STALE_FIXTURE_ROOT = mkdtempSync(path.join(tmpdir(), "u6-stale-cli-"));
mkdirSync(path.join(STALE_FIXTURE_ROOT, "scripts"), { recursive: true });
writeFileSync(
path.join(STALE_FIXTURE_ROOT, "scripts/test-timings.json"),
JSON.stringify({
capturedAt: new Date(Date.now() - 90 * 86_400_000).toISOString(),
packages: { "@fusion/core": { files: { "packages/core/a.test.ts": 500 } } },
}),
);
process.on("exit", () => rmSync(STALE_FIXTURE_ROOT, { recursive: true, force: true }));
import {
computeSplitPlan,
planShardAssignments,
selectShardPackages,
countPackageTestFiles,
loadPlanningTimings,
computePackageDurationWeight,
sumFileDurations,
enumerateDashboardLanes,
laneProjectNames,
buildShardCommands,
TIMINGS_STALENESS_DAYS,
} from "../ci-test-shard.mjs";
function silentLogger() {
return { log() {}, warn() {}, error() {} };
}
function writeSnapshot(dir, capturedAt, packages) {
const file = path.join(dir, "test-timings.json");
writeFileSync(file, JSON.stringify({ capturedAt, packages }));
return file;
}
test("computeSplitPlan: returns unsplit entries when package weights do not exceed split limit", () => {
const packages = [
{ name: "a", testFileCount: 2 },
{ name: "b", testFileCount: 1 },
];
const result = computeSplitPlan(packages, 3, { threshold: 2 });
assert.deepEqual(result, [
{ name: "a", weight: 2 },
{ name: "b", weight: 1 },
]);
});
test("computeSplitPlan: returns [] for empty input", () => {
assert.deepEqual(computeSplitPlan([], 4), []);
});
test("computeSplitPlan: high threshold avoids splitting", () => {
const result = computeSplitPlan([{ name: "only", testFileCount: 10 }], 4, { threshold: Number.POSITIVE_INFINITY });
assert.deepEqual(result, [{ name: "only", weight: 10 }]);
});
test("computeSplitPlan: threshold <= 0 splits when total > 1", () => {
const result = computeSplitPlan([{ name: "only", testFileCount: 10 }], 4, { threshold: 0 });
assert.equal(result.length, 4);
assert.ok(result.every((entry) => entry.shardCount === 4));
});
test("computeSplitPlan: exact split limit boundary stays unsplit", () => {
const packages = [
{ name: "boundary", testFileCount: 2 },
{ name: "other", testFileCount: 2 },
];
const total = 2;
const threshold = 1;
// perShardBudget = 2, splitLimit = 2, and 2 is not greater than 2.
const result = computeSplitPlan(packages, total, { threshold });
assert.deepEqual(result, [
{ name: "boundary", weight: 2 },
{ name: "other", weight: 2 },
]);
});
test("computeSplitPlan: oversized package splits into balanced virtual entries", () => {
const result = computeSplitPlan([{ name: "big", testFileCount: 10 }], 4, { threshold: 0.5 });
assert.equal(result.length, 4);
assert.ok(result.every((entry) => entry.name === "big"));
assert.deepEqual(
result.map((entry) => entry.shardIndex),
[1, 2, 3, 4],
);
assert.ok(result.every((entry) => entry.shardCount === 4));
assert.deepEqual(result.map((entry) => entry.weight), [3, 3, 3, 3]);
assert.equal(result.reduce((sum, entry) => sum + entry.weight, 0), 12);
});
test("computeSplitPlan: half-budget package now splits into exactly two virtual entries (FN-4989)", () => {
const packages = [
{ name: "dashboard", testFileCount: 553 },
{ name: "engine", testFileCount: 365 },
{ name: "core", testFileCount: 200 },
{ name: "cli", testFileCount: 71 },
{ name: "tail-a", testFileCount: 39 },
{ name: "tail-b", testFileCount: 35 },
{ name: "tail-c", testFileCount: 31 },
{ name: "tail-d", testFileCount: 19 },
{ name: "tail-e", testFileCount: 18 },
{ name: "tail-f", testFileCount: 18 },
{ name: "tail-g", testFileCount: 17 },
{ name: "tail-h", testFileCount: 16 },
{ name: "tail-i", testFileCount: 15 },
{ name: "tail-j", testFileCount: 12 },
];
const result = computeSplitPlan(packages, 4);
const coreEntries = result.filter((entry) => entry.name === "core");
assert.equal(coreEntries.length, 2);
assert.ok(coreEntries.every((entry) => entry.shardCount === 2));
assert.deepEqual(
coreEntries.map((entry) => entry.shardIndex).sort((a, b) => (a ?? 0) - (b ?? 0)),
[1, 2],
);
assert.ok(coreEntries.every((entry) => entry.weight === 100));
});
test("computeSplitPlan: package above splitLimit but below perShardBudget still splits in two", () => {
const result = computeSplitPlan(
[
{ name: "solo", testFileCount: 6 },
{ name: "big", testFileCount: 20 },
],
4,
);
const soloEntries = result.filter((entry) => entry.name === "solo");
assert.equal(soloEntries.length, 2);
assert.deepEqual(
soloEntries.map((entry) => ({
shardIndex: entry.shardIndex,
shardCount: entry.shardCount,
weight: entry.weight,
})),
[
{ shardIndex: 1, shardCount: 2, weight: 3 },
{ shardIndex: 2, shardCount: 2, weight: 3 },
],
);
});
test("planShardAssignments: returns array of requested shard count", () => {
const result = planShardAssignments([{ name: "a", testFileCount: 1 }], 3);
assert.equal(result.length, 3);
});
test("planShardAssignments: trailing shards are empty when shard count exceeds packages", () => {
const result = planShardAssignments([{ name: "a", testFileCount: 1 }], 3, { threshold: Number.POSITIVE_INFINITY });
assert.deepEqual(result[1], []);
assert.deepEqual(result[2], []);
});
test("planShardAssignments: best-fit balancing keeps shard totals within 1", () => {
const packages = [
{ name: "a", testFileCount: 5 },
{ name: "b", testFileCount: 4 },
{ name: "c", testFileCount: 3 },
{ name: "d", testFileCount: 2 },
];
const splitPlan = computeSplitPlan(packages, 2, { threshold: Number.POSITIVE_INFINITY });
const byName = new Map(splitPlan.map((entry) => [entry.name, entry.weight]));
const shards = planShardAssignments(packages, 2, { threshold: Number.POSITIVE_INFINITY });
const totals = shards.map((entries) => entries.reduce((sum, entry) => sum + (byName.get(entry.name) ?? 0), 0));
assert.ok(Math.max(...totals) - Math.min(...totals) <= 1);
});
test("planShardAssignments: oversized package can be split across shards with consistent shardCount", () => {
const packages = [
{ name: "big", testFileCount: 20 },
{ name: "small", testFileCount: 2 },
];
const shards = planShardAssignments(packages, 4);
const bigEntries = shards.flat().filter((entry) => entry.name === "big");
assert.ok(bigEntries.some((entry) => (entry.shardCount ?? 0) > 1));
const shardCounts = new Set(bigEntries.map((entry) => entry.shardCount));
assert.equal(shardCounts.size, 1);
});
test("planShardAssignments: preserves total input weight via computeSplitPlan-derived mapping", () => {
const packages = [
{ name: "big", testFileCount: 20 },
{ name: "small", testFileCount: 2 },
];
const splitPlan = computeSplitPlan(packages, 4);
const keyFor = (entry) => `${entry.name}:${entry.shardIndex ?? 0}/${entry.shardCount ?? 0}`;
const weightByKey = new Map(splitPlan.map((entry) => [keyFor(entry), entry.weight]));
const assigned = planShardAssignments(packages, 4);
const assignedWeight = assigned
.flat()
.reduce((sum, entry) => sum + (weightByKey.get(keyFor(entry)) ?? 0), 0);
const inputWeight = packages.reduce((sum, pkg) => sum + pkg.testFileCount, 0);
assert.equal(assignedWeight, inputWeight);
});
test("planShardAssignments: FN-4989 real-ci-like distribution stays below 2% total variance", () => {
const packages = [
{ name: "@fusion/dashboard", testFileCount: 553 },
{ name: "@fusion/engine", testFileCount: 365 },
{ name: "@fusion/core", testFileCount: 200 },
{ name: "@runfusion/fusion", testFileCount: 71 },
{ name: "tail-a", testFileCount: 39 },
{ name: "tail-b", testFileCount: 35 },
{ name: "tail-c", testFileCount: 31 },
{ name: "tail-d", testFileCount: 19 },
{ name: "tail-e", testFileCount: 18 },
{ name: "tail-f", testFileCount: 18 },
{ name: "tail-g", testFileCount: 17 },
{ name: "tail-h", testFileCount: 16 },
{ name: "tail-i", testFileCount: 15 },
{ name: "tail-j", testFileCount: 12 },
];
const shards = planShardAssignments(packages, 4);
const totals = shards.map((entries) => entries.reduce((sum, entry) => sum + entry.weight, 0));
const totalWeight = totals.reduce((sum, weight) => sum + weight, 0);
const varianceRatio = (Math.max(...totals) - Math.min(...totals)) / totalWeight;
assert.ok(
varianceRatio < 0.02,
`expected <2% variance but got ${(varianceRatio * 100).toFixed(2)}% (${totals.join("/")})`,
);
});
test("planShardAssignments: FN-5036 keeps split engine slices from co-locating with heavy core and stays within 5% variance", () => {
const packages = [
{ name: "@fusion/dashboard", testFileCount: 606 },
{ name: "@fusion/engine", testFileCount: 365 },
{ name: "@fusion/core", testFileCount: 200 },
{ name: "@runfusion/fusion", testFileCount: 71 },
{ name: "tail-a", testFileCount: 39 },
{ name: "tail-b", testFileCount: 35 },
{ name: "tail-c", testFileCount: 31 },
{ name: "tail-d", testFileCount: 19 },
{ name: "tail-e", testFileCount: 18 },
{ name: "tail-f", testFileCount: 18 },
{ name: "tail-g", testFileCount: 17 },
{ name: "tail-h", testFileCount: 16 },
{ name: "tail-i", testFileCount: 15 },
{ name: "tail-j", testFileCount: 12 },
];
const shards = planShardAssignments(packages, 4);
const totals = shards.map((entries) => entries.reduce((sum, entry) => sum + entry.weight, 0));
const mean = totals.reduce((sum, weight) => sum + weight, 0) / totals.length;
const varianceRatio = (Math.max(...totals) - Math.min(...totals)) / mean;
assert.ok(
varianceRatio <= 0.05,
`expected <=5% variance but got ${(varianceRatio * 100).toFixed(2)}% (${totals.join("/")})`,
);
for (const entries of shards) {
const hasCore = entries.some((entry) => entry.name === "@fusion/core");
if (!hasCore) continue;
const engineSliceCount = entries.filter((entry) => entry.name === "@fusion/engine" && entry.shardCount).length;
assert.ok(
engineSliceCount < 2,
"expected @fusion/core to not share a shard with both @fusion/engine virtual slices",
);
}
});
test("planShardAssignments: FN-5033 regression workload stays below 5% 4-shard variance", () => {
const packages = [
{ name: "@fusion/dashboard", testFileCount: 608 },
{ name: "@fusion/engine", testFileCount: 380 },
{ name: "@fusion/core", testFileCount: 202 },
{ name: "@runfusion/fusion", testFileCount: 120 },
{ name: "tail-a", testFileCount: 90 },
{ name: "tail-b", testFileCount: 80 },
{ name: "tail-c", testFileCount: 70 },
{ name: "tail-d", testFileCount: 70 },
];
const shards = planShardAssignments(packages, 4);
const totals = shards.map((entries) => entries.reduce((sum, entry) => sum + entry.weight, 0));
const perShardBudget = totals.reduce((sum, weight) => sum + weight, 0) / totals.length;
const varianceRatio = (Math.max(...totals) - Math.min(...totals)) / perShardBudget;
assert.ok(
varianceRatio < 0.05,
`expected <5% variance but got ${(varianceRatio * 100).toFixed(2)}% (${totals.join("/")})`,
);
});
test("planShardAssignments: best-fit places unsplit large package on tightest under-budget shard", () => {
const packages = [
{ name: "anchor", testFileCount: 290 },
{ name: "preload", testFileCount: 220 },
{ name: "x-large-unsplit", testFileCount: 200 },
{ name: "near-budget", testFileCount: 170 },
{ name: "small", testFileCount: 40 },
{ name: "tiny", testFileCount: 30 },
];
const shards = planShardAssignments(packages, 3, { threshold: Number.POSITIVE_INFINITY });
const targetIndex = shards.findIndex((entries) => entries.some((entry) => entry.name === "x-large-unsplit"));
assert.equal(targetIndex, 2, "best-fit should place 200-weight package onto the empty shard");
});
test("planShardAssignments: uses minimum overshoot and deterministic tie-break when all candidates exceed budget", () => {
const packages = [
{ name: "gamma", testFileCount: 80 },
{ name: "beta", testFileCount: 70 },
{ name: "alpha", testFileCount: 60 },
{ name: "overshoot", testFileCount: 100 },
];
const first = planShardAssignments(packages, 2, { threshold: Number.POSITIVE_INFINITY });
const second = planShardAssignments(packages, 2, { threshold: Number.POSITIVE_INFINITY });
const overshootShard = first.findIndex((entries) => entries.some((entry) => entry.name === "overshoot"));
assert.equal(overshootShard, 0);
assert.deepEqual(first, second);
});
test("planShardAssignments: keeps split slices isolated across distinct shards", () => {
const packages = [
{ name: "split-me", testFileCount: 16 },
{ name: "small", testFileCount: 2 },
];
const shards = planShardAssignments(packages, 2, { threshold: 0.5 });
const splitSliceShardIndices = shards
.map((entries, shardIndex) => ({ entries, shardIndex }))
.filter(({ entries }) => entries.some((entry) => entry.name === "split-me"))
.map(({ shardIndex }) => shardIndex);
assert.deepEqual(splitSliceShardIndices, [0, 1]);
});
test("planShardAssignments: deterministic output for repeated calls", () => {
const packages = [
{ name: "p1", testFileCount: 41 },
{ name: "p2", testFileCount: 39 },
{ name: "p3", testFileCount: 27 },
{ name: "p4", testFileCount: 12 },
{ name: "p5", testFileCount: 8 },
];
assert.deepEqual(
planShardAssignments(packages, 3, { threshold: Number.POSITIVE_INFINITY }),
planShardAssignments(packages, 3, { threshold: Number.POSITIVE_INFINITY }),
);
});
test("selectShardPackages: returns the same shard assignment as planShardAssignments", () => {
const packages = [
{ name: "a", testFileCount: 5 },
{ name: "b", testFileCount: 1 },
];
const total = 3;
const shard = 2;
assert.deepEqual(
selectShardPackages(packages, shard, total),
planShardAssignments(packages, total)[shard - 1],
);
});
test("selectShardPackages: returns [] for empty shard index assignment", () => {
const result = selectShardPackages([{ name: "a", testFileCount: 1 }], 3, 3, {
threshold: Number.POSITIVE_INFINITY,
});
assert.deepEqual(result, []);
});
test("countPackageTestFiles: counts matching test files under __tests__", (t) => {
const tmpRoot = mkdtempSync(path.join(tmpdir(), "fn-4207-"));
t.after(() => rmSync(tmpRoot, { recursive: true, force: true }));
mkdirSync(path.join(tmpRoot, "pkg/src/__tests__"), { recursive: true });
mkdirSync(path.join(tmpRoot, "pkg/app/__tests__"), { recursive: true });
mkdirSync(path.join(tmpRoot, "pkg/scripts/__tests__"), { recursive: true });
writeFileSync(path.join(tmpRoot, "pkg/src/__tests__/foo.test.ts"), "");
writeFileSync(path.join(tmpRoot, "pkg/app/__tests__/bar.test.tsx"), "");
writeFileSync(path.join(tmpRoot, "pkg/scripts/__tests__/baz.test.mjs"), "");
writeFileSync(path.join(tmpRoot, "pkg/src/__tests__/helper.ts"), "");
writeFileSync(path.join(tmpRoot, "pkg/README.md"), "");
assert.equal(countPackageTestFiles("pkg", { projectRoot: tmpRoot }), 3);
});
test("countPackageTestFiles: returns 0 when no __tests__ matches exist", (t) => {
const tmpRoot = mkdtempSync(path.join(tmpdir(), "fn-4207-empty-"));
t.after(() => rmSync(tmpRoot, { recursive: true, force: true }));
mkdirSync(path.join(tmpRoot, "pkg/src"), { recursive: true });
writeFileSync(path.join(tmpRoot, "pkg/src/index.ts"), "export {}\n");
assert.equal(countPackageTestFiles("pkg", { projectRoot: tmpRoot }), 0);
});
// ---------------------------------------------------------------------------
// U6 (R3, R4): duration-based weighting, staleness, dashboard lane distribution
// ---------------------------------------------------------------------------
test("U6: duration weights produce balanced shards on skewed inputs where file-count would skew", () => {
// 10 "engine" real-git files at 10s each (100s) vs 50 "core" unit files at
// 0.4s each (20s). File-count weighting would call core the heavier package.
const durationPackages = [
{ name: "engine", weight: 100_000, splittable: true },
{ name: "core", weight: 20_000, splittable: true },
{ name: "cli", weight: 12_000, splittable: true },
{ name: "tail", weight: 8_000, splittable: true },
];
const shards = planShardAssignments(durationPackages, 2);
const totals = shards.map((entries) => entries.reduce((sum, e) => sum + e.weight, 0));
const spread = (Math.max(...totals) - Math.min(...totals)) / (totals.reduce((a, b) => a + b, 0) / 2);
assert.ok(spread <= 0.05, `duration spread ${(spread * 100).toFixed(1)}% should be <=5% (${totals.join("/")})`);
// Motivation fixture: the SAME workload weighted by file count is badly
// skewed because the heavy engine has few files.
const fileCountPackages = [
{ name: "engine", testFileCount: 10 },
{ name: "core", testFileCount: 50 },
{ name: "cli", testFileCount: 30 },
{ name: "tail", testFileCount: 20 },
];
// Disable splitting to expose the raw file-count balance signal.
const fcShards = planShardAssignments(fileCountPackages, 2, { threshold: Number.POSITIVE_INFINITY });
const fcDurations = { engine: 100_000, core: 20_000, cli: 12_000, tail: 8_000 };
const fcTotals = fcShards.map((entries) => entries.reduce((sum, e) => sum + fcDurations[e.name], 0));
const fcSpread =
(Math.max(...fcTotals) - Math.min(...fcTotals)) / (fcTotals.reduce((a, b) => a + b, 0) / 2);
assert.ok(
fcSpread > 0.05,
`file-count weighting should mis-balance real durations (got ${(fcSpread * 100).toFixed(1)}%, ${fcTotals.join("/")})`,
);
});
test("U6: loadPlanningTimings sums per-package durations and derives a median per-file fallback", (t) => {
const dir = mkdtempSync(path.join(tmpdir(), "u6-timings-"));
t.after(() => rmSync(dir, { recursive: true, force: true }));
const snapshotPath = writeSnapshot(dir, new Date().toISOString(), {
"@fusion/core": { files: { "packages/core/a.test.ts": 200, "packages/core/b.test.ts": 600 } },
"@fusion/engine": { files: { "packages/engine/x.test.ts": 1000 } },
});
const timings = loadPlanningTimings({ snapshotPath });
assert.equal(timings.present, true);
assert.equal(timings.stale, false);
assert.equal(timings.fileDurations.get("packages/core/a.test.ts"), 200);
// median of [200, 600, 1000] = 600
assert.equal(timings.medianPerFileMs, 600);
});
test("U6: sumFileDurations reports timed/untimed counts", () => {
const map = new Map([["a.test.ts", 300]]);
const result = sumFileDurations(["a.test.ts", "missing.test.ts"], map);
assert.equal(result.durationMs, 300);
assert.equal(result.timedCount, 1);
assert.equal(result.untimedCount, 1);
});
test("U6: untimed package falls back to median-scaled file-count weight with a warning", (t) => {
const projectRoot = mkdtempSync(path.join(tmpdir(), "u6-fallback-"));
t.after(() => rmSync(projectRoot, { recursive: true, force: true }));
// Build a fake package with 3 test files, none present in the snapshot.
mkdirSync(path.join(projectRoot, "packages/newpkg/src/__tests__"), { recursive: true });
for (const f of ["one", "two", "three"]) {
writeFileSync(path.join(projectRoot, `packages/newpkg/src/__tests__/${f}.test.ts`), "");
}
const snapshotPath = writeSnapshot(projectRoot, new Date().toISOString(), {
"@fusion/core": { files: { "packages/core/a.test.ts": 500, "packages/core/b.test.ts": 500 } },
});
const timings = loadPlanningTimings({ snapshotPath });
const weighted = computePackageDurationWeight(
{ name: "@fusion/newpkg", dir: "packages/newpkg" },
timings,
{ projectRoot },
);
assert.equal(weighted.fullyUntimed, true);
// 3 untimed files * median(500) = 1500
assert.equal(weighted.weight, 1500);
});
test("U6: staleness — snapshot older than the budget is flagged stale (warning, not failure)", (t) => {
const dir = mkdtempSync(path.join(tmpdir(), "u6-stale-"));
t.after(() => rmSync(dir, { recursive: true, force: true }));
const old = new Date(Date.now() - (TIMINGS_STALENESS_DAYS + 10) * 86_400_000).toISOString();
const snapshotPath = writeSnapshot(dir, old, { p: { files: { "a.test.ts": 100 } } });
const timings = loadPlanningTimings({ snapshotPath });
assert.equal(timings.stale, true);
assert.ok(timings.ageDays > TIMINGS_STALENESS_DAYS);
const fresh = new Date(Date.now() - 1 * 86_400_000).toISOString();
const freshPath = writeSnapshot(dir, fresh, { p: { files: { "a.test.ts": 100 } } });
assert.equal(loadPlanningTimings({ snapshotPath: freshPath }).stale, false);
});
test("U6: --check-timings-staleness exits non-zero on a stale snapshot", () => {
const result = spawnSync(
process.execPath,
[path.join(REPO_ROOT, "scripts/ci-test-shard.mjs"), "--check-timings-staleness"],
{ cwd: STALE_FIXTURE_ROOT, encoding: "utf8" },
);
assert.equal(result.status, 1, result.stderr || result.stdout);
assert.match(result.stderr, /stale/i);
});
test("U6: enumerateDashboardLanes expands the test chain to leaf vitest lanes (no hardcoding)", () => {
const scripts = {
test: "pnpm run test:app && pnpm run test:api",
"test:app": "pnpm run test:app:foundation && pnpm run test:app:components",
"test:app:foundation": "vitest run --project dashboard-app-quality-foundation-api",
"test:app:components": "vitest run --project dashboard-app-quality-components-a",
"test:api": "vitest run --project dashboard-api-quality",
};
const lanes = enumerateDashboardLanes(scripts, "test");
assert.deepEqual(lanes, [
"test:app:foundation",
"test:app:components",
"test:api",
]);
});
test("U6: enumerateDashboardLanes reads lanes from a fixture package.json shape", () => {
const pkgJson = {
scripts: {
test: "pnpm run test:quality:app && pnpm run test:quality:api",
"test:quality:app": "pnpm run test:quality:app:a && pnpm run test:quality:app:b",
"test:quality:app:a": "vitest run --project x",
"test:quality:app:b": "vitest run --project y",
"test:quality:api": "vitest run --project z",
// unrelated script not reachable from `test` must not appear
"test:deep": "vitest run --project deep",
},
};
const lanes = enumerateDashboardLanes(pkgJson.scripts, "test");
assert.deepEqual(lanes, ["test:quality:app:a", "test:quality:app:b", "test:quality:api"]);
});
test("U6: laneProjectNames extracts --project targets including = and space forms", () => {
assert.deepEqual(laneProjectNames("vitest run --project foo --project=bar baz"), ["foo", "bar"]);
});
test("U6: every dashboard lane is assigned to exactly one shard (union == enumerated list)", () => {
const lanes = ["lane-a", "lane-b", "lane-c", "lane-d", "lane-e"];
const units = [
{ name: "@fusion/engine", weight: 50_000, splittable: true },
{ name: "@fusion/core", weight: 40_000, splittable: true },
...lanes.map((lane, i) => ({
name: "@fusion/dashboard",
lane,
runKind: "dashboard-lane",
weight: 10_000 + i * 1000,
splittable: false,
})),
];
const shards = planShardAssignments(units, 4);
const occur = new Map();
let dashboardShardSlices = 0;
for (const shard of shards) {
for (const entry of shard) {
if (entry.runKind === "dashboard-lane") occur.set(entry.lane, (occur.get(entry.lane) ?? 0) + 1);
if (entry.name === "@fusion/dashboard" && entry.shardCount) dashboardShardSlices += 1;
}
}
assert.equal(dashboardShardSlices, 0, "dashboard lane units must never be vitest --shard sliced");
assert.deepEqual([...occur.keys()].sort(), [...lanes].sort());
for (const lane of lanes) assert.equal(occur.get(lane), 1, `lane ${lane} should appear exactly once`);
});
test("U6: buildShardCommands emits per-lane `run <lane>`, plain `test`, and virtual `--shard`", () => {
const entries = [
{ name: "@fusion/core", weight: 1 },
{ name: "@fusion/engine", weight: 1, shardIndex: 1, shardCount: 2 },
{ name: "@fusion/dashboard", weight: 1, runKind: "dashboard-lane", lane: "test:quality:api" },
];
const commands = buildShardCommands(entries);
const plain = commands.find((c) => c.kind === "plain");
const virtual = commands.find((c) => c.kind === "virtual");
const lane = commands.find((c) => c.kind === "dashboard-lane");
assert.deepEqual(plain.args, ["--filter", "@fusion/core", "test"]);
assert.deepEqual(virtual.args, ["--filter", "@fusion/engine", "test", "--shard=1/2"]);
assert.deepEqual(lane.args, ["--filter", "@fusion/dashboard", "run", "test:quality:api"]);
});
test("U6: --dry-run prints planned commands and per-shard weight for all 4 shards", () => {
const result = spawnSync(
process.execPath,
[path.join(REPO_ROOT, "scripts/ci-test-shard.mjs"), "--dry-run", "--total", "4"],
{ cwd: REPO_ROOT, encoding: "utf8" },
);
assert.equal(result.status, 0, result.stderr);
for (let n = 1; n <= 4; n += 1) {
assert.match(result.stdout, new RegExp(`shard ${n}/4 — weight`));
}
// Each dashboard lane appears exactly once across the printed plan.
const laneMatches = result.stdout.match(/--filter @fusion\/dashboard run [\w:-]+/g) ?? [];
const laneNames = laneMatches.map((m) => m.split("run ")[1]);
assert.equal(new Set(laneNames).size, laneNames.length, "no dashboard lane should be printed twice");
assert.ok(laneNames.length >= 10, `expected the dashboard lane chain, saw ${laneNames.length}`);
// Dashboard must NOT be virtual-sliced.
assert.doesNotMatch(result.stdout, /--filter @fusion\/dashboard test --shard/);
});