mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 10:16:03 -10:00
fix(run): report Go benchmark and repeated test results (#1868)
Quiet Go benchmarks print their measurement without a terminal pass event, so they stayed running and were shown as ignored when the run ended; repeated attempts (-count, -cpu) share a test path and were counted once. The parser now completes measured benchmarks when their package finishes (a failure at a later CPU setting still wins), maps CPU-suffixed and nested benchmark names back to the started test, and counts each new attempt while keeping a failed one failed. Co-authored-by: shohruh-genius <[email protected]>
This commit is contained in:
1 parent
fc5a7d8c91
commit
4beca55a54
2 files changed
+128
-19
No files matched your search
@@ -51,6 +51,61 @@ describe("Go JSON tests", () => {
|
||||
expect(results.roots[0]?.output).toContain("./main.go:3:1: undefined: missing");
|
||||
});
|
||||
|
||||
it("counts repeated test results once per start and preserves a failed attempt", () => {
|
||||
const parser = goParser();
|
||||
const results = new TestResults();
|
||||
const lines = [
|
||||
record("p", "run", "TestRepeated"),
|
||||
record("p", "fail", "TestRepeated"),
|
||||
record("p", "run", "TestRepeated"),
|
||||
record("p", "pass", "TestRepeated"),
|
||||
record("p", "pass", "TestRepeated"),
|
||||
record("p", "fail"),
|
||||
];
|
||||
lines.forEach((line) => results.apply(parser.feed(line)));
|
||||
expect(results.counts()).toMatchObject({ passed: 1, failed: 1, running: 0, total: 2 });
|
||||
expect(results.get("p\0TestRepeated")?.own).toBe("failed");
|
||||
});
|
||||
|
||||
it("completes benchmark measurements and explicit bench events in interleaved packages", () => {
|
||||
const parser = goParser();
|
||||
const results = new TestResults();
|
||||
const lines = [
|
||||
record("one", "run", "BenchmarkQuiet"),
|
||||
record("two", "run", "BenchmarkLogged"),
|
||||
record("one", "output", "BenchmarkQuiet", { Output: "BenchmarkQuiet-4 \t 1\t 214 ns/op\n" }),
|
||||
record("two", "output", "BenchmarkLogged", { Output: " bench_test.go:4: logged\n" }),
|
||||
record("two", "bench", "BenchmarkLogged-4"),
|
||||
record("one", "output", undefined, { Output: "BenchmarkQuiet-8 \t 1\t 210 ns/op\n" }),
|
||||
record("one", "run", "BenchmarkFails"),
|
||||
record("one", "output", "BenchmarkFails", { Output: "BenchmarkFails-4 \t 1\t 220 ns/op\n" }),
|
||||
record("one", "fail", "BenchmarkFails-8"),
|
||||
record("one", "fail"),
|
||||
record("two", "pass"),
|
||||
];
|
||||
lines.forEach((line) => results.apply(parser.feed(line)));
|
||||
expect(results.counts()).toMatchObject({ passed: 2, failed: 1, running: 0, total: 3 });
|
||||
expect(results.get("one\0BenchmarkQuiet")?.own).toBe("passed");
|
||||
expect(results.get("one\0BenchmarkFails")?.own).toBe("failed");
|
||||
expect(results.get("two\0BenchmarkLogged")?.own).toBe("passed");
|
||||
});
|
||||
|
||||
it("maps benchmark measurements without a Test field to the actual nested benchmark name", () => {
|
||||
const parser = goParser();
|
||||
const results = new TestResults();
|
||||
const lines = [
|
||||
record("p", "run", "BenchmarkNested"),
|
||||
record("p", "run", "BenchmarkNested/deep-2"),
|
||||
record("p", "output", undefined, { Output: "BenchmarkNested/deep-2 \t 1\t 214 ns/op\n" }),
|
||||
record("p", "output", undefined, { Output: "BenchmarkNested/deep-2-4 \t 1\t 210 ns/op\n" }),
|
||||
record("p", "pass"),
|
||||
];
|
||||
lines.forEach((line) => results.apply(parser.feed(line)));
|
||||
expect(results.counts()).toMatchObject({ passed: 1, failed: 0, running: 0, total: 1 });
|
||||
expect(results.get("p\0BenchmarkNested")?.children.map((node) => node.name)).toEqual(["deep-2"]);
|
||||
expect(results.get("p\0BenchmarkNested\0deep-2")?.own).toBe("passed");
|
||||
});
|
||||
|
||||
it("ignores malformed metadata and negative durations without throwing", () => {
|
||||
for (const line of [
|
||||
"{",
|
||||
|
||||
@@ -9,6 +9,7 @@ const RUN_RE = /^=== (?:RUN|CONT|PAUSE)\s+(\S+)/;
|
||||
const RESULT_RE = /^\s*--- (PASS|FAIL|SKIP): (\S+) \(([\d.]+s)\)/;
|
||||
const PKG_RE = /^(ok|FAIL|---)\s+(\S+)\s+(?:\(cached\)|([\d.]+s)|\[[^\]]+\])/;
|
||||
const LOCATION_RE = /^\s+([\w.-]+\.go):(\d+):/;
|
||||
const BENCHMARK_RE = /^(Benchmark\S*) *\t[ \t]*\d+ *(?:\t|$)/m;
|
||||
|
||||
function plainGoParser(): TestOutputParser {
|
||||
let buffer: TestEvent[] = [];
|
||||
@@ -85,6 +86,19 @@ interface GoEvent {
|
||||
Elapsed?: number;
|
||||
}
|
||||
|
||||
interface GoPackage {
|
||||
tests: boolean;
|
||||
failed: boolean;
|
||||
/** Benchmark names, with whether Go reported a successful measurement or bench event. */
|
||||
benchmarks: Map<string, boolean>;
|
||||
}
|
||||
|
||||
function benchmarkName(pkg: GoPackage, name: string): string {
|
||||
if (pkg.benchmarks.has(name)) return name;
|
||||
const withoutCpu = name.replace(/-\d+$/, "");
|
||||
return pkg.benchmarks.has(withoutCpu) ? withoutCpu : name;
|
||||
}
|
||||
|
||||
export function goJsonEvent(line: string): GoEvent | null {
|
||||
if (!line.startsWith("{")) return null;
|
||||
try {
|
||||
@@ -94,9 +108,19 @@ export function goJsonEvent(line: string): GoEvent | null {
|
||||
if (
|
||||
typeof pkg !== "string" ||
|
||||
!pkg ||
|
||||
!["start", "run", "pause", "cont", "output", "pass", "fail", "skip", "build-output", "build-fail"].includes(
|
||||
event.Action,
|
||||
)
|
||||
![
|
||||
"start",
|
||||
"run",
|
||||
"pause",
|
||||
"cont",
|
||||
"output",
|
||||
"pass",
|
||||
"bench",
|
||||
"fail",
|
||||
"skip",
|
||||
"build-output",
|
||||
"build-fail",
|
||||
].includes(event.Action)
|
||||
)
|
||||
return null;
|
||||
if (event.Test !== undefined && typeof event.Test !== "string") return null;
|
||||
@@ -111,19 +135,48 @@ export function goParser(): TestOutputParser {
|
||||
const plain = plainGoParser();
|
||||
const located = new Set<string>();
|
||||
const finished = new Set<string>();
|
||||
const packages = new Map<string, { tests: boolean; failed: boolean }>();
|
||||
const packages = new Map<string, GoPackage>();
|
||||
const totals = { passed: 0, failed: 0, skipped: 0 };
|
||||
|
||||
function finish(pkg: GoPackage, path: string[], status: keyof typeof totals, durationMs?: number): TestEvent[] {
|
||||
const key = path.join("\0");
|
||||
if (finished.has(key)) return [];
|
||||
finished.add(key);
|
||||
if (path.length === 2) {
|
||||
totals[status]++;
|
||||
if (status === "failed") pkg.failed = true;
|
||||
} else if (path.length === 1 && (!pkg.tests || (status === "failed" && !pkg.failed))) totals[status]++;
|
||||
return [{ type: "result", path, status, durationMs }];
|
||||
}
|
||||
|
||||
return {
|
||||
feed(line) {
|
||||
const e = goJsonEvent(line);
|
||||
if (!e) return plain.feed(line);
|
||||
const pkg = packages.get(e.Package) ?? { tests: false, failed: false };
|
||||
const pkg = packages.get(e.Package) ?? { tests: false, failed: false, benchmarks: new Map() };
|
||||
packages.set(e.Package, pkg);
|
||||
const path = [e.Package, ...(e.Test ? e.Test.split("/") : [])];
|
||||
if (e.Test) pkg.tests = true;
|
||||
if (e.Action === "run") return [{ type: "start", path }];
|
||||
const benchmark = e.Test?.startsWith("Benchmark") ? e.Test : undefined;
|
||||
const name = benchmark && e.Action !== "run" ? benchmarkName(pkg, benchmark) : e.Test;
|
||||
const path = [e.Package, ...(name ? name.split("/") : [])];
|
||||
if (name) pkg.tests = true;
|
||||
if (e.Action === "run") {
|
||||
finished.delete(path.join("\0"));
|
||||
if (benchmark) pkg.benchmarks.set(benchmark, false);
|
||||
return [{ type: "start", path }];
|
||||
}
|
||||
if (e.Action === "bench") {
|
||||
if (name) pkg.benchmarks.set(name, true);
|
||||
return [];
|
||||
}
|
||||
if (e.Action === "output" || e.Action === "build-output") {
|
||||
if (!e.Output) return [];
|
||||
if (e.Action === "output" && (!name || benchmark)) {
|
||||
const measurement = BENCHMARK_RE.exec(e.Output)?.[1];
|
||||
if (measurement) {
|
||||
const measured = benchmarkName(pkg, measurement);
|
||||
if (pkg.benchmarks.has(measured) && (!name || name === measured)) pkg.benchmarks.set(measured, true);
|
||||
}
|
||||
}
|
||||
const lines = e.Output.replace(/\n$/, "").split("\n");
|
||||
const events: TestEvent[] = lines.map((line) => ({ type: "output", path, line }));
|
||||
const key = path.join("\0");
|
||||
@@ -144,20 +197,21 @@ export function goParser(): TestOutputParser {
|
||||
return events;
|
||||
}
|
||||
if (!["pass", "fail", "skip", "build-fail"].includes(e.Action)) return [];
|
||||
const key = path.join("\0");
|
||||
if (finished.has(key)) return [];
|
||||
finished.add(key);
|
||||
const status = e.Action === "pass" ? "passed" : e.Action === "skip" ? "skipped" : "failed";
|
||||
if (e.Test && path.length === 2) {
|
||||
totals[status]++;
|
||||
if (status === "failed") pkg.failed = true;
|
||||
} else if (!e.Test && (!pkg.tests || (status === "failed" && !pkg.failed))) totals[status]++;
|
||||
const durationMs =
|
||||
typeof e.Elapsed === "number" && Number.isFinite(e.Elapsed) && e.Elapsed >= 0 ? e.Elapsed * 1000 : undefined;
|
||||
return [
|
||||
{ type: "result", path, status, durationMs },
|
||||
{ type: "summary", ...totals },
|
||||
];
|
||||
const events: TestEvent[] = [];
|
||||
if (!name) {
|
||||
// Quiet benchmarks can emit measurements without a terminal event. Wait for the
|
||||
// package result so a failure from a later CPU setting can take precedence.
|
||||
for (const [test, measured] of pkg.benchmarks) {
|
||||
if (measured) events.push(...finish(pkg, [e.Package, ...test.split("/")], "passed"));
|
||||
}
|
||||
pkg.benchmarks.clear();
|
||||
}
|
||||
events.push(...finish(pkg, path, status, durationMs));
|
||||
if (events.length) events.push({ type: "summary", ...totals });
|
||||
return events;
|
||||
},
|
||||
end: () => plain.end?.() ?? [],
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user