mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
Merge branch 'release/v1.45.0' into feat/v145-runtime
This commit is contained in:
commit
a859adb59c
146 files changed
+24075
-13836
No files matched your search
@@ -157,6 +157,9 @@ describe("container dependency installation", () => {
|
||||
|
||||
expect(dockerfile).toContain(
|
||||
"COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./\n" +
|
||||
"# The dependency patches pnpm-workspace.yaml names; the frozen install\n" +
|
||||
"# refuses to run without them.\n" +
|
||||
"COPY patches patches\n" +
|
||||
"COPY scripts/prepare.mjs scripts/prepare.mjs\n" +
|
||||
"RUN pnpm install --frozen-lockfile --prod=false",
|
||||
);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Check each module's mutation score against its floor.
|
||||
*
|
||||
* node scripts/mutation-floors.mjs [reports/mutation/mutation.json]
|
||||
*
|
||||
* Stryker's own `thresholds.break` is one number for the whole run, which
|
||||
* lets a well-tested module hide a weak one. `stryker.floors.json` names a
|
||||
* floor per module instead (a module is a set of path prefixes), and this
|
||||
* script reads the JSON report Stryker writes and fails when any module
|
||||
* scores below its floor. The score is Stryker's: killed and timed-out
|
||||
* mutants over every mutant that was valid and covered or not, so a mutant no
|
||||
* test reaches counts against the module.
|
||||
*
|
||||
* Floors sit just below the last measured score. Raise one when a change
|
||||
* raises the score; a floor that is lowered to let a run pass has stopped
|
||||
* being a floor.
|
||||
*/
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { relative, resolve } from "node:path";
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const reportPath = resolve(
|
||||
root,
|
||||
process.argv[2] ?? "reports/mutation/mutation.json",
|
||||
);
|
||||
const floorsPath = resolve(root, "stryker.floors.json");
|
||||
|
||||
if (!existsSync(reportPath)) {
|
||||
console.error(
|
||||
`no mutation report at ${reportPath}; run pnpm exec stryker run`,
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const report = JSON.parse(readFileSync(reportPath, "utf8"));
|
||||
const { modules } = JSON.parse(readFileSync(floorsPath, "utf8"));
|
||||
|
||||
const DETECTED = new Set(["Killed", "Timeout"]);
|
||||
const UNDETECTED = new Set(["Survived", "NoCoverage"]);
|
||||
|
||||
const files = Object.entries(report.files ?? {}).map(([path, file]) => [
|
||||
relative(root, resolve(root, path)).split("\\").join("/"),
|
||||
file,
|
||||
]);
|
||||
|
||||
let failed = false;
|
||||
const rows = [];
|
||||
for (const [name, { paths, floor }] of Object.entries(modules)) {
|
||||
let detected = 0;
|
||||
let undetected = 0;
|
||||
let matched = 0;
|
||||
for (const [path, file] of files) {
|
||||
if (!paths.some((prefix) => path.startsWith(prefix))) continue;
|
||||
matched += 1;
|
||||
for (const mutant of file.mutants ?? []) {
|
||||
if (DETECTED.has(mutant.status)) detected += 1;
|
||||
else if (UNDETECTED.has(mutant.status)) undetected += 1;
|
||||
}
|
||||
}
|
||||
const total = detected + undetected;
|
||||
const score = total === 0 ? null : (100 * detected) / total;
|
||||
const ok = matched > 0 && score !== null && score >= floor;
|
||||
if (!ok) failed = true;
|
||||
rows.push(
|
||||
`${ok ? "ok " : "FAIL"} ${name.padEnd(16)} ${
|
||||
score === null ? "no mutants" : `${score.toFixed(1)} %`
|
||||
} (floor ${floor} %, ${detected}/${total} mutants, ${matched} files)`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(rows.join("\n"));
|
||||
if (failed) {
|
||||
console.error(
|
||||
"\nA module scored below its floor, or the report covered none of its files.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -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