From 4b2b70ec79a0d419651436259f7423f826cdc868 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 20 Jun 2026 03:04:34 +0200 Subject: [PATCH] fix(test): guard gateway benchmark cli args --- scripts/bench-gateway-restart.ts | 39 +++++++++++++++++++++- scripts/bench-gateway-startup.ts | 38 ++++++++++++++++++++- test/scripts/bench-gateway-restart.test.ts | 21 ++++++++++++ test/scripts/bench-gateway-startup.test.ts | 21 ++++++++++++ 4 files changed, 117 insertions(+), 2 deletions(-) diff --git a/scripts/bench-gateway-restart.ts b/scripts/bench-gateway-restart.ts index 0bb555dfc1be..ace8e1988099 100644 --- a/scripts/bench-gateway-restart.ts +++ b/scripts/bench-gateway-restart.ts @@ -178,6 +178,21 @@ const DEFAULT_TIMEOUT_MS = 30_000; const DEFAULT_POST_READY_DELAY_MS = 250; const DEFAULT_ENTRY = "dist/entry.js"; const RESTART_INTENT_FILENAME = "gateway-restart-intent.json"; +const BOOLEAN_FLAGS = new Set(["--allow-failures", "--help", "-h", "--json"]); +const VALUE_FLAGS = new Set([ + "--case", + "--entry", + "--output", + "--post-ready-delay-ms", + "--restarts", + "--runs", + "--timeout-ms", + "--warmup", +]); + +class CliArgumentError extends Error { + override name = "CliArgumentError"; +} const BASE_CONFIG = { browser: { enabled: false }, @@ -233,11 +248,26 @@ const GATEWAY_CASES: readonly GatewayBenchCase[] = [ 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`); + throw new CliArgumentError(`${flag} requires a value`); } return value; } +function validateCliArgs(argv: string[]): void { + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index] ?? ""; + if (BOOLEAN_FLAGS.has(arg)) { + continue; + } + if (VALUE_FLAGS.has(arg)) { + readRequiredFlagValue(argv, index, arg); + index += 1; + continue; + } + throw new CliArgumentError(`Unknown argument: ${arg}`); + } +} + function parseFlagValue(argv: string[], flag: string): string | undefined { for (let index = 0; index < argv.length; index += 1) { if (argv[index] === flag) { @@ -319,6 +349,7 @@ function resolveCases(caseIds: string[]): GatewayBenchCase[] { } function parseOptions(argv: string[] = process.argv.slice(2)): CliOptions { + validateCliArgs(argv); return { allowFailures: hasFlag(argv, "--allow-failures"), cases: resolveCases(parseRepeatableFlag(argv, "--case")), @@ -1649,6 +1680,7 @@ export const testing = { shouldFailBenchmark, stopChild, summarizeCase, + validateCliArgs, waitForRestartProbe, writeConfig, writeRestartIntent, @@ -1656,6 +1688,11 @@ export const testing = { if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { main().catch((err: unknown) => { + if (err instanceof CliArgumentError) { + console.error(err.message); + process.exitCode = 1; + return; + } console.error(err instanceof Error ? err.stack : String(err)); process.exitCode = 1; }); diff --git a/scripts/bench-gateway-startup.ts b/scripts/bench-gateway-startup.ts index 3d5b8a1e85a3..d617dccc9db6 100644 --- a/scripts/bench-gateway-startup.ts +++ b/scripts/bench-gateway-startup.ts @@ -108,6 +108,20 @@ const DEFAULT_RUNS = 5; const DEFAULT_WARMUP = 1; const DEFAULT_TIMEOUT_MS = 30_000; const DEFAULT_ENTRY = "dist/entry.js"; +const BOOLEAN_FLAGS = new Set(["--help", "-h", "--json"]); +const VALUE_FLAGS = new Set([ + "--case", + "--cpu-prof-dir", + "--entry", + "--output", + "--runs", + "--timeout-ms", + "--warmup", +]); + +class CliArgumentError extends Error { + override name = "CliArgumentError"; +} const BASE_CONFIG = { browser: { enabled: false }, @@ -187,11 +201,26 @@ const GATEWAY_CASES: readonly GatewayBenchCase[] = [ 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`); + throw new CliArgumentError(`${flag} requires a value`); } return value; } +function validateCliArgs(argv: string[]): void { + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index] ?? ""; + if (BOOLEAN_FLAGS.has(arg)) { + continue; + } + if (VALUE_FLAGS.has(arg)) { + readRequiredFlagValue(argv, index, arg); + index += 1; + continue; + } + throw new CliArgumentError(`Unknown argument: ${arg}`); + } +} + function parseFlagValue(argv: string[], flag: string): string | undefined { for (let index = 0; index < argv.length; index += 1) { if (argv[index] === flag) { @@ -265,6 +294,7 @@ function resolveCases(caseIds: string[]): GatewayBenchCase[] { } function parseOptions(argv: string[] = process.argv.slice(2)): CliOptions { + validateCliArgs(argv); return { cases: resolveCases(parseRepeatableFlag(argv, "--case")), cpuProfDir: parseFlagValue(argv, "--cpu-prof-dir"), @@ -965,12 +995,18 @@ export const testing = { sanitizedEnv, stopChild, summarizeCase, + validateCliArgs, waitForProbe, writeConfig, }; if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { main().catch((err: unknown) => { + if (err instanceof CliArgumentError) { + console.error(err.message); + process.exitCode = 1; + return; + } console.error(err instanceof Error ? err.stack : String(err)); process.exitCode = 1; }); diff --git a/test/scripts/bench-gateway-restart.test.ts b/test/scripts/bench-gateway-restart.test.ts index e89d258d01fc..b4ff139466e0 100644 --- a/test/scripts/bench-gateway-restart.test.ts +++ b/test/scripts/bench-gateway-restart.test.ts @@ -42,6 +42,7 @@ describe("gateway restart benchmark script", () => { }); it("rejects ambiguous benchmark CLI values before spawning Node", () => { + expect(() => testing.parseOptions(["--wat"])).toThrow("Unknown argument: --wat"); expect(testing.parsePositiveInt("5", 1, "--restarts")).toBe(5); expect(testing.parseNonNegativeInt("0", 1, "--warmup")).toBe(0); expect( @@ -73,6 +74,26 @@ describe("gateway restart benchmark script", () => { expect(() => testing.resolveEntry("--inspect")).toThrow(/must be a file path/u); }); + it("rejects unknown benchmark CLI args before checking platform or running cases", () => { + const result = spawnSync( + process.execPath, + ["--import", "tsx", "scripts/bench-gateway-restart.ts", "--wat"], + { + cwd: process.cwd(), + encoding: "utf8", + env: { + ...process.env, + NODE_NO_WARNINGS: "1", + }, + }, + ); + + expect(result.status).toBe(1); + expect(result.stdout).toBe(""); + expect(result.stderr.trim()).toBe("Unknown argument: --wat"); + expect(result.stderr).not.toContain("\n at "); + }); + it("guards the SIGUSR1 restart benchmark on Windows", () => { expect(() => testing.ensureSupportedRestartPlatform("linux")).not.toThrow(); expect(() => testing.ensureSupportedRestartPlatform("darwin")).not.toThrow(); diff --git a/test/scripts/bench-gateway-startup.test.ts b/test/scripts/bench-gateway-startup.test.ts index f9c4ff3c0050..cb0e9f8bc1fe 100644 --- a/test/scripts/bench-gateway-startup.test.ts +++ b/test/scripts/bench-gateway-startup.test.ts @@ -52,6 +52,7 @@ describe("gateway startup benchmark script", () => { }); it("rejects ambiguous benchmark CLI values before spawning Node", () => { + expect(() => testing.parseOptions(["--wat"])).toThrow("Unknown argument: --wat"); expect(testing.parsePositiveInt("5", 1, "--runs")).toBe(5); expect(testing.parseNonNegativeInt("0", 1, "--warmup")).toBe(0); expect( @@ -83,6 +84,26 @@ describe("gateway startup benchmark script", () => { expect(() => testing.resolveEntry("--inspect")).toThrow(/must be a file path/u); }); + it("rejects unknown benchmark CLI args before running cases", () => { + const result = spawnSync( + process.execPath, + ["--import", "tsx", "scripts/bench-gateway-startup.ts", "--wat"], + { + cwd: process.cwd(), + encoding: "utf8", + env: { + ...process.env, + NODE_NO_WARNINGS: "1", + }, + }, + ); + + expect(result.status).toBe(1); + expect(result.stdout).toBe(""); + expect(result.stderr.trim()).toBe("Unknown argument: --wat"); + expect(result.stderr).not.toContain("\n at "); + }); + it("does not disable local-check policy in the child gateway environment", () => { const env = testing.sanitizedEnv("/tmp/openclaw-bench", "/tmp/openclaw-bench/config.json", { config: {},