Files
cfw-autumn/server/tests/testRunner/outputParser.ts
2025-11-14 15:59:39 +00:00

142 lines
3.2 KiB
TypeScript

/**
* Parses test output to extract structured failure information
*/
export type TestFailure = {
testFile: string;
testName: string;
errorMessage: string;
errorLocation?: string;
stackTrace?: string;
};
export type TestSummary = {
totalFiles: number;
passedFiles: number;
failedFiles: number;
totalTests: number;
passedTests: number;
failedTests: number;
failures: TestFailure[];
duration: string;
};
/**
* Parses bun test output to extract failure information
*/
export function parseTestOutput(output: string): TestSummary {
const lines = output.split("\n");
const failures: TestFailure[] = [];
let totalFiles = 0;
let failedFiles = 0;
let totalTests = 0;
let passedTests = 0;
let failedTests = 0;
let duration = "0s";
// Extract summary statistics
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Match: "Ran X tests across Y file(s). [Zs]"
const ranMatch = line.match(/Ran (\d+) tests across (\d+) file/);
if (ranMatch) {
totalTests += Number.parseInt(ranMatch[1]);
totalFiles += Number.parseInt(ranMatch[2]);
}
// Match: "X pass"
const passMatch = line.match(/^\s*(\d+) pass/);
if (passMatch) {
passedTests += Number.parseInt(passMatch[1]);
}
// Match: "X fail"
const failMatch = line.match(/^\s*(\d+) fail/);
if (failMatch) {
failedTests += Number.parseInt(failMatch[1]);
}
// Match duration in summary
const durationMatch = line.match(/\[(\d+\.\d+s)\]/);
if (durationMatch) {
duration = durationMatch[1];
}
}
let passedFiles = totalFiles - failedFiles;
// Extract failure details
let currentTestFile = "";
const inFailureSection = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Detect test file being processed
const fileMatch = line.match(/tests\/[\w/.-]+\.test\.ts:/);
if (fileMatch) {
currentTestFile = fileMatch[0].replace(":", "");
}
// Detect failure markers
if (line.includes("(fail)")) {
const failMatch = line.match(/\(fail\)\s+(.+?)\s+\[(\d+\.\d+ms)\]/);
if (failMatch) {
const testName = failMatch[1];
// Look backwards for error message
let errorMessage = "";
let errorLocation = "";
for (let j = i - 1; j >= Math.max(0, i - 20); j--) {
const prevLine = lines[j];
// Find the error line (starts with "error:")
if (prevLine.startsWith("error:")) {
errorMessage = prevLine.replace("error:", "").trim();
break;
}
}
// Look forward for stack trace location
for (let j = i + 1; j < Math.min(lines.length, i + 10); j++) {
const nextLine = lines[j];
if (nextLine.includes("at ") && nextLine.includes(".ts:")) {
errorLocation = nextLine.trim();
break;
}
}
failures.push({
testFile: currentTestFile,
testName,
errorMessage,
errorLocation,
});
if (currentTestFile && !failedFiles) {
failedFiles++;
}
}
}
}
// Calculate failed files from failures
const uniqueFailedFiles = new Set(failures.map((f) => f.testFile));
failedFiles = uniqueFailedFiles.size;
passedFiles = totalFiles - failedFiles;
return {
totalFiles,
passedFiles,
failedFiles,
totalTests,
passedTests,
failedTests,
failures,
duration,
};
}