test: align planner explain output with execution

This commit is contained in:
Tak Hoffman
2026-03-25 15:17:06 -05:00
parent de5e46f9b0
commit 5598f6bd83
3 changed files with 74 additions and 30 deletions

View File

@@ -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,

View File

@@ -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, "../..");

View File

@@ -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");
});
});