From 8fd2fa13c6f4270a1c9596f7efb7559ba937ddd5 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 23 Mar 2026 11:50:48 +0000 Subject: [PATCH] test: avoid repo-root perf profile artifacts --- package.json | 4 +- scripts/run-vitest-profile.mjs | 112 ++++++++++++++++++++++++ test/scripts/run-vitest-profile.test.ts | 77 ++++++++++++++++ 3 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 scripts/run-vitest-profile.mjs create mode 100644 test/scripts/run-vitest-profile.test.ts diff --git a/package.json b/package.json index c94fd15581f..290dd49962d 100644 --- a/package.json +++ b/package.json @@ -731,8 +731,8 @@ "test:perf:hotspots": "node scripts/test-hotspots.mjs", "test:perf:imports": "OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 pnpm test", "test:perf:imports:changed": "OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 pnpm test -- --changed origin/main", - "test:perf:profile:main": "node --cpu-prof --cpu-prof-dir=.artifacts/vitest-main-profile ./node_modules/vitest/vitest.mjs run --config vitest.unit.config.ts --no-file-parallelism", - "test:perf:profile:runner": "vitest run --config vitest.unit.config.ts --no-file-parallelism --execArgv=--cpu-prof --execArgv=--cpu-prof-dir=.artifacts/vitest-runner-profile --execArgv=--heap-prof --execArgv=--heap-prof-dir=.artifacts/vitest-runner-profile", + "test:perf:profile:main": "node scripts/run-vitest-profile.mjs main", + "test:perf:profile:runner": "node scripts/run-vitest-profile.mjs runner", "test:perf:update-memory-hotspots": "node scripts/test-update-memory-hotspots.mjs", "test:perf:update-timings": "node scripts/test-update-timings.mjs", "test:sectriage": "pnpm exec vitest run --config vitest.gateway.config.ts && vitest run --config vitest.unit.config.ts --exclude src/daemon/launchd.integration.test.ts --exclude src/process/exec.test.ts", diff --git a/scripts/run-vitest-profile.mjs b/scripts/run-vitest-profile.mjs new file mode 100644 index 00000000000..007a27782d0 --- /dev/null +++ b/scripts/run-vitest-profile.mjs @@ -0,0 +1,112 @@ +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; + +export function parseArgs(argv) { + const args = { + mode: "", + outputDir: process.env.OPENCLAW_VITEST_PROFILE_DIR?.trim() || "", + }; + + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + if (arg === "--output-dir") { + args.outputDir = argv[i + 1] ?? ""; + i += 1; + continue; + } + if (!args.mode) { + args.mode = arg; + continue; + } + throw new Error(`Unknown argument: ${arg}`); + } + + if (args.mode !== "main" && args.mode !== "runner") { + throw new Error( + "Usage: node scripts/run-vitest-profile.mjs [--output-dir ]", + ); + } + + return args; +} + +export function resolveVitestProfileDir({ mode, outputDir }) { + if (outputDir && outputDir.trim()) { + return path.resolve(outputDir); + } + + return fs.mkdtempSync(path.join(os.tmpdir(), `openclaw-vitest-${mode}-profile-`)); +} + +export function buildVitestProfileCommand({ mode, outputDir }) { + if (mode === "main") { + return { + command: process.execPath, + args: [ + "--cpu-prof", + `--cpu-prof-dir=${outputDir}`, + "./node_modules/vitest/vitest.mjs", + "run", + "--config", + "vitest.unit.config.ts", + "--no-file-parallelism", + ], + }; + } + + return { + command: "pnpm", + args: [ + "vitest", + "run", + "--config", + "vitest.unit.config.ts", + "--no-file-parallelism", + "--execArgv=--cpu-prof", + `--execArgv=--cpu-prof-dir=${outputDir}`, + "--execArgv=--heap-prof", + `--execArgv=--heap-prof-dir=${outputDir}`, + ], + }; +} + +function main() { + const parsed = parseArgs(process.argv.slice(2)); + const outputDir = resolveVitestProfileDir(parsed); + fs.mkdirSync(outputDir, { recursive: true }); + + const plan = buildVitestProfileCommand({ + mode: parsed.mode, + outputDir, + }); + + console.log(`[run-vitest-profile] writing ${parsed.mode} profiles to ${outputDir}`); + + const result = spawnSync(plan.command, plan.args, { + stdio: "inherit", + shell: process.platform === "win32" && plan.command === "pnpm", + env: process.env, + }); + + if (result.error) { + throw result.error; + } + process.exit(result.status ?? 1); +} + +const isMain = + typeof process.argv[1] === "string" && + process.argv[1].length > 0 && + import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href; + +if (isMain) { + try { + main(); + } catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} diff --git a/test/scripts/run-vitest-profile.test.ts b/test/scripts/run-vitest-profile.test.ts new file mode 100644 index 00000000000..8d9b31ea71c --- /dev/null +++ b/test/scripts/run-vitest-profile.test.ts @@ -0,0 +1,77 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + buildVitestProfileCommand, + parseArgs, + resolveVitestProfileDir, +} from "../../scripts/run-vitest-profile.mjs"; + +describe("scripts/run-vitest-profile", () => { + const tempDirs: string[] = []; + + afterEach(() => { + while (tempDirs.length > 0) { + const dir = tempDirs.pop(); + if (dir) { + fs.rmSync(dir, { recursive: true, force: true }); + } + } + }); + + it("defaults profile output outside the repo", () => { + const outputDir = resolveVitestProfileDir({ mode: "main", outputDir: "" }); + tempDirs.push(outputDir); + + expect(outputDir.startsWith(os.tmpdir())).toBe(true); + expect(outputDir.startsWith(process.cwd())).toBe(false); + }); + + it("keeps explicit output directories", () => { + expect( + resolveVitestProfileDir({ mode: "runner", outputDir: ".artifacts/custom-profile" }), + ).toBe(path.resolve(".artifacts/custom-profile")); + }); + + it("builds main-thread cpu profiling args", () => { + expect(buildVitestProfileCommand({ mode: "main", outputDir: "/tmp/profile-main" })).toEqual({ + command: process.execPath, + args: [ + "--cpu-prof", + "--cpu-prof-dir=/tmp/profile-main", + "./node_modules/vitest/vitest.mjs", + "run", + "--config", + "vitest.unit.config.ts", + "--no-file-parallelism", + ], + }); + }); + + it("builds runner cpu and heap profiling args", () => { + expect(buildVitestProfileCommand({ mode: "runner", outputDir: "/tmp/profile-runner" })).toEqual( + { + command: "pnpm", + args: [ + "vitest", + "run", + "--config", + "vitest.unit.config.ts", + "--no-file-parallelism", + "--execArgv=--cpu-prof", + "--execArgv=--cpu-prof-dir=/tmp/profile-runner", + "--execArgv=--heap-prof", + "--execArgv=--heap-prof-dir=/tmp/profile-runner", + ], + }, + ); + }); + + it("parses mode and explicit output dir", () => { + expect(parseArgs(["runner", "--output-dir", "/tmp/out"])).toEqual({ + mode: "runner", + outputDir: "/tmp/out", + }); + }); +});