Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | 1x 9x 9x 9x 9x 9x 9x 9x 1x 9x 9x 2x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 1x 1x 9x 6x 3x 3x 9x 9x 9x 9x 9x 3x 9x 2x 1x 1x 2x | import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, relative } from "node:path";
const TOP_TEST_COUNT = 10;
function readBudgetConfiguration(env) {
const twoThresholdMode = env.TEST_BUDGET_WARN_MS !== undefined || env.TEST_BUDGET_FAIL_MS !== undefined;
const legacyBudgetConfigured = env.TEST_BUDGET_MS !== undefined;
const legacyBudgetMs = Number(env.TEST_BUDGET_MS ?? 0);
const warningBudgetMs = env.TEST_BUDGET_WARN_MS === undefined ? null : Number(env.TEST_BUDGET_WARN_MS);
const failureBudgetMs = env.TEST_BUDGET_FAIL_MS === undefined ? null : Number(env.TEST_BUDGET_FAIL_MS);
let configurationError = null;
if (twoThresholdMode && (warningBudgetMs <= 0 || failureBudgetMs <= 0 || failureBudgetMs < warningBudgetMs)) {
configurationError = "TEST_BUDGET_WARN_MS und TEST_BUDGET_FAIL_MS müssen positiv sein; die Hard-Fail-Schwelle muss mindestens der Warnschwelle entsprechen.";
}
let effectiveFailureBudgetMs = null;
if (twoThresholdMode) effectiveFailureBudgetMs = failureBudgetMs;
else Eif (legacyBudgetConfigured && legacyBudgetMs > 0) effectiveFailureBudgetMs = legacyBudgetMs;
const budgetMs = effectiveFailureBudgetMs;
return { twoThresholdMode, warningBudgetMs: twoThresholdMode ? warningBudgetMs : null, effectiveFailureBudgetMs, budgetMs, configurationError };
}
function summarizeBudget(durationMs, configuration) {
const { twoThresholdMode, warningBudgetMs, effectiveFailureBudgetMs, configurationError } = configuration;
const warningExceeded = !configurationError && twoThresholdMode && durationMs > warningBudgetMs;
const failureExceeded = !configurationError && effectiveFailureBudgetMs > 0 && durationMs > effectiveFailureBudgetMs;
return { warningExceeded, failureExceeded };
}
export default class TestBudgetReporter {
constructor() {
this.startedAt = Date.now();
this.tests = [];
}
onTestCaseResult(testCase) {
const diagnostic = testCase.diagnostic();
const result = testCase.result();
this.tests.push({
name: testCase.fullName,
file: relative(process.cwd(), testCase.module.moduleId),
durationMs: Math.round(diagnostic?.duration ?? 0),
state: result.state,
});
}
onTestRunEnd(_testModules, unhandledErrors, reason) {
const finishedAt = Date.now();
const durationMs = finishedAt - this.startedAt;
const suite = process.env.TEST_BUDGET_SUITE ?? "vitest";
const configuration = readBudgetConfiguration(process.env);
const { twoThresholdMode, warningBudgetMs, effectiveFailureBudgetMs, budgetMs, configurationError } = configuration;
const artifactPath =
process.env.TEST_METRICS_FILE ?? `test-results/${suite}-metrics.json`;
const effectiveWarningBudgetMs = warningBudgetMs;
const { warningExceeded, failureExceeded } = summarizeBudget(durationMs, configuration);
// In two-threshold mode the legacy field represents the hard-fail limit,
// so existing consumers continue to interpret this as a blocking failure.
const budgetExceeded = failureExceeded;
const counts = this.tests.reduce((result, test) => {
result[test.state] = (result[test.state] ?? 0) + 1;
return result;
}, {});
const slowestTests = [...this.tests]
.sort((left, right) => right.durationMs - left.durationMs)
.slice(0, TOP_TEST_COUNT);
const artifact = {
schemaVersion: 1,
suite,
budgetMs,
budgetExceeded,
warningBudgetMs: effectiveWarningBudgetMs,
failureBudgetMs: effectiveFailureBudgetMs,
warningExceeded,
failureExceeded,
configurationError,
startedAt: new Date(this.startedAt).toISOString(),
finishedAt: new Date(finishedAt).toISOString(),
durationMs,
status: reason,
counts,
unhandledErrorCount: unhandledErrors.length,
slowestTests,
};
mkdirSync(dirname(artifactPath), { recursive: true });
writeFileSync(artifactPath, `${JSON.stringify(artifact, null, 2)}\n`);
if (configurationError) {
console.error(`Ungültige Testbudget-Konfiguration: ${configurationError}`);
process.exitCode = 1;
}
if (twoThresholdMode && !configurationError) {
console.log(
`\nTestbudget [${suite}]: ${durationMs} ms | Warnschwelle: ${warningBudgetMs} ms | Hard Fail: ${effectiveFailureBudgetMs} ms`,
);
} else {
const budgetLabel = budgetMs ? `${budgetMs} ms` : "deaktiviert";
console.log(`\nTestbudget [${suite}]: ${durationMs} ms / ${budgetLabel}`);
}
console.log(`Messartefakt: ${artifactPath}`);
console.log(`Langsamste ${slowestTests.length} Tests:`);
for (const test of slowestTests) {
console.log(
` ${test.durationMs.toString().padStart(6)} ms ${test.name}`,
);
}
if (warningExceeded && !failureExceeded) {
console.warn(
`Testbudget-Warnschwelle überschritten: ${suite} benötigte ${durationMs} ms, Warnschwelle ${warningBudgetMs} ms, Hard-Fail-Schwelle ${effectiveFailureBudgetMs} ms.`,
);
}
if (failureExceeded) {
if (twoThresholdMode) {
console.error(
`Testbudget-Hard-Limit überschritten: ${suite} benötigte ${durationMs} ms, erlaubt sind maximal ${effectiveFailureBudgetMs} ms.`,
);
} else {
console.error(
`Testbudget ueberschritten: ${suite} benoetigte ${durationMs} ms, erlaubt sind ${budgetMs} ms.`,
);
}
process.exitCode = 1;
}
}
}
|