From 5598f6bd83d2b4bb6e5facdebd58eeb91cd29180 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:17:06 -0500 Subject: [PATCH] test: align planner explain output with execution --- scripts/test-planner/planner.mjs | 75 ++++++++++++++++++------------ test/scripts/test-parallel.test.ts | 12 +++++ test/scripts/test-planner.test.ts | 17 +++++++ 3 files changed, 74 insertions(+), 30 deletions(-) diff --git a/scripts/test-planner/planner.mjs b/scripts/test-planner/planner.mjs index 40c40f90f93..2aa2a22f04c 100644 --- a/scripts/test-planner/planner.mjs +++ b/scripts/test-planner/planner.mjs @@ -31,9 +31,21 @@ const normalizeSurfaces = (values = []) => [ ), ]; +const EXPLICIT_PLAN_SURFACES = new Set(["unit", "extensions", "channels", "gateway"]); + +const validateExplicitSurfaces = (surfaces) => { + const invalidSurfaces = surfaces.filter((surface) => !EXPLICIT_PLAN_SURFACES.has(surface)); + if (invalidSurfaces.length > 0) { + throw new Error( + `Unsupported --surface value(s): ${invalidSurfaces.join(", ")}. Supported surfaces: unit, extensions, channels, gateway.`, + ); + } +}; + const buildRequestedSurfaces = (request, env) => { const explicit = normalizeSurfaces(request.surfaces ?? []); if (explicit.length > 0) { + validateExplicitSurfaces(explicit); return explicit; } const surfaces = []; @@ -186,52 +198,37 @@ const withIncludeFileEnv = (context, unitId, files) => ({ OPENCLAW_VITEST_INCLUDE_FILE: context.writeTempJsonArtifact(unitId, files), }); -const buildDefaultUnits = (context, request) => { - const { - env, - runtime, - executionBudget, - catalog, - unitTimingManifest, - channelTimingManifest, - unitMemoryHotspotManifest, - } = context; - const noIsolateArgs = context.noIsolateArgs; - const selectedSurfaces = buildRequestedSurfaces(request, env); - const selectedSurfaceSet = new Set(selectedSurfaces); - - const defaultHeavyUnitFileLimit = - runtime.intentProfile === "max" - ? Math.max(executionBudget.heavyUnitFileLimit, 90) - : executionBudget.heavyUnitFileLimit; - const defaultHeavyUnitLaneCount = - runtime.intentProfile === "max" - ? Math.max(executionBudget.heavyUnitLaneCount, 6) - : executionBudget.heavyUnitLaneCount; +const resolveUnitHeavyFileGroups = (context) => { + const { env, runtime, executionBudget, catalog, unitTimingManifest, unitMemoryHotspotManifest } = + context; const heavyUnitFileLimit = parseEnvNumber( env, "OPENCLAW_TEST_HEAVY_UNIT_FILE_LIMIT", - defaultHeavyUnitFileLimit, + runtime.intentProfile === "max" + ? Math.max(executionBudget.heavyUnitFileLimit, 90) + : executionBudget.heavyUnitFileLimit, ); const heavyUnitLaneCount = parseEnvNumber( env, "OPENCLAW_TEST_HEAVY_UNIT_LANES", - defaultHeavyUnitLaneCount, + runtime.intentProfile === "max" + ? Math.max(executionBudget.heavyUnitLaneCount, 6) + : executionBudget.heavyUnitLaneCount, ); const heavyUnitMinDurationMs = parseEnvNumber(env, "OPENCLAW_TEST_HEAVY_UNIT_MIN_MS", 1200); - const defaultMemoryHeavyUnitFileLimit = executionBudget.memoryHeavyUnitFileLimit; const memoryHeavyUnitFileLimit = parseEnvNumber( env, "OPENCLAW_TEST_MEMORY_HEAVY_UNIT_FILE_LIMIT", - defaultMemoryHeavyUnitFileLimit, + executionBudget.memoryHeavyUnitFileLimit, ); const memoryHeavyUnitMinDeltaKb = parseEnvNumber( env, "OPENCLAW_TEST_MEMORY_HEAVY_UNIT_MIN_KB", unitMemoryHotspotManifest.defaultMinDeltaKb, ); - const { memoryHeavyFiles: memoryHeavyUnitFiles, timedHeavyFiles: timedHeavyUnitFiles } = - selectUnitHeavyFileGroups({ + return { + heavyUnitLaneCount, + ...selectUnitHeavyFileGroups({ candidates: catalog.allKnownUnitFiles, behaviorOverrides: catalog.unitBehaviorOverrideSet, timedLimit: heavyUnitFileLimit, @@ -240,7 +237,21 @@ const buildDefaultUnits = (context, request) => { memoryMinDeltaKb: memoryHeavyUnitMinDeltaKb, timings: unitTimingManifest, hotspots: unitMemoryHotspotManifest, - }); + }), + }; +}; + +const buildDefaultUnits = (context, request) => { + const { env, executionBudget, catalog, unitTimingManifest, channelTimingManifest } = context; + const noIsolateArgs = context.noIsolateArgs; + const selectedSurfaces = buildRequestedSurfaces(request, env); + const selectedSurfaceSet = new Set(selectedSurfaces); + + const { + heavyUnitLaneCount, + memoryHeavyFiles: memoryHeavyUnitFiles, + timedHeavyFiles: timedHeavyUnitFiles, + } = resolveUnitHeavyFileGroups(context); const unitMemoryIsolatedFiles = [...memoryHeavyUnitFiles].filter( (file) => !catalog.unitBehaviorOverrideSet.has(file), ); @@ -858,7 +869,11 @@ export function explainExecutionTarget(request, options = {}) { ? ["--isolate=false"] : []; const [target] = request.fileFilters; - const classification = context.catalog.classifyTestFile(target, { unitMemoryIsolatedFiles: [] }); + const { memoryHeavyFiles } = resolveUnitHeavyFileGroups(context); + const unitMemoryIsolatedFiles = [...memoryHeavyFiles].filter( + (file) => !context.catalog.unitBehaviorOverrideSet.has(file), + ); + const classification = context.catalog.classifyTestFile(target, { unitMemoryIsolatedFiles }); const targetedUnit = createTargetedUnit(context, classification, [normalizeRepoPath(target)]); return { runtimeProfile: context.runtime.runtimeProfileName, diff --git a/test/scripts/test-parallel.test.ts b/test/scripts/test-parallel.test.ts index afda37f576e..5e0cd5c42aa 100644 --- a/test/scripts/test-parallel.test.ts +++ b/test/scripts/test-parallel.test.ts @@ -248,6 +248,18 @@ describe("scripts/test-parallel lane planning", () => { ).toThrowError(/Unsupported test profile "macmini"/u); }); + it("rejects unknown explicit surface names", () => { + const repoRoot = path.resolve(import.meta.dirname, "../.."); + + expect(() => + execFileSync("node", ["scripts/test-parallel.mjs", "--plan", "--surface", "channel"], { + cwd: repoRoot, + env: process.env, + encoding: "utf8", + }), + ).toThrowError(/Unsupported --surface value\(s\): channel/u); + }); + it("rejects wrapper --files values that look like options", () => { const repoRoot = path.resolve(import.meta.dirname, "../.."); diff --git a/test/scripts/test-planner.test.ts b/test/scripts/test-planner.test.ts index 2501968f346..278c3a71c79 100644 --- a/test/scripts/test-planner.test.ts +++ b/test/scripts/test-planner.test.ts @@ -110,4 +110,21 @@ describe("test planner", () => { expect(explanation.reasons).toContain("base-pinned-manifest"); expect(explanation.intentProfile).toBe("normal"); }); + + it("uses hotspot-backed memory isolation when explaining unit tests", () => { + const explanation = explainExecutionTarget( + { + mode: "local", + fileFilters: ["src/infra/outbound/targets.channel-resolution.test.ts"], + }, + { + env: { + OPENCLAW_TEST_LOAD_AWARE: "0", + }, + }, + ); + + expect(explanation.isolate).toBe(true); + expect(explanation.reasons).toContain("unit-memory-isolated"); + }); });