From d689b3fc8935ae4b3170632dc11746ba9e1d4cb3 Mon Sep 17 00:00:00 2001 From: Shakker Date: Fri, 20 Mar 2026 04:43:09 +0000 Subject: [PATCH] fix(ci): prioritize memory-heavy unit scheduling --- scripts/test-parallel.mjs | 39 +++++++++-------------- scripts/test-runner-manifest.mjs | 38 ++++++++++++++++++++++ test/scripts/test-runner-manifest.test.ts | 30 +++++++++++++++++ 3 files changed, 83 insertions(+), 24 deletions(-) diff --git a/scripts/test-parallel.mjs b/scripts/test-parallel.mjs index c2245daadaa..78b2ad44f67 100644 --- a/scripts/test-parallel.mjs +++ b/scripts/test-parallel.mjs @@ -18,9 +18,8 @@ import { loadUnitMemoryHotspotManifest, loadTestRunnerBehavior, loadUnitTimingManifest, - selectMemoryHeavyFiles, + selectUnitHeavyFileGroups, packFilesByDuration, - selectTimedHeavyFiles, } from "./test-runner-manifest.mjs"; // On Windows, `.cmd` launchers can fail with `spawn EINVAL` when invoked without a shell @@ -311,33 +310,25 @@ const memoryHeavyUnitMinDeltaKb = parseEnvNumber( "OPENCLAW_TEST_MEMORY_HEAVY_UNIT_MIN_KB", unitMemoryHotspotManifest.defaultMinDeltaKb, ); -const timedHeavyUnitFiles = - shouldSplitUnitRuns && heavyUnitFileLimit > 0 - ? selectTimedHeavyFiles({ +const { memoryHeavyFiles: memoryHeavyUnitFiles, timedHeavyFiles: timedHeavyUnitFiles } = + shouldSplitUnitRuns + ? selectUnitHeavyFileGroups({ candidates: allKnownUnitFiles, - limit: heavyUnitFileLimit, - minDurationMs: heavyUnitMinDurationMs, - exclude: unitBehaviorOverrideSet, + behaviorOverrides: unitBehaviorOverrideSet, + timedLimit: heavyUnitFileLimit, + timedMinDurationMs: heavyUnitMinDurationMs, + memoryLimit: memoryHeavyUnitFileLimit, + memoryMinDeltaKb: memoryHeavyUnitMinDeltaKb, timings: unitTimingManifest, - }) - : []; -const memoryHeavyUnitFiles = - shouldSplitUnitRuns && memoryHeavyUnitFileLimit > 0 - ? selectMemoryHeavyFiles({ - candidates: allKnownUnitFiles, - limit: memoryHeavyUnitFileLimit, - minDeltaKb: memoryHeavyUnitMinDeltaKb, - exclude: unitBehaviorOverrideSet, hotspots: unitMemoryHotspotManifest, }) - : []; + : { + memoryHeavyFiles: [], + timedHeavyFiles: [], + }; +const unitSchedulingOverrideSet = new Set([...unitBehaviorOverrideSet, ...memoryHeavyUnitFiles]); const unitFastExcludedFiles = [ - ...new Set([ - ...unitBehaviorOverrideSet, - ...timedHeavyUnitFiles, - ...memoryHeavyUnitFiles, - ...channelSingletonFiles, - ]), + ...new Set([...unitSchedulingOverrideSet, ...timedHeavyUnitFiles, ...channelSingletonFiles]), ]; const unitAutoSingletonFiles = [ ...new Set([...unitSingletonIsolatedFiles, ...memoryHeavyUnitFiles]), diff --git a/scripts/test-runner-manifest.mjs b/scripts/test-runner-manifest.mjs index b795d0c5bd2..4e0ff9d0a5a 100644 --- a/scripts/test-runner-manifest.mjs +++ b/scripts/test-runner-manifest.mjs @@ -168,6 +168,44 @@ export function selectMemoryHeavyFiles({ .map((entry) => entry.file); } +export function selectUnitHeavyFileGroups({ + candidates, + behaviorOverrides = new Set(), + timedLimit, + timedMinDurationMs, + memoryLimit, + memoryMinDeltaKb, + timings, + hotspots, +}) { + const memoryHeavyFiles = + memoryLimit > 0 + ? selectMemoryHeavyFiles({ + candidates, + limit: memoryLimit, + minDeltaKb: memoryMinDeltaKb, + exclude: behaviorOverrides, + hotspots, + }) + : []; + const schedulingOverrides = new Set([...behaviorOverrides, ...memoryHeavyFiles]); + const timedHeavyFiles = + timedLimit > 0 + ? selectTimedHeavyFiles({ + candidates, + limit: timedLimit, + minDurationMs: timedMinDurationMs, + exclude: schedulingOverrides, + timings, + }) + : []; + + return { + memoryHeavyFiles, + timedHeavyFiles, + }; +} + export function packFilesByDuration(files, bucketCount, estimateDurationMs) { const normalizedBucketCount = Math.max(0, Math.floor(bucketCount)); if (normalizedBucketCount <= 0 || files.length === 0) { diff --git a/test/scripts/test-runner-manifest.test.ts b/test/scripts/test-runner-manifest.test.ts index fdfe2e576c7..cd650ae2aad 100644 --- a/test/scripts/test-runner-manifest.test.ts +++ b/test/scripts/test-runner-manifest.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { selectMemoryHeavyFiles, selectTimedHeavyFiles, + selectUnitHeavyFileGroups, } from "../../scripts/test-runner-manifest.mjs"; describe("scripts/test-runner-manifest timed selection", () => { @@ -60,4 +61,33 @@ describe("scripts/test-runner-manifest memory selection", () => { }), ).toEqual(["b.test.ts", "c.test.ts"]); }); + + it("gives memory-heavy isolation precedence over timed-heavy buckets", () => { + expect( + selectUnitHeavyFileGroups({ + candidates: ["overlap.test.ts", "memory-only.test.ts", "timed-only.test.ts"], + behaviorOverrides: new Set(), + timedLimit: 3, + timedMinDurationMs: 1000, + memoryLimit: 3, + memoryMinDeltaKb: 256 * 1024, + timings: { + defaultDurationMs: 250, + files: { + "overlap.test.ts": { durationMs: 5000 }, + "timed-only.test.ts": { durationMs: 4200 }, + }, + }, + hotspots: { + files: { + "overlap.test.ts": { deltaKb: 900 * 1024 }, + "memory-only.test.ts": { deltaKb: 700 * 1024 }, + }, + }, + }), + ).toEqual({ + memoryHeavyFiles: ["overlap.test.ts", "memory-only.test.ts"], + timedHeavyFiles: ["timed-only.test.ts"], + }); + }); });