From 5b294b7fbd63221497afacf17e8ff6be7aa5dcda Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 4 Apr 2026 05:20:07 +0100 Subject: [PATCH] test: keep vitest thread workers conservative --- src/infra/vitest-config.test.ts | 26 ++++++++++++++++++++++---- test/vitest-scoped-config.test.ts | 16 ++++++++++++---- vitest.shared.config.ts | 18 ++++++++++++++++-- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/infra/vitest-config.test.ts b/src/infra/vitest-config.test.ts index 500f9afdd38..bbd373a210f 100644 --- a/src/infra/vitest-config.test.ts +++ b/src/infra/vitest-config.test.ts @@ -17,8 +17,9 @@ describe("resolveLocalVitestMaxWorkers", () => { loadAverage1m: 0, totalMemoryBytes: 64 * 1024 ** 3, }, + "threads", ), - ).toBe(3); + ).toBe(2); }); it("lets OPENCLAW_VITEST_MAX_WORKERS override the inferred cap", () => { @@ -60,8 +61,9 @@ describe("resolveLocalVitestMaxWorkers", () => { loadAverage1m: 0, totalMemoryBytes: 16 * 1024 ** 3, }, + "threads", ), - ).toBe(2); + ).toBe(1); }); it("lets roomy hosts use more local parallelism", () => { @@ -73,8 +75,9 @@ describe("resolveLocalVitestMaxWorkers", () => { loadAverage1m: 0, totalMemoryBytes: 128 * 1024 ** 3, }, + "threads", ), - ).toBe(4); + ).toBe(3); }); it("backs off further when the host is already busy", () => { @@ -86,8 +89,23 @@ describe("resolveLocalVitestMaxWorkers", () => { loadAverage1m: 16, totalMemoryBytes: 128 * 1024 ** 3, }, + "threads", ), - ).toBe(2); + ).toBe(1); + }); + + it("keeps fork pools less conservative than thread pools on roomy hosts", () => { + expect( + resolveLocalVitestMaxWorkers( + {}, + { + cpuCount: 16, + loadAverage1m: 0, + totalMemoryBytes: 128 * 1024 ** 3, + }, + "forks", + ), + ).toBe(4); }); }); diff --git a/test/vitest-scoped-config.test.ts b/test/vitest-scoped-config.test.ts index a4706dac02d..94bc8c9444e 100644 --- a/test/vitest-scoped-config.test.ts +++ b/test/vitest-scoped-config.test.ts @@ -65,7 +65,11 @@ describe("createScopedVitestConfig", () => { setupFiles: ["test/setup.extensions.ts"], }); - expect(config.test?.setupFiles).toEqual(["test/setup.extensions.ts"]); + expect(config.test?.setupFiles).toEqual([ + "test/setup.ts", + "test/setup.extensions.ts", + "test/setup-openclaw-runtime.ts", + ]); }); }); @@ -85,7 +89,7 @@ describe("scoped vitest configs", () => { it("defaults channel tests to non-isolated mode", () => { expect(defaultChannelsConfig.test?.isolate).toBe(false); - expect(defaultChannelsConfig.test?.pool).toBe("forks"); + expect(defaultChannelsConfig.test?.pool).toBe("threads"); }); it("keeps the core channel lane limited to non-extension roots", () => { @@ -121,7 +125,7 @@ describe("scoped vitest configs", () => { it("defaults extension tests to non-isolated mode", () => { expect(defaultExtensionsConfig.test?.isolate).toBe(false); - expect(defaultExtensionsConfig.test?.pool).toBe("forks"); + expect(defaultExtensionsConfig.test?.pool).toBe("threads"); }); it("normalizes extension channel include patterns relative to the scoped dir", () => { @@ -165,7 +169,11 @@ describe("scoped vitest configs", () => { expect(defaultChannelsConfig.test?.exclude).not.toContain( bundledPluginFile("telegram", "src/fetch.test.ts"), ); - expect(defaultExtensionsConfig.test?.setupFiles).toEqual(["test/setup.extensions.ts"]); + expect(defaultExtensionsConfig.test?.setupFiles).toEqual([ + "test/setup.ts", + "test/setup.extensions.ts", + "test/setup-openclaw-runtime.ts", + ]); }); it("keeps provider plugin tests out of the shared extensions lane", () => { diff --git a/vitest.shared.config.ts b/vitest.shared.config.ts index 1f782386cbb..34ec87d641d 100644 --- a/vitest.shared.config.ts +++ b/vitest.shared.config.ts @@ -44,6 +44,7 @@ function detectVitestHostInfo(): Required { export function resolveLocalVitestMaxWorkers( env: Record = process.env, system: VitestHostInfo = detectVitestHostInfo(), + pool: OpenClawVitestPool = resolveDefaultVitestPool(env), ): number { const override = parsePositiveInt(env.OPENCLAW_VITEST_MAX_WORKERS ?? env.OPENCLAW_TEST_WORKERS); if (override !== null) { @@ -76,6 +77,19 @@ export function resolveLocalVitestMaxWorkers( inferred = Math.max(1, inferred - 1); } + if (pool === "threads") { + // Thread workers are faster per slot on the steady state, but their startup + // compile pressure is much burstier. Keep headroom so a second local Vitest + // run can start without immediately saturating the host. + inferred = Math.min(inferred, 4); + if (cpuCount >= 8) { + inferred = Math.max(1, inferred - 1); + } + if (loadRatio >= 0.5) { + inferred = Math.max(1, inferred - 1); + } + } + return clamp(inferred, 1, 16); } @@ -92,9 +106,9 @@ export function resolveDefaultVitestPool( const repoRoot = path.dirname(fileURLToPath(import.meta.url)); const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true"; const isWindows = process.platform === "win32"; -const localWorkers = resolveLocalVitestMaxWorkers(); -const ciWorkers = isWindows ? 2 : 3; const defaultPool = resolveDefaultVitestPool(); +const localWorkers = resolveLocalVitestMaxWorkers(process.env, detectVitestHostInfo(), defaultPool); +const ciWorkers = isWindows ? 2 : 3; export const sharedVitestConfig = { resolve: {