fix(test): reject missing gateway bench flag values

This commit is contained in:
Vincent Koc
2026-06-06 21:03:45 +02:00
parent c6ee13529f
commit e974d98811
4 changed files with 128 additions and 50 deletions
+42 -26
View File
@@ -218,20 +218,29 @@ const GATEWAY_CASES: readonly GatewayBenchCase[] = [
},
] as const;
function parseFlagValue(flag: string): string | undefined {
const index = process.argv.indexOf(flag);
if (index === -1) {
return undefined;
function readRequiredFlagValue(argv: string[], index: number, flag: string): string {
const value = argv[index + 1];
if (!value || value.startsWith("-")) {
throw new Error(`${flag} requires a value`);
}
return process.argv[index + 1];
return value;
}
function hasFlag(flag: string): boolean {
return process.argv.includes(flag);
function parseFlagValue(argv: string[], flag: string): string | undefined {
for (let index = 0; index < argv.length; index += 1) {
if (argv[index] === flag) {
return readRequiredFlagValue(argv, index, flag);
}
}
return undefined;
}
function hasHelpFlag(): boolean {
return hasFlag("--help") || hasFlag("-h");
function hasFlag(argv: string[], flag: string): boolean {
return argv.includes(flag);
}
function hasHelpFlag(argv: string[]): boolean {
return hasFlag(argv, "--help") || hasFlag(argv, "-h");
}
function ensureSupportedRestartPlatform(platform: NodeJS.Platform = process.platform): void {
@@ -242,11 +251,12 @@ function ensureSupportedRestartPlatform(platform: NodeJS.Platform = process.plat
}
}
function parseRepeatableFlag(flag: string): string[] {
function parseRepeatableFlag(argv: string[], flag: string): string[] {
const values: string[] = [];
for (let index = 0; index < process.argv.length; index += 1) {
if (process.argv[index] === flag && process.argv[index + 1]) {
values.push(process.argv[index + 1]);
for (let index = 0; index < argv.length; index += 1) {
if (argv[index] === flag) {
values.push(readRequiredFlagValue(argv, index, flag));
index += 1;
}
}
return values;
@@ -296,22 +306,26 @@ function resolveCases(caseIds: string[]): GatewayBenchCase[] {
});
}
function parseOptions(): CliOptions {
function parseOptions(argv: string[] = process.argv.slice(2)): CliOptions {
return {
allowFailures: hasFlag("--allow-failures"),
cases: resolveCases(parseRepeatableFlag("--case")),
entry: resolveEntry(parseFlagValue("--entry")),
json: hasFlag("--json"),
output: resolveOutputPath(parseFlagValue("--output")),
allowFailures: hasFlag(argv, "--allow-failures"),
cases: resolveCases(parseRepeatableFlag(argv, "--case")),
entry: resolveEntry(parseFlagValue(argv, "--entry")),
json: hasFlag(argv, "--json"),
output: resolveOutputPath(parseFlagValue(argv, "--output")),
postReadyDelayMs: parseNonNegativeInt(
parseFlagValue("--post-ready-delay-ms"),
parseFlagValue(argv, "--post-ready-delay-ms"),
DEFAULT_POST_READY_DELAY_MS,
"--post-ready-delay-ms",
),
restarts: parsePositiveInt(parseFlagValue("--restarts"), DEFAULT_RESTARTS, "--restarts"),
runs: parsePositiveInt(parseFlagValue("--runs"), DEFAULT_RUNS, "--runs"),
timeoutMs: parsePositiveInt(parseFlagValue("--timeout-ms"), DEFAULT_TIMEOUT_MS, "--timeout-ms"),
warmup: parseNonNegativeInt(parseFlagValue("--warmup"), DEFAULT_WARMUP, "--warmup"),
restarts: parsePositiveInt(parseFlagValue(argv, "--restarts"), DEFAULT_RESTARTS, "--restarts"),
runs: parsePositiveInt(parseFlagValue(argv, "--runs"), DEFAULT_RUNS, "--runs"),
timeoutMs: parsePositiveInt(
parseFlagValue(argv, "--timeout-ms"),
DEFAULT_TIMEOUT_MS,
"--timeout-ms",
),
warmup: parseNonNegativeInt(parseFlagValue(argv, "--warmup"), DEFAULT_WARMUP, "--warmup"),
};
}
@@ -1600,13 +1614,14 @@ function shouldFailBenchmark(results: CaseResult[], options: { allowFailures: bo
}
async function main() {
if (hasHelpFlag()) {
const argv = process.argv.slice(2);
if (hasHelpFlag(argv)) {
printUsage();
return;
}
ensureSupportedRestartPlatform();
const options = parseOptions();
const options = parseOptions(argv);
const results: CaseResult[] = [];
for (const benchCase of options.cases) {
results.push(
@@ -1665,6 +1680,7 @@ export const testing = {
hasInitialReadyLogs,
hasBenchmarkFailures,
parseNonNegativeInt,
parseOptions,
parsePositiveInt,
resolveRestartDeadlineFailure,
resolveEntry,
+40 -24
View File
@@ -178,27 +178,37 @@ const GATEWAY_CASES: readonly GatewayBenchCase[] = [
},
] as const;
function parseFlagValue(flag: string): string | undefined {
const index = process.argv.indexOf(flag);
if (index === -1) {
return undefined;
function readRequiredFlagValue(argv: string[], index: number, flag: string): string {
const value = argv[index + 1];
if (!value || value.startsWith("-")) {
throw new Error(`${flag} requires a value`);
}
return process.argv[index + 1];
return value;
}
function hasFlag(flag: string): boolean {
return process.argv.includes(flag);
function parseFlagValue(argv: string[], flag: string): string | undefined {
for (let index = 0; index < argv.length; index += 1) {
if (argv[index] === flag) {
return readRequiredFlagValue(argv, index, flag);
}
}
return undefined;
}
function hasHelpFlag(): boolean {
return hasFlag("--help") || hasFlag("-h");
function hasFlag(argv: string[], flag: string): boolean {
return argv.includes(flag);
}
function parseRepeatableFlag(flag: string): string[] {
function hasHelpFlag(argv: string[]): boolean {
return hasFlag(argv, "--help") || hasFlag(argv, "-h");
}
function parseRepeatableFlag(argv: string[], flag: string): string[] {
const values: string[] = [];
for (let index = 0; index < process.argv.length; index += 1) {
if (process.argv[index] === flag && process.argv[index + 1]) {
values.push(process.argv[index + 1]);
for (let index = 0; index < argv.length; index += 1) {
if (argv[index] === flag) {
values.push(readRequiredFlagValue(argv, index, flag));
index += 1;
}
}
return values;
@@ -248,16 +258,20 @@ function resolveCases(caseIds: string[]): GatewayBenchCase[] {
});
}
function parseOptions(): CliOptions {
function parseOptions(argv: string[] = process.argv.slice(2)): CliOptions {
return {
cases: resolveCases(parseRepeatableFlag("--case")),
cpuProfDir: parseFlagValue("--cpu-prof-dir"),
entry: resolveEntry(parseFlagValue("--entry")),
json: hasFlag("--json"),
output: resolveOutputPath(parseFlagValue("--output")),
runs: parsePositiveInt(parseFlagValue("--runs"), DEFAULT_RUNS, "--runs"),
timeoutMs: parsePositiveInt(parseFlagValue("--timeout-ms"), DEFAULT_TIMEOUT_MS, "--timeout-ms"),
warmup: parseNonNegativeInt(parseFlagValue("--warmup"), DEFAULT_WARMUP, "--warmup"),
cases: resolveCases(parseRepeatableFlag(argv, "--case")),
cpuProfDir: parseFlagValue(argv, "--cpu-prof-dir"),
entry: resolveEntry(parseFlagValue(argv, "--entry")),
json: hasFlag(argv, "--json"),
output: resolveOutputPath(parseFlagValue(argv, "--output")),
runs: parsePositiveInt(parseFlagValue(argv, "--runs"), DEFAULT_RUNS, "--runs"),
timeoutMs: parsePositiveInt(
parseFlagValue(argv, "--timeout-ms"),
DEFAULT_TIMEOUT_MS,
"--timeout-ms",
),
warmup: parseNonNegativeInt(parseFlagValue(argv, "--warmup"), DEFAULT_WARMUP, "--warmup"),
};
}
@@ -1033,12 +1047,13 @@ function printResult(result: CaseResult): void {
}
async function main() {
if (hasHelpFlag()) {
const argv = process.argv.slice(2);
if (hasHelpFlag(argv)) {
printUsage();
return;
}
const options = parseOptions();
const options = parseOptions(argv);
if (options.cpuProfDir) {
mkdirSync(options.cpuProfDir, { recursive: true });
}
@@ -1085,6 +1100,7 @@ export const testing = {
classifyProbeErrorKind,
collectResultFailures,
collectStartupTrace,
parseOptions,
parseNonNegativeInt,
parsePositiveInt,
resolveEntry,
@@ -44,9 +44,32 @@ describe("gateway restart benchmark script", () => {
it("rejects ambiguous benchmark CLI values before spawning Node", () => {
expect(testing.parsePositiveInt("5", 1, "--restarts")).toBe(5);
expect(testing.parseNonNegativeInt("0", 1, "--warmup")).toBe(0);
expect(
testing.parseOptions([
"--case",
"skipChannelsNoAcpxProbe",
"--output",
"restart.json",
"--allow-failures",
"--restarts",
"2",
]),
).toMatchObject({
allowFailures: true,
cases: [{ id: "skipChannelsNoAcpxProbe" }],
output: "restart.json",
restarts: 2,
});
expect(() => testing.parsePositiveInt("2abc", 1, "--restarts")).toThrow(
/--restarts must be an integer/u,
);
expect(() => testing.parseOptions(["--output", "--case", "skipChannels"])).toThrow(
"--output requires a value",
);
expect(() => testing.parseOptions(["--case"])).toThrow("--case requires a value");
expect(() => testing.parseOptions(["--restarts", "--runs", "1"])).toThrow(
"--restarts requires a value",
);
expect(() => testing.resolveEntry("--inspect")).toThrow(/must be a file path/u);
});
@@ -54,9 +54,32 @@ describe("gateway startup benchmark script", () => {
it("rejects ambiguous benchmark CLI values before spawning Node", () => {
expect(testing.parsePositiveInt("5", 1, "--runs")).toBe(5);
expect(testing.parseNonNegativeInt("0", 1, "--warmup")).toBe(0);
expect(
testing.parseOptions([
"--case",
"default",
"--output",
"startup.json",
"--json",
"--runs",
"2",
]),
).toMatchObject({
cases: [{ id: "default" }],
json: true,
output: "startup.json",
runs: 2,
});
expect(() => testing.parsePositiveInt("2abc", 1, "--runs")).toThrow(
/--runs must be an integer/u,
);
expect(() => testing.parseOptions(["--output", "--case", "default"])).toThrow(
"--output requires a value",
);
expect(() => testing.parseOptions(["--case"])).toThrow("--case requires a value");
expect(() => testing.parseOptions(["--runs", "--warmup", "0"])).toThrow(
"--runs requires a value",
);
expect(() => testing.resolveEntry("--inspect")).toThrow(/must be a file path/u);
});