Skip to content

Commit e31e525

Browse files
committed
fix(bench): reject invalid routing benchmark options
1 parent 53e7b6a commit e31e525

2 files changed

Lines changed: 82 additions & 7 deletions

File tree

bench/routing/run.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, it } from 'bun:test'
2+
import { parseArgs } from './run'
3+
import { DEFAULT_TARGETS } from './targets'
4+
5+
describe('routing benchmark options', () => {
6+
it('preserves the default matrix and accepts fractional timing windows', () => {
7+
expect(parseArgs([])).toMatchObject({
8+
targets: DEFAULT_TARGETS.map(target => target.id),
9+
connections: 50,
10+
warmupSeconds: 5,
11+
durationSeconds: 30,
12+
runs: 3,
13+
db: true,
14+
})
15+
expect(parseArgs(['-c', '2', '-d', '0.5', '--warmup', '0.25', '--runs', '1'])).toMatchObject({
16+
connections: 2, durationSeconds: 0.5, warmupSeconds: 0.25, runs: 1,
17+
})
18+
expect(parseArgs(['--warmup', '0']).warmupSeconds).toBe(0)
19+
})
20+
21+
it.each(['--connections', '--runs'])('requires a positive safe integer for %s', (flag) => {
22+
for (const value of ['0', '-1', '1.5', 'NaN', 'Infinity', '9007199254740992', ''])
23+
expect(() => parseArgs([flag, value]), `${flag} ${value}`).toThrow()
24+
})
25+
26+
it.each(['0', '-1', 'NaN', 'Infinity', ''])('rejects invalid measured duration %s', (value) => {
27+
expect(() => parseArgs(['--duration', value])).toThrow()
28+
})
29+
30+
it.each(['-1', 'NaN', 'Infinity', ''])('rejects invalid warmup %s', (value) => {
31+
expect(() => parseArgs(['--warmup', value])).toThrow()
32+
})
33+
34+
it.each(['missing', 'stacks,missing', '', 'stacks,'])('rejects unknown or empty target selections: %s', (value) => {
35+
expect(() => parseArgs(['--targets', value])).toThrow()
36+
})
37+
38+
it.each(['missing', 'static-json,missing', '', 'static-json,'])('rejects unknown or empty scenario selections: %s', (value) => {
39+
expect(() => parseArgs(['--scenarios', value])).toThrow()
40+
})
41+
42+
it('rejects a matrix emptied by --no-db', () => {
43+
expect(() => parseArgs(['--scenarios', 'db-roundtrip', '--no-db'])).toThrow('No scenarios')
44+
})
45+
46+
it('accepts explicit tuned targets and a mixed matrix with --no-db', () => {
47+
expect(parseArgs(['--targets', 'stacks-wal-full,bun-raw', '--scenarios', 'static-json,db-roundtrip', '--no-db'])).toMatchObject({
48+
targets: ['stacks-wal-full', 'bun-raw'], scenarios: ['static-json', 'db-roundtrip'], db: false,
49+
})
50+
})
51+
52+
it('rejects missing values and unknown flags', () => {
53+
expect(() => parseArgs(['--runs'])).toThrow('--runs needs a value')
54+
expect(() => parseArgs(['--mystery'])).toThrow('Unknown flag')
55+
})
56+
})

bench/routing/run.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ interface Options {
4141
db: boolean
4242
}
4343

44-
function parseArgs(argv: string[]): Options {
44+
export function parseArgs(argv: string[]): Options {
4545
const opts: Options = {
4646
targets: DEFAULT_TARGETS.map(t => t.id),
4747
scenarios: SCENARIOS.map(s => s.id),
@@ -55,7 +55,7 @@ function parseArgs(argv: string[]): Options {
5555
const arg = argv[i]!
5656
const next = () => {
5757
const value = argv[++i]
58-
if (value == null) throw new Error(`${arg} needs a value`)
58+
if (value == null || value.trim() === '') throw new Error(`${arg} needs a value`)
5959
return value
6060
}
6161
switch (arg) {
@@ -75,6 +75,24 @@ function parseArgs(argv: string[]): Options {
7575
throw new Error(`Unknown flag ${arg}`)
7676
}
7777
}
78+
if (!Number.isSafeInteger(opts.connections) || opts.connections <= 0)
79+
throw new Error('--connections must be a positive safe integer')
80+
if (!Number.isSafeInteger(opts.runs) || opts.runs <= 0)
81+
throw new Error('--runs must be a positive safe integer')
82+
if (!Number.isFinite(opts.durationSeconds) || opts.durationSeconds <= 0)
83+
throw new Error('--duration must be a positive finite number')
84+
if (!Number.isFinite(opts.warmupSeconds) || opts.warmupSeconds < 0)
85+
throw new Error('--warmup must be a non-negative finite number')
86+
87+
const unknownTargets = opts.targets.filter(id => !TARGETS.some(target => target.id === id))
88+
if (unknownTargets.length > 0)
89+
throw new Error(`Unknown target(s): ${unknownTargets.map(id => JSON.stringify(id)).join(', ')}`)
90+
const unknownScenarios = opts.scenarios.filter(id => !SCENARIOS.some(scenario => scenario.id === id))
91+
if (unknownScenarios.length > 0)
92+
throw new Error(`Unknown scenario(s): ${unknownScenarios.map(id => JSON.stringify(id)).join(', ')}`)
93+
if (!SCENARIOS.some(scenario => opts.scenarios.includes(scenario.id) && (opts.db || !scenario.requiresDb)))
94+
throw new Error('No scenarios remain after applying --no-db')
95+
7896
return opts
7997
}
8098

@@ -83,10 +101,10 @@ const HELP = `bun bench/routing/run.ts [flags]
83101
--targets comma-separated target ids (default: ${DEFAULT_TARGETS.map(t => t.id).join(', ')})
84102
--scenarios comma-separated scenario ids (${SCENARIOS.map(s => s.id).join(', ')})
85103
--driver oha | bombardier | autocannon | builtin (default: first available)
86-
--connections concurrent connections (default 50)
87-
--warmup seconds discarded before measuring (default 5)
88-
--duration seconds measured (default 30)
89-
--runs repeats per scenario, median reported (default 3)
104+
--connections concurrent connections, positive integer (default 50)
105+
--warmup non-negative seconds discarded before measuring (default 5)
106+
--duration positive seconds measured (default 30)
107+
--runs positive integer repeats per scenario, median reported (default 3)
90108
--no-db skip the SQLite fixture and the db-roundtrip scenario
91109
92110
Available targets: ${TARGETS.map(t => t.id).join(', ')}`
@@ -208,4 +226,5 @@ async function main(): Promise<void> {
208226
console.log(report)
209227
}
210228

211-
await main()
229+
if (import.meta.main)
230+
await main()

0 commit comments

Comments
 (0)