diff --git a/src/infra/vitest-config.test.ts b/src/infra/vitest-config.test.ts index 96c93fc4ede..883144316fc 100644 --- a/src/infra/vitest-config.test.ts +++ b/src/infra/vitest-config.test.ts @@ -28,7 +28,7 @@ describe("resolveLocalVitestMaxWorkers", () => { "threads", idleVitestStats, ), - ).toBe(2); + ).toBe(6); }); it("lets OPENCLAW_VITEST_MAX_WORKERS override the inferred cap", () => { @@ -77,7 +77,7 @@ describe("resolveLocalVitestMaxWorkers", () => { "threads", idleVitestStats, ), - ).toBe(1); + ).toBe(2); }); it("lets roomy hosts use more local parallelism", () => { @@ -92,7 +92,7 @@ describe("resolveLocalVitestMaxWorkers", () => { "threads", idleVitestStats, ), - ).toBe(3); + ).toBe(8); }); it("backs off further when the host is already busy", () => { @@ -122,7 +122,7 @@ describe("resolveLocalVitestMaxWorkers", () => { "threads", idleVitestStats, ), - ).toBe(3); + ).toBe(12); }); }); @@ -188,7 +188,7 @@ describe("resolveLocalVitestScheduling", () => { idleVitestStats, ), ).toEqual({ - maxWorkers: 3, + maxWorkers: 8, fileParallelism: true, throttledBySystem: false, }); diff --git a/test/vitest-boundary-config.test.ts b/test/vitest-boundary-config.test.ts index fe3f8c54dcb..cd3356548e4 100644 --- a/test/vitest-boundary-config.test.ts +++ b/test/vitest-boundary-config.test.ts @@ -12,7 +12,7 @@ describe("loadBoundaryIncludePatternsFromEnv", () => { }); describe("boundary vitest config", () => { - it("keeps boundary suites on the shared runner with shared test bootstrap", () => { + it("keeps boundary suites on the non-isolated runner with shared test bootstrap", () => { const config = createBoundaryVitestConfig({}); expect(config.test?.isolate).toBe(false); diff --git a/test/vitest-projects-config.test.ts b/test/vitest-projects-config.test.ts index 3211bcca640..f0e59b3c5fa 100644 --- a/test/vitest-projects-config.test.ts +++ b/test/vitest-projects-config.test.ts @@ -13,14 +13,14 @@ describe("projects vitest config", () => { expect(baseConfig.test?.projects).toEqual([...rootVitestProjects]); }); - it("keeps the heavy root projects on fork workers only where explicitly required", () => { - expect(createGatewayVitestConfig().test.pool).toBe("forks"); - expect(createAgentsVitestConfig().test.pool).toBe("forks"); - expect(createCommandsVitestConfig().test.pool).toBe("forks"); + it("keeps root projects on the shared thread-first pool by default", () => { + expect(createGatewayVitestConfig().test.pool).toBe("threads"); + expect(createAgentsVitestConfig().test.pool).toBe("threads"); + expect(createCommandsVitestConfig().test.pool).toBe("threads"); expect(createContractsVitestConfig().test.pool).toBe("threads"); }); - it("keeps the contracts lane on the shared non-isolated runner", () => { + it("keeps the contracts lane on the non-isolated runner by default", () => { const config = createContractsVitestConfig(); expect(config.test.isolate).toBe(false); expect(config.test.runner).toBe("./test/non-isolated-runner.ts"); @@ -36,13 +36,13 @@ describe("projects vitest config", () => { expect(config.test.deps?.optimizer?.web?.enabled).toBe(true); }); - it("keeps the unit lane on the shared non-isolated runner", () => { + it("keeps the unit lane on the non-isolated runner by default", () => { const config = createUnitVitestConfig(); expect(config.test.isolate).toBe(false); expect(config.test.runner).toBe("./test/non-isolated-runner.ts"); }); - it("keeps the bundled lane on the shared non-isolated runner", () => { + it("keeps the bundled lane on thread workers with the non-isolated runner", () => { expect(bundledConfig.test?.pool).toBe("threads"); expect(bundledConfig.test?.isolate).toBe(false); expect(bundledConfig.test?.runner).toBe("./test/non-isolated-runner.ts"); diff --git a/test/vitest-scoped-config.test.ts b/test/vitest-scoped-config.test.ts index 8422b5c417d..23e4d8caabd 100644 --- a/test/vitest-scoped-config.test.ts +++ b/test/vitest-scoped-config.test.ts @@ -51,7 +51,7 @@ import { BUNDLED_PLUGIN_TEST_GLOB, bundledPluginFile } from "./helpers/bundled-p const EXTENSIONS_CHANNEL_GLOB = ["extensions", "channel", "**"].join("/"); describe("resolveVitestIsolation", () => { - it("defaults shared scoped configs to non-isolated workers", () => { + it("defaults shared scoped configs to the non-isolated runner", () => { expect(resolveVitestIsolation({})).toBe(false); }); @@ -63,7 +63,7 @@ describe("resolveVitestIsolation", () => { }); describe("createScopedVitestConfig", () => { - it("applies non-isolated mode by default", () => { + it("applies the non-isolated runner by default", () => { const config = createScopedVitestConfig(["src/example.test.ts"], { env: {} }); expect(config.test?.isolate).toBe(false); expect(config.test?.runner).toBe("./test/non-isolated-runner.ts"); @@ -160,7 +160,7 @@ describe("scoped vitest configs", () => { const defaultUtilsConfig = createUtilsVitestConfig({}); const defaultWizardConfig = createWizardVitestConfig({}); - it("keeps most scoped lanes on thread workers with the non-isolated runner", () => { + it("keeps scoped lanes on threads with the shared non-isolated runner", () => { for (const config of [ defaultChannelsConfig, defaultAcpConfig, @@ -176,19 +176,18 @@ describe("scoped vitest configs", () => { expect(config.test?.isolate).toBe(false); expect(config.test?.runner).toBe("./test/non-isolated-runner.ts"); } - }); - it("keeps gateway, commands, and agents on fork workers", () => { for (const config of [defaultGatewayConfig, defaultCommandsConfig, defaultAgentsConfig]) { - expect(config.test?.pool).toBe("forks"); + expect(config.test?.pool).toBe("threads"); expect(config.test?.isolate).toBe(false); expect(config.test?.runner).toBe("./test/non-isolated-runner.ts"); } }); - it("defaults channel tests to non-isolated thread mode", () => { + it("defaults channel tests to threads with the non-isolated runner", () => { expect(defaultChannelsConfig.test?.isolate).toBe(false); expect(defaultChannelsConfig.test?.pool).toBe("threads"); + expect(defaultChannelsConfig.test?.runner).toBe("./test/non-isolated-runner.ts"); }); it("keeps the core channel lane limited to non-extension roots", () => { @@ -222,9 +221,10 @@ describe("scoped vitest configs", () => { } }); - it("defaults extension tests to non-isolated thread mode", () => { + it("defaults extension tests to threads with the non-isolated runner", () => { expect(defaultExtensionsConfig.test?.isolate).toBe(false); expect(defaultExtensionsConfig.test?.pool).toBe("threads"); + expect(defaultExtensionsConfig.test?.runner).toBe("./test/non-isolated-runner.ts"); }); it("normalizes extension channel include patterns relative to the scoped dir", () => { diff --git a/test/vitest-ui-package-config.test.ts b/test/vitest-ui-package-config.test.ts index a26d94049b6..a5d07923ad9 100644 --- a/test/vitest-ui-package-config.test.ts +++ b/test/vitest-ui-package-config.test.ts @@ -3,20 +3,20 @@ import uiConfig from "../ui/vitest.config.ts"; import uiNodeConfig from "../ui/vitest.node.config.ts"; describe("ui package vitest config", () => { - it("keeps the standalone ui package on forks with isolation enabled", () => { - expect(uiConfig.test?.pool).toBe("forks"); + it("keeps the standalone ui package on thread workers with isolation enabled", () => { + expect(uiConfig.test?.pool).toBe("threads"); expect(uiConfig.test?.isolate).toBe(true); expect(uiConfig.test?.projects).toHaveLength(3); for (const project of uiConfig.test?.projects ?? []) { - expect(project.test?.pool).toBe("forks"); + expect(project.test?.pool).toBe("threads"); expect(project.test?.isolate).toBe(true); expect(project.test?.runner).toBeUndefined(); } }); - it("keeps the standalone ui node config on forks with isolation enabled", () => { - expect(uiNodeConfig.test?.pool).toBe("forks"); + it("keeps the standalone ui node config on thread workers with isolation enabled", () => { + expect(uiNodeConfig.test?.pool).toBe("threads"); expect(uiNodeConfig.test?.isolate).toBe(true); expect(uiNodeConfig.test?.runner).toBeUndefined(); }); diff --git a/test/vitest-unit-config.test.ts b/test/vitest-unit-config.test.ts index 15ddb3ac8b6..ae857535e7c 100644 --- a/test/vitest-unit-config.test.ts +++ b/test/vitest-unit-config.test.ts @@ -68,7 +68,7 @@ describe("loadExtraExcludePatternsFromEnv", () => { }); describe("unit vitest config", () => { - it("defaults unit tests to non-isolated mode", () => { + it("defaults unit tests to the non-isolated runner", () => { const unitConfig = createUnitVitestConfig({}); expect(unitConfig.test?.isolate).toBe(false); expect(unitConfig.test?.runner).toBe("./test/non-isolated-runner.ts"); diff --git a/vitest.boundary.config.ts b/vitest.boundary.config.ts index f3afb4829b4..f6333428d32 100644 --- a/vitest.boundary.config.ts +++ b/vitest.boundary.config.ts @@ -1,5 +1,6 @@ import { defineProject } from "vitest/config"; import { loadPatternListFromEnv, narrowIncludePatternsForCli } from "./vitest.pattern-file.ts"; +import { resolveVitestIsolation } from "./vitest.scoped-config.ts"; import { sharedVitestConfig } from "./vitest.shared.config.ts"; import { boundaryTestFiles } from "./vitest.unit-paths.mjs"; @@ -14,13 +15,14 @@ export function createBoundaryVitestConfig( argv: string[] = process.argv, ) { const cliIncludePatterns = narrowIncludePatternsForCli(boundaryTestFiles, argv); + const isolate = resolveVitestIsolation(env); return defineProject({ ...sharedVitestConfig, test: { ...sharedVitestConfig.test, name: "boundary", - isolate: false, - runner: "./test/non-isolated-runner.ts", + isolate, + ...(isolate ? { runner: undefined } : { runner: "./test/non-isolated-runner.ts" }), include: loadBoundaryIncludePatternsFromEnv(env) ?? cliIncludePatterns ?? boundaryTestFiles, ...(cliIncludePatterns !== null ? { passWithNoTests: true } : {}), // Boundary workers still need the shared isolated HOME/bootstrap. Only diff --git a/vitest.config.ts b/vitest.config.ts index cec3e1c715a..6cf0715aecc 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -62,6 +62,7 @@ export default defineConfig({ ...sharedVitestConfig, test: { ...sharedVitestConfig.test, + runner: "./test/non-isolated-runner.ts", projects: [...rootVitestProjects], }, }); diff --git a/vitest.contracts.config.ts b/vitest.contracts.config.ts index 5c867af1b67..a48cca398bc 100644 --- a/vitest.contracts.config.ts +++ b/vitest.contracts.config.ts @@ -9,6 +9,8 @@ export function createContractsVitestConfig() { ...base, test: { ...baseTest, + isolate: false, + runner: "./test/non-isolated-runner.ts", setupFiles: baseTest.setupFiles ?? [], include: [ "src/channels/plugins/contracts/**/*.test.ts", diff --git a/vitest.scoped-config.ts b/vitest.scoped-config.ts index 3d099a63651..85253cef2dd 100644 --- a/vitest.scoped-config.ts +++ b/vitest.scoped-config.ts @@ -69,6 +69,7 @@ export function createScopedVitestConfig( ]), ]; const useNonIsolatedRunner = options?.useNonIsolatedRunner ?? !isolate; + const runner = useNonIsolatedRunner ? "./test/non-isolated-runner.ts" : undefined; return defineConfig({ ...base, @@ -78,7 +79,7 @@ export function createScopedVitestConfig( ...(options?.name ? { name: options.name } : {}), ...(options?.environment ? { environment: options.environment } : {}), isolate, - ...(useNonIsolatedRunner ? { runner: "./test/non-isolated-runner.ts" } : {}), + ...(runner ? { runner } : { runner: undefined }), setupFiles, ...(scopedDir ? { dir: scopedDir } : {}), include: relativizeScopedPatterns(cliInclude ?? include, scopedDir), diff --git a/vitest.shared.config.ts b/vitest.shared.config.ts index 63b370095eb..d387b9bb2f9 100644 --- a/vitest.shared.config.ts +++ b/vitest.shared.config.ts @@ -86,36 +86,42 @@ export function resolveLocalVitestScheduling( const loadAverage1m = Math.max(0, system.loadAverage1m ?? 0); const totalMemoryGb = (system.totalMemoryBytes ?? 0) / 1024 ** 3; + // Keep smaller hosts conservative, but let large local boxes actually use + // their cores. Thread workers scale much better than the old fork-first cap. let inferred = - cpuCount <= 4 ? 1 : cpuCount <= 8 ? 2 : cpuCount <= 12 ? 3 : cpuCount <= 16 ? 4 : 6; + cpuCount <= 2 + ? 1 + : cpuCount <= 4 + ? 2 + : cpuCount <= 8 + ? 4 + : Math.max(1, Math.floor(cpuCount * 0.75)); if (totalMemoryGb <= 16) { inferred = Math.min(inferred, 2); } else if (totalMemoryGb <= 32) { - inferred = Math.min(inferred, 3); - } else if (totalMemoryGb <= 64) { inferred = Math.min(inferred, 4); - } else if (totalMemoryGb <= 128) { - inferred = Math.min(inferred, 5); - } else { + } else if (totalMemoryGb <= 64) { inferred = Math.min(inferred, 6); + } else if (totalMemoryGb <= 128) { + inferred = Math.min(inferred, 8); + } else if (totalMemoryGb <= 256) { + inferred = Math.min(inferred, 12); + } else { + inferred = Math.min(inferred, 16); } const loadRatio = loadAverage1m > 0 ? loadAverage1m / cpuCount : 0; if (loadRatio >= 1) { inferred = Math.max(1, Math.floor(inferred / 2)); } else if (loadRatio >= 0.75) { + inferred = Math.max(1, inferred - 2); + } else if (loadRatio >= 0.5) { inferred = Math.max(1, inferred - 1); } - if (pool === "threads") { - inferred = Math.min(inferred, 4); - if (cpuCount >= 8) { - inferred = Math.max(1, inferred - 1); - } - if (loadRatio >= 0.5) { - inferred = Math.max(1, inferred - 1); - } + if (pool === "forks") { + inferred = Math.min(inferred, 8); } inferred = clamp(inferred, 1, 16); diff --git a/vitest.unit.config.ts b/vitest.unit.config.ts index 1ab118f8615..26ea01fbc92 100644 --- a/vitest.unit.config.ts +++ b/vitest.unit.config.ts @@ -40,7 +40,7 @@ export function createUnitVitestConfigWithOptions( ...sharedTest, name: options.name ?? "unit", isolate, - ...(isolate ? {} : { runner: "./test/non-isolated-runner.ts" }), + ...(isolate ? { runner: undefined } : { runner: "./test/non-isolated-runner.ts" }), setupFiles: [ ...new Set([...(sharedTest.setupFiles ?? []), "test/setup-openclaw-runtime.ts"]), ],