Skip to content

Commit 5ee5f2e

Browse files
fix: fail benchmark reindex when indexer exits non-zero (#117)
* fix: fail benchmark reindex when indexer exits non-zero Check child exit code and surface stderr instead of recording timings for failed indexer runs. * fix: validate benchmark reindex runs is a positive integer
1 parent a444c40 commit 5ee5f2e

4 files changed

Lines changed: 123 additions & 27 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@stainless-code/codemap": patch
3+
---
4+
5+
Fail benchmark reindex runs when the spawned indexer exits non-zero instead of recording misleading timings.

src/benchmark-reindex.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { describe, expect, it } from "bun:test";
2+
3+
import { runBenchmarkReindex } from "./benchmark-reindex";
4+
5+
describe("runBenchmarkReindex", () => {
6+
it("throws when the indexer exits non-zero instead of recording a timing", async () => {
7+
let spawnCalls = 0;
8+
await expect(
9+
runBenchmarkReindex("fail case", ["--full"], {
10+
runs: 1,
11+
spawnIndexer: async () => {
12+
spawnCalls++;
13+
return {
14+
exitCode: 1,
15+
stderr: "indexer exploded",
16+
stdout: "",
17+
};
18+
},
19+
}),
20+
).rejects.toThrow(/benchmark reindex "fail case" failed \(exit 1\)/);
21+
expect(spawnCalls).toBe(1);
22+
});
23+
24+
it("rejects invalid runs values", async () => {
25+
const spawnIndexer = async () => ({
26+
exitCode: 0,
27+
stderr: "",
28+
stdout: "",
29+
});
30+
await expect(
31+
runBenchmarkReindex("bad-runs", [], { runs: 0, spawnIndexer }),
32+
).rejects.toThrow(/requires runs >= 1/);
33+
await expect(
34+
runBenchmarkReindex("bad-runs", [], { runs: -1, spawnIndexer }),
35+
).rejects.toThrow(/requires runs >= 1/);
36+
await expect(
37+
runBenchmarkReindex("bad-runs", [], { runs: 1.5, spawnIndexer }),
38+
).rejects.toThrow(/requires runs >= 1/);
39+
});
40+
41+
it("records timings when every run exits zero", async () => {
42+
const result = await runBenchmarkReindex("ok", [], {
43+
runs: 2,
44+
spawnIndexer: async () => ({
45+
exitCode: 0,
46+
stderr: "",
47+
stdout: "",
48+
}),
49+
});
50+
expect(result.label).toBe("ok");
51+
expect(result.runs).toBe(2);
52+
expect(result.avg).toBeGreaterThanOrEqual(0);
53+
});
54+
});

src/benchmark-reindex.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
async function timeMsAsync(fn: () => Promise<void>): Promise<{ ms: number }> {
2+
const start = performance.now();
3+
await fn();
4+
return { ms: performance.now() - start };
5+
}
6+
7+
export interface IndexerSpawnResult {
8+
exitCode: number | null;
9+
stderr: string;
10+
stdout: string;
11+
}
12+
13+
export type IndexerSpawn = (args: string[]) => Promise<IndexerSpawnResult>;
14+
15+
export async function runBenchmarkReindex(
16+
label: string,
17+
args: string[],
18+
opts: { spawnIndexer: IndexerSpawn; runs?: number },
19+
): Promise<{
20+
label: string;
21+
avg: number;
22+
min: number;
23+
max: number;
24+
runs: number;
25+
}> {
26+
const runs = opts.runs ?? 3;
27+
if (!Number.isInteger(runs) || runs < 1) {
28+
throw new Error(`benchmark reindex "${label}" requires runs >= 1`);
29+
}
30+
const times: number[] = [];
31+
for (let i = 0; i < runs; i++) {
32+
const t = await timeMsAsync(async () => {
33+
const { exitCode, stderr, stdout } = await opts.spawnIndexer(args);
34+
if (exitCode !== 0) {
35+
const detail = [stderr, stdout].filter(Boolean).join("\n").trim();
36+
throw new Error(
37+
`benchmark reindex "${label}" failed (exit ${exitCode ?? "?"}): ${detail || "(no output)"}`,
38+
);
39+
}
40+
});
41+
times.push(t.ms);
42+
}
43+
const avg = times.reduce((a, b) => a + b, 0) / runs;
44+
const min = Math.min(...times);
45+
const max = Math.max(...times);
46+
return { label, avg, min, max, runs };
47+
}

src/benchmark.ts

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { join, resolve } from "node:path";
44
import { loadScenariosFromConfigFile } from "./benchmark-config";
55
import { getDefaultScenarios } from "./benchmark-default-scenarios";
66
import type { Scenario } from "./benchmark-default-scenarios";
7+
import { runBenchmarkReindex } from "./benchmark-reindex";
78
import { loadUserConfig, resolveCodemapConfig } from "./config";
89
import { closeDb, openDb } from "./db";
910
import { configureResolver } from "./resolver";
@@ -40,14 +41,6 @@ function timeMs(fn: () => unknown): { result: unknown; ms: number } {
4041
return { result, ms: performance.now() - start };
4142
}
4243

43-
async function timeMsAsync(
44-
fn: () => Promise<unknown>,
45-
): Promise<{ result: unknown; ms: number }> {
46-
const start = performance.now();
47-
const result = await fn();
48-
return { result, ms: performance.now() - start };
49-
}
50-
5144
function fmtBytes(b: number): string {
5245
if (b < 1024) return `${b} B`;
5346
if (b < 1024 * 1024) return `${(b / 1024).toFixed(1)} KB`;
@@ -182,25 +175,22 @@ console.log(
182175

183176
const INDEXER_PATH = join(import.meta.dirname, "index.ts");
184177

185-
async function benchmarkReindex(label: string, args: string[]) {
186-
const runs = 3;
187-
const times: number[] = [];
188-
for (let i = 0; i < runs; i++) {
189-
const t = await timeMsAsync(async () => {
190-
const proc = Bun.spawn(["bun", INDEXER_PATH, ...args], {
191-
cwd: getProjectRoot(),
192-
stdout: "pipe",
193-
stderr: "pipe",
194-
});
195-
await proc.exited;
196-
return proc.exitCode;
197-
});
198-
times.push(t.ms);
199-
}
200-
const avg = times.reduce((a, b) => a + b, 0) / runs;
201-
const min = Math.min(...times);
202-
const max = Math.max(...times);
203-
return { label, avg, min, max, runs };
178+
async function spawnIndexer(args: string[]) {
179+
const proc = Bun.spawn(["bun", INDEXER_PATH, ...args], {
180+
cwd: getProjectRoot(),
181+
stdout: "pipe",
182+
stderr: "pipe",
183+
});
184+
await proc.exited;
185+
return {
186+
exitCode: proc.exitCode,
187+
stderr: await new Response(proc.stderr).text(),
188+
stdout: await new Response(proc.stdout).text(),
189+
};
190+
}
191+
192+
function benchmarkReindex(label: string, args: string[]) {
193+
return runBenchmarkReindex(label, args, { spawnIndexer });
204194
}
205195

206196
console.log(" ─── Reindex Benchmarks ───\n");

0 commit comments

Comments
 (0)