diff --git a/scripts/bench-sqlite-reliability.ts b/scripts/bench-sqlite-reliability.ts index 963a2f2c686b..42dc9b73d243 100644 --- a/scripts/bench-sqlite-reliability.ts +++ b/scripts/bench-sqlite-reliability.ts @@ -2,76 +2,10 @@ import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import type { - CliOptions, - ProfileId, - ReliabilityReport, -} from "./lib/sqlite-reliability-contract.js"; +import { CliUsageError, parseSqliteReliabilityCli } from "./lib/sqlite-reliability-cli.js"; +import type { ReliabilityReport } from "./lib/sqlite-reliability-contract.js"; import { runReliabilityStress } from "./lib/sqlite-reliability-runner.js"; -const BOOLEAN_FLAGS = new Set(["--help"]); -const VALUE_FLAGS = new Set(["--agent", "--output", "--profile", "--repository", "--state-dir"]); - -class CliUsageError extends Error { - override name = "CliUsageError"; -} - -function parseFlagValue(flag: string, argv: string[]): string | undefined { - const index = argv.indexOf(flag); - if (index === -1) { - return undefined; - } - const value = argv[index + 1]; - if (!value || value.startsWith("-")) { - throw new CliUsageError(`${flag} requires a value`); - } - return value; -} - -function validateArgs(argv: string[]): void { - const seenValueFlags = new Set(); - for (let index = 0; index < argv.length; index += 1) { - const arg = argv[index] ?? ""; - if (BOOLEAN_FLAGS.has(arg)) { - continue; - } - if (!VALUE_FLAGS.has(arg)) { - throw new CliUsageError(`Unknown argument: ${arg}`); - } - if (seenValueFlags.has(arg)) { - throw new CliUsageError(`${arg} was provided more than once`); - } - seenValueFlags.add(arg); - const value = argv[index + 1]; - if (!value || value.startsWith("-")) { - throw new CliUsageError(`${arg} requires a value`); - } - index += 1; - } -} - -function parseProfile(raw: string | undefined): ProfileId { - if (!raw) { - return "default"; - } - if (raw === "smoke" || raw === "default" || raw === "large") { - return raw; - } - throw new CliUsageError( - `--profile must be one of smoke, default, large; got ${JSON.stringify(raw)}`, - ); -} - -function parseOptions(argv: string[]): CliOptions { - return { - agentId: parseFlagValue("--agent", argv) ?? null, - output: parseFlagValue("--output", argv) ?? null, - profile: parseProfile(parseFlagValue("--profile", argv)), - repository: parseFlagValue("--repository", argv) ?? null, - stateDir: parseFlagValue("--state-dir", argv) ?? null, - }; -} - function printUsage(): void { console.log(`OpenClaw SQLite reliability stress proof @@ -123,12 +57,12 @@ function printProofLines(report: ReliabilityReport): void { async function main(argv: string[]): Promise { try { - validateArgs(argv); - if (argv.includes("--help")) { + const cli = parseSqliteReliabilityCli(argv); + if (cli.help) { printUsage(); return; } - const options = parseOptions(argv); + const { options } = cli; const report = await runReliabilityStress(options); if (options.output) { fs.mkdirSync(path.dirname(options.output), { recursive: true }); diff --git a/scripts/lib/sqlite-reliability-cli.ts b/scripts/lib/sqlite-reliability-cli.ts new file mode 100644 index 000000000000..dba360530625 --- /dev/null +++ b/scripts/lib/sqlite-reliability-cli.ts @@ -0,0 +1,73 @@ +import type { CliOptions, ProfileId } from "./sqlite-reliability-contract.js"; + +const BOOLEAN_FLAGS = new Set(["--help"]); +const VALUE_FLAGS = new Set(["--agent", "--output", "--profile", "--repository", "--state-dir"]); + +export class CliUsageError extends Error { + override name = "CliUsageError"; +} + +function parseFlagValue(flag: string, argv: string[]): string | undefined { + const index = argv.indexOf(flag); + if (index === -1) { + return undefined; + } + const value = argv[index + 1]; + if (!value || value.startsWith("-")) { + throw new CliUsageError(`${flag} requires a value`); + } + return value; +} + +function validateArgs(argv: string[]): void { + const seenValueFlags = new Set(); + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index] ?? ""; + if (BOOLEAN_FLAGS.has(arg)) { + continue; + } + if (!VALUE_FLAGS.has(arg)) { + throw new CliUsageError(`Unknown argument: ${arg}`); + } + if (seenValueFlags.has(arg)) { + throw new CliUsageError(`${arg} was provided more than once`); + } + seenValueFlags.add(arg); + const value = argv[index + 1]; + if (!value || value.startsWith("-")) { + throw new CliUsageError(`${arg} requires a value`); + } + index += 1; + } +} + +function parseProfile(raw: string | undefined): ProfileId { + if (!raw) { + return "default"; + } + if (raw === "smoke" || raw === "default" || raw === "large") { + return raw; + } + throw new CliUsageError( + `--profile must be one of smoke, default, large; got ${JSON.stringify(raw)}`, + ); +} + +export function parseSqliteReliabilityCli( + argv: string[], +): { help: true } | { help: false; options: CliOptions } { + validateArgs(argv); + if (argv.includes("--help")) { + return { help: true }; + } + return { + help: false, + options: { + agentId: parseFlagValue("--agent", argv) ?? null, + output: parseFlagValue("--output", argv) ?? null, + profile: parseProfile(parseFlagValue("--profile", argv)), + repository: parseFlagValue("--repository", argv) ?? null, + stateDir: parseFlagValue("--state-dir", argv) ?? null, + }, + }; +} diff --git a/test/scripts/bench-sqlite-reliability.test.ts b/test/scripts/bench-sqlite-reliability.test.ts index ee078525248c..d886c5ac928b 100644 --- a/test/scripts/bench-sqlite-reliability.test.ts +++ b/test/scripts/bench-sqlite-reliability.test.ts @@ -5,6 +5,7 @@ import os from "node:os"; import path from "node:path"; import { DatabaseSync } from "node:sqlite"; import { afterEach, describe, expect, it } from "vitest"; +import { parseSqliteReliabilityCli } from "../../scripts/lib/sqlite-reliability-cli.js"; import { monitorSqliteWalDuring } from "../../scripts/lib/sqlite-reliability-wal-monitor.js"; const tempDirs: string[] = []; @@ -126,17 +127,15 @@ describe("scripts/bench-sqlite-reliability", () => { expect(unknown.stdout).toBe(""); expect(unknown.stderr.trim()).toBe("error: Unknown argument: --wat"); - const duplicate = runProof(["--profile", "smoke", "--profile", "large"]); - expect(duplicate.status).toBe(2); - expect(duplicate.stdout).toBe(""); - expect(duplicate.stderr.trim()).toBe("error: --profile was provided more than once"); - - const invalid = runProof(["--profile", "huge"]); - expect(invalid.status).toBe(2); - expect(invalid.stdout).toBe(""); - expect(invalid.stderr.trim()).toBe( - 'error: --profile must be one of smoke, default, large; got "huge"', + expect(() => parseSqliteReliabilityCli(["--profile", "smoke", "--profile", "large"])).toThrow( + "--profile was provided more than once", ); + expect(() => parseSqliteReliabilityCli(["--profile", "huge"])).toThrow( + '--profile must be one of smoke, default, large; got "huge"', + ); + expect(parseSqliteReliabilityCli(["--help", "--profile", "huge"])).toEqual({ + help: true, + }); }); it("reuses a state directory without stale rows or restore collisions", () => {