mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
ci: shard both suites by measured duration and split the unit suite
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.
This commit is contained in:
1 parent
28f9b1948b
commit
e9f89bbebf
10 files changed
+3736
-47
No files matched your search
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Regenerate a timing file for the duration-balanced sequencer
|
||||
* (`tests/durations/sequencer.ts`) from Vitest JSON reports.
|
||||
*
|
||||
* node scripts/test-durations.mjs <suite> <report.json> [<report.json> …]
|
||||
*
|
||||
* `<suite>` is `unit` or `integration` and names the file written,
|
||||
* `tests/durations/<suite>.json`. Pass every shard's report of one run; a
|
||||
* file measured twice keeps the longer reading. Files the reports do not
|
||||
* mention keep their previous value, so a partial run refines the table
|
||||
* rather than emptying it, and files that no longer exist are dropped.
|
||||
*
|
||||
* Produce a report with
|
||||
* pnpm exec vitest run [--config …] --reporter=default \
|
||||
* --reporter=json --outputFile.json=report.json
|
||||
* The nightly shuffled workflow uploads the reports of a CI run, which is
|
||||
* the better source: CI is where the shards have to balance.
|
||||
*/
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { relative, resolve } from "node:path";
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const [suite, ...reports] = process.argv.slice(2);
|
||||
|
||||
if (!["unit", "integration"].includes(suite ?? "") || reports.length === 0) {
|
||||
console.error(
|
||||
"usage: node scripts/test-durations.mjs <unit|integration> <report.json>…",
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const target = resolve(root, "tests/durations", `${suite}.json`);
|
||||
const previous = existsSync(target)
|
||||
? JSON.parse(readFileSync(target, "utf8")).files
|
||||
: {};
|
||||
|
||||
const measured = new Map();
|
||||
for (const report of reports) {
|
||||
const parsed = JSON.parse(readFileSync(report, "utf8"));
|
||||
for (const result of parsed.testResults ?? []) {
|
||||
if (typeof result.startTime !== "number") continue;
|
||||
if (typeof result.endTime !== "number") continue;
|
||||
const file = relative(root, result.name).split("\\").join("/");
|
||||
const seconds = Math.max(0, (result.endTime - result.startTime) / 1000);
|
||||
measured.set(file, Math.max(measured.get(file) ?? 0, seconds));
|
||||
}
|
||||
}
|
||||
|
||||
if (measured.size === 0) {
|
||||
console.error("the reports carry no per-file timings; nothing written");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const files = {};
|
||||
const keys = new Set([...Object.keys(previous), ...measured.keys()]);
|
||||
for (const file of [...keys].sort()) {
|
||||
if (!existsSync(resolve(root, file))) continue;
|
||||
const seconds = measured.get(file) ?? previous[file];
|
||||
files[file] = Math.round(seconds * 10) / 10;
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
target,
|
||||
JSON.stringify(
|
||||
{
|
||||
source: `vitest JSON report of ${measured.size} files, written ${new Date().toISOString().slice(0, 10)}`,
|
||||
files,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + "\n",
|
||||
);
|
||||
const total = Object.values(files).reduce((a, b) => a + b, 0);
|
||||
console.log(
|
||||
`${target}: ${Object.keys(files).length} files, ${Math.round(total)} s in total`,
|
||||
);
|
||||
Reference in new issue
Block a user