import { spawnSync } from "node:child_process"; import path from "node:path"; import { floatFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs"; import { formatMs } from "./lib/vitest-report-cli-utils.mjs"; function parseArgs(argv) { const args = parseFlagArgs( argv, { cwd: process.cwd(), ref: "origin/main", rss: process.platform === "darwin", mode: "ref", }, [ stringFlag("--cwd", "cwd"), stringFlag("--ref", "ref"), floatFlag("--max-workers", "maxWorkers", { min: 1 }), ], { allowUnknownOptions: true, onUnhandledArg(arg, target) { if (arg === "--no-rss") { target.rss = false; return "handled"; } if (arg === "--worktree") { target.mode = "worktree"; return "handled"; } return undefined; }, }, ); return { cwd: path.resolve(args.cwd), mode: args.mode, ref: args.ref, rss: args.rss, ...(typeof args.maxWorkers === "number" ? { maxWorkers: Math.trunc(args.maxWorkers) } : {}), }; } function quoteArg(arg) { return /[^A-Za-z0-9_./:-]/.test(arg) ? JSON.stringify(arg) : arg; } function runGitList(args, cwd) { const result = spawnSync("git", args, { cwd, encoding: "utf8", }); if (result.status !== 0) { throw new Error(result.stderr || result.stdout || `git ${args.join(" ")} failed`); } return result.stdout .split("\n") .map((line) => line.trim()) .filter((line) => line.length > 0); } function listChangedPaths(opts) { if (opts.mode === "worktree") { return [ ...new Set([ ...runGitList(["diff", "--name-only", "--relative", "HEAD", "--"], opts.cwd), ...runGitList(["ls-files", "--others", "--exclude-standard"], opts.cwd), ]), ].toSorted((left, right) => left.localeCompare(right)); } return runGitList(["diff", "--name-only", `${opts.ref}...HEAD`], opts.cwd); } function parseMaxRssKb(output) { const match = output.match(/(\d+)\s+maximum resident set size/u); return match ? Number.parseInt(match[1], 10) : null; } function formatRss(valueKb) { if (valueKb === null) { return "n/a"; } return `${(valueKb / 1024).toFixed(1)}MB`; } function runBenchCommand(params) { const env = { ...process.env }; if (typeof params.maxWorkers === "number") { env.OPENCLAW_VITEST_MAX_WORKERS = String(params.maxWorkers); } const startedAt = process.hrtime.bigint(); const commandArgs = params.rss ? ["-l", ...params.command] : params.command; const result = spawnSync( params.rss ? "/usr/bin/time" : commandArgs[0], params.rss ? commandArgs : commandArgs.slice(1), { cwd: params.cwd, env, encoding: "utf8", maxBuffer: 1024 * 1024 * 32, }, ); const elapsedMs = Number(process.hrtime.bigint() - startedAt) / 1_000_000; const output = `${result.stdout ?? ""}${result.stderr ?? ""}`; return { elapsedMs, maxRssKb: params.rss ? parseMaxRssKb(output) : null, status: result.status ?? 1, output, }; } function printRunSummary(label, result) { console.log( `${label.padEnd(8, " ")} wall=${formatMs(result.elapsedMs).padStart(9, " ")} rss=${formatRss( result.maxRssKb, ).padStart(9, " ")}`, ); } const opts = parseArgs(process.argv.slice(2)); const changedPaths = listChangedPaths(opts); if (changedPaths.length === 0) { console.log( opts.mode === "worktree" ? "[bench-test-changed] no changed paths in worktree" : `[bench-test-changed] no changed paths for ${opts.ref}...HEAD`, ); process.exit(0); } console.log( opts.mode === "worktree" ? "[bench-test-changed] mode=worktree" : `[bench-test-changed] ref=${opts.ref}`, ); console.log("[bench-test-changed] changed paths:"); for (const changedPath of changedPaths) { console.log(`- ${changedPath}`); } const routedCommand = opts.mode === "worktree" ? [process.execPath, "scripts/test-projects.mjs", ...changedPaths] : [process.execPath, "scripts/test-projects.mjs", "--changed", opts.ref]; const rootCommand = [ process.execPath, "scripts/run-vitest.mjs", "run", "--config", "vitest.config.ts", ...changedPaths, ]; console.log(`[bench-test-changed] routed: ${routedCommand.map(quoteArg).join(" ")}`); const routed = runBenchCommand({ command: routedCommand, cwd: opts.cwd, rss: opts.rss, ...(typeof opts.maxWorkers === "number" ? { maxWorkers: opts.maxWorkers } : {}), }); if (routed.status !== 0) { process.stderr.write(routed.output); process.exit(routed.status); } console.log(`[bench-test-changed] root: ${rootCommand.map(quoteArg).join(" ")}`); const root = runBenchCommand({ command: rootCommand, cwd: opts.cwd, rss: opts.rss, ...(typeof opts.maxWorkers === "number" ? { maxWorkers: opts.maxWorkers } : {}), }); if (root.status !== 0) { process.stderr.write(root.output); process.exit(root.status); } printRunSummary("routed", routed); printRunSummary("root", root); console.log( `[bench-test-changed] delta wall=${formatMs(root.elapsedMs - routed.elapsedMs)} rss=${ routed.maxRssKb !== null && root.maxRssKb !== null ? formatRss(root.maxRssKb - routed.maxRssKb) : "n/a" }`, );