mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
Vitest's --shard cut the file list into slices of equal length, which gave the integration shards 954 s to 1 449 s. A sequencer now deals files out by their measured duration, plus a fixed per-file cost for import and setup, longest first to the shard with the least work, and starts the longest files first inside a shard. Timings live in tests/durations/*.json and are regenerated from Vitest JSON reports with scripts/test-durations.mjs; every CI shard uploads its report. Integration shards run three workers each, one cloned database per worker. The unit suite runs as three projects decided per file from its source: guards and unit share a module graph inside a worker, isolated keeps a fresh one for every file that mocks, spies, stubs or resets. In CI it is three duration-balanced shards next to a job for lint, format, types and the OpenAPI drift check. The required check names stay as they were. "Lint, Typecheck & Test" is now the job that answers for the static checks and every unit shard, the way "integration" already answers for its shards. The browser-install guard recognises a direct, flags-only vitest run as a run of the unit suite.
65 lines
2.7 KiB
TypeScript
65 lines
2.7 KiB
TypeScript
/**
|
|
* Which unit-test files can share a module graph, decided from their source.
|
|
*
|
|
* Vitest isolates every test file by default: each one re-imports the
|
|
* application modules it touches. In this suite that import is most of the
|
|
* run (63 % of the 869 s CI wall time before the split), and most files do
|
|
* not need it, because they neither replace a module nor change process
|
|
* state. Those files run in projects with `isolate: false`, where a worker
|
|
* keeps its module cache from one file to the next; everything else keeps
|
|
* full isolation.
|
|
*
|
|
* A file needs isolation when its source does anything that outlives the
|
|
* file in a shared graph: mocks or spies on a module, stubbed env or
|
|
* globals, fake timers, a module reset, an assignment to `process.env` or a
|
|
* global, or a `__reset…ForTests` hook. The match is deliberately broad; a
|
|
* false positive costs a little speed, a false negative costs a flaky
|
|
* neighbour. A file that turns out to need isolation for a reason this list
|
|
* misses says so with a `// vitest-isolate` comment.
|
|
*/
|
|
import { readFileSync } from "node:fs";
|
|
|
|
const NEEDS_ISOLATION = new RegExp(
|
|
[
|
|
String.raw`\bvi\s*\.\s*(?:mock|doMock|unmock|doUnmock|hoisted|stubEnv|stubGlobal|unstubAllEnvs|unstubAllGlobals|spyOn|useFakeTimers|setSystemTime|resetModules|importMock)\b`,
|
|
String.raw`\bprocess\.env(?:\.\w+|\[[^\]]+\])\s*=(?!=)`,
|
|
String.raw`\bdelete\s+process\.env`,
|
|
String.raw`\b(?:globalThis|global|window)(?:\.\w+|\[[^\]]+\])\s*=(?!=)`,
|
|
String.raw`Object\.defineProperty\(\s*(?:globalThis|global|window|process)`,
|
|
String.raw`__\w*[Rr]eset\w*ForTests`,
|
|
String.raw`process\.chdir\(`,
|
|
String.raw`vitest-isolate`,
|
|
].join("|"),
|
|
);
|
|
|
|
export function needsIsolation(source: string): boolean {
|
|
return NEEDS_ISOLATION.test(source);
|
|
}
|
|
|
|
export interface UnitProjectFiles {
|
|
/** Structural guards that share a module graph. */
|
|
guards: string[];
|
|
/** Behavioural unit and component tests that share a module graph. */
|
|
shared: string[];
|
|
/** Everything that mocks, stubs or resets; one fresh graph per file. */
|
|
isolated: string[];
|
|
}
|
|
|
|
const GUARD_LOCATIONS =
|
|
/^(?:src\/__tests__\/|scripts\/__tests__\/|eslint-plugins\/|tests\/)/;
|
|
|
|
/** Split `files` (paths relative to `root`) into the three projects. */
|
|
export function classifyUnitFiles(
|
|
root: string,
|
|
files: readonly string[],
|
|
): UnitProjectFiles {
|
|
const result: UnitProjectFiles = { guards: [], shared: [], isolated: [] };
|
|
for (const file of files) {
|
|
const source = readFileSync(`${root}/${file}`, "utf8");
|
|
if (needsIsolation(source)) result.isolated.push(file);
|
|
else if (GUARD_LOCATIONS.test(file)) result.guards.push(file);
|
|
else result.shared.push(file);
|
|
}
|
|
return result;
|
|
}
|