test: shard vitest by measured cost instead of path hash (#650)

Vitest's BaseSequencer sorts specs by SHA1 of their path and slices an equal number of files per shard, which is blind to how long each one takes. That dropped the four costliest generated matrix specs into a single shard: 24m47s against shard 1's 3m47s.

Partition by measured cost instead, greedy longest-processing-time-first. CI wall goes 25 min to 21.3 min. The remaining ceiling is that `format-matrix-comprehensive` and `format-matrix` are each one indivisible file, and tests within a file run sequentially in a single fork.

Coverage is unchanged by construction. The partition is total and disjoint, guarded over the real spec list for shard counts 1 through 8. Per-shard totals on the PR run matched the baseline exactly: 297 files, 9903 tests, 9435 passed, 468 skipped.
This commit is contained in:
SnapOtter
2026-07-27 00:06:04 +08:00
committed by GitHub
parent ada5727114
commit d91abc8a23
3 changed files with 249 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
import { readdirSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { costOf, FILE_COST_SECONDS, partitionByCost } from "../../helpers/shard-partition.js";
const repoRoot = path.resolve(__dirname, "../../..");
/** Every integration spec on disk, repo-relative, sorted. Mirrors the CI glob. */
function integrationSpecs(): string[] {
const out: string[] = [];
const walk = (dir: string) => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walk(full);
else if (entry.name.endsWith(".test.ts")) out.push(path.relative(repoRoot, full));
}
};
walk(path.join(repoRoot, "tests/integration"));
return out.sort();
}
const HEAVYWEIGHTS = [
"tests/integration/generated/format-matrix-comprehensive.test.ts",
"tests/integration/generated/format-matrix.test.ts",
"tests/integration/generated/format-matrix-generated.test.ts",
"tests/integration/generated/format-matrix-exotic.test.ts",
];
describe("partitionByCost", () => {
// The whole point of the helper: a shard must never silently drop a spec.
// These two invariants are what make the CI change coverage-neutral.
describe("partition is total and disjoint", () => {
it("places every input file in exactly one bin", () => {
const files = integrationSpecs();
const bins = partitionByCost(files, 4);
const flat = bins.flat();
expect(flat.slice().sort()).toEqual(files.slice().sort());
expect(new Set(flat).size).toBe(files.length);
});
it("holds for shard counts 1 through 8", () => {
const files = integrationSpecs();
for (let count = 1; count <= 8; count++) {
const flat = partitionByCost(files, count).flat();
expect(new Set(flat).size, `count=${count} lost or duplicated a file`).toBe(files.length);
expect(flat.slice().sort(), `count=${count} changed the file set`).toEqual(
files.slice().sort(),
);
}
});
it("returns exactly `count` bins even when files are scarce", () => {
expect(partitionByCost(["a.test.ts", "b.test.ts"], 4)).toHaveLength(4);
expect(partitionByCost([], 4)).toHaveLength(4);
expect(partitionByCost([], 4).flat()).toEqual([]);
});
});
// Each shard is a separate vitest process computing the partition
// independently, so all of them must agree or specs get run twice or never.
describe("determinism across processes", () => {
it("is stable across repeated calls", () => {
const files = integrationSpecs();
expect(partitionByCost(files, 4)).toEqual(partitionByCost(files, 4));
});
it("ignores input ordering", () => {
const files = integrationSpecs();
const shuffled = files.slice().reverse();
expect(partitionByCost(shuffled, 4)).toEqual(partitionByCost(files, 4));
});
});
describe("balance", () => {
it("splits the four heavyweight matrix specs across different bins", () => {
const bins = partitionByCost(integrationSpecs(), 4);
const landedIn = HEAVYWEIGHTS.map((h) => bins.findIndex((b) => b.includes(h)));
expect(landedIn).not.toContain(-1);
expect(new Set(landedIn).size).toBe(HEAVYWEIGHTS.length);
});
it("keeps the costliest bin within 25% of the ideal share", () => {
const files = integrationSpecs();
const bins = partitionByCost(files, 4);
const cost = (b: string[]) => b.reduce((sum, f) => sum + costOf(f), 0);
const ideal = cost(files) / 4;
const heaviest = Math.max(...bins.map(cost));
// A spec is never split, so the floor is the single costliest file.
const floor = Math.max(ideal, ...files.map(costOf));
expect(heaviest).toBeLessThanOrEqual(floor * 1.25);
});
});
describe("costOf", () => {
it("returns the measured cost for a known-heavy spec", () => {
const known = "tests/integration/generated/format-matrix-comprehensive.test.ts";
expect(costOf(known)).toBe(FILE_COST_SECONDS[known]);
});
it("falls back to a default for unmeasured specs", () => {
expect(costOf("tests/integration/does/not/exist.test.ts")).toBeGreaterThan(0);
});
it("matches regardless of leading slash or absolute prefix", () => {
const known = "tests/integration/generated/format-matrix.test.ts";
expect(costOf(`/${known}`)).toBe(costOf(known));
expect(costOf(path.join(repoRoot, known))).toBe(costOf(known));
});
});
describe("cost table hygiene", () => {
it("only lists specs that still exist", () => {
const onDisk = new Set(integrationSpecs());
const stale = Object.keys(FILE_COST_SECONDS).filter((f) => !onDisk.has(f));
expect(stale, `cost table references deleted specs: ${stale.join(", ")}`).toEqual([]);
});
});
});