diff --git a/package.json b/package.json index 0fa5304382..9efd75f972 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,8 @@ "test:install:e2e:openai": "OPENCLAW_E2E_MODELS=openai CLAWDBOT_E2E_MODELS=openai bash scripts/test-install-sh-e2e-docker.sh", "test:install:smoke": "bash scripts/test-install-sh-docker.sh", "test:live": "OPENCLAW_LIVE_TEST=1 CLAWDBOT_LIVE_TEST=1 vitest run --config vitest.live.config.ts", + "test:lowcpu": "OPENCLAW_TEST_PROFILE=low NODE_OPTIONS=--max-old-space-size=8192 node scripts/test-parallel.mjs", + "test:macmini": "OPENCLAW_TEST_PROFILE=low NODE_OPTIONS=--max-old-space-size=8192 node scripts/test-parallel.mjs", "test:ui": "pnpm --dir ui test", "test:watch": "vitest", "tsgo:test": "tsgo -p tsconfig.test.json", diff --git a/scripts/test-parallel.mjs b/scripts/test-parallel.mjs index ccfaa11108..4cd1a4c0e9 100644 --- a/scripts/test-parallel.mjs +++ b/scripts/test-parallel.mjs @@ -110,6 +110,11 @@ const silentArgs = const rawPassthroughArgs = process.argv.slice(2); const passthroughArgs = rawPassthroughArgs[0] === "--" ? rawPassthroughArgs.slice(1) : rawPassthroughArgs; +const rawTestProfile = process.env.OPENCLAW_TEST_PROFILE?.trim().toLowerCase(); +const testProfile = + rawTestProfile === "low" || rawTestProfile === "max" || rawTestProfile === "normal" + ? rawTestProfile + : "normal"; const overrideWorkers = Number.parseInt(process.env.OPENCLAW_TEST_WORKERS ?? "", 10); const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null; @@ -122,10 +127,29 @@ const keepGatewaySerial = const parallelRuns = keepGatewaySerial ? runs.filter((entry) => entry.name !== "gateway") : runs; const serialRuns = keepGatewaySerial ? runs.filter((entry) => entry.name === "gateway") : []; const localWorkers = Math.max(4, Math.min(16, os.cpus().length)); -const defaultUnitWorkers = localWorkers; -// Local perf: extensions tend to be the critical path under parallel vitest runs; give them more headroom. -const defaultExtensionsWorkers = Math.max(1, Math.min(6, Math.floor(localWorkers / 2))); -const defaultGatewayWorkers = Math.max(1, Math.min(2, Math.floor(localWorkers / 4))); +const defaultWorkerBudget = + testProfile === "low" + ? { + unit: 2, + unitIsolated: 1, + extensions: 1, + gateway: 1, + } + : testProfile === "max" + ? { + unit: localWorkers, + unitIsolated: Math.min(4, localWorkers), + extensions: Math.max(1, Math.min(6, Math.floor(localWorkers / 2))), + gateway: Math.max(1, Math.min(2, Math.floor(localWorkers / 4))), + } + : { + // Local `pnpm test` runs multiple vitest groups concurrently; + // keep per-group workers conservative to avoid pegging all cores. + unit: Math.max(2, Math.min(8, Math.floor(localWorkers / 2))), + unitIsolated: 1, + extensions: Math.max(1, Math.min(4, Math.floor(localWorkers / 4))), + gateway: 1, + }; // Keep worker counts predictable for local runs; trim macOS CI workers to avoid worker crashes/OOM. // In CI on linux/windows, prefer Vitest defaults to avoid cross-test interference from lower worker counts. @@ -140,16 +164,15 @@ const maxWorkersForRun = (name) => { return 1; } if (name === "unit-isolated") { - // Local: allow a bit of parallelism while keeping this run stable. - return Math.min(4, localWorkers); + return defaultWorkerBudget.unitIsolated; } if (name === "extensions") { - return defaultExtensionsWorkers; + return defaultWorkerBudget.extensions; } if (name === "gateway") { - return defaultGatewayWorkers; + return defaultWorkerBudget.gateway; } - return defaultUnitWorkers; + return defaultWorkerBudget.unit; }; const WARNING_SUPPRESSION_FLAGS = [