diff --git a/test/vitest-projects-config.test.ts b/test/vitest-projects-config.test.ts index 52e54374f314..3621c48613ad 100644 --- a/test/vitest-projects-config.test.ts +++ b/test/vitest-projects-config.test.ts @@ -40,6 +40,7 @@ import { import { fullSuiteVitestShards } from "./vitest/vitest.test-shards.mjs"; import { createUiVitestConfig } from "./vitest/vitest.ui.config.ts"; import { createUnitFastFakeTimersVitestConfig } from "./vitest/vitest.unit-fast-fake-timers.config.ts"; +import { createUnitFastIsolatedVitestConfig } from "./vitest/vitest.unit-fast-isolated.config.ts"; import unitFastRootConfig from "./vitest/vitest.unit-fast-root.config.ts"; import { createUnitFastVitestConfig } from "./vitest/vitest.unit-fast.config.ts"; @@ -121,6 +122,35 @@ describe("projects vitest config", () => { ).toEqual([]); }); + it.each([ + ["ordinary", createUnitFastVitestConfig, "src/plugin-sdk/provider-entry.test.ts"], + [ + "isolated", + createUnitFastIsolatedVitestConfig, + "src/system-agent/assistant.configured.test.ts", + ], + ["fake timers", createUnitFastFakeTimersVitestConfig, "src/acp/control-plane/manager.test.ts"], + ])("limits %s unit-fast include files to the project's owned tests", (_, createConfig, owned) => { + const unrelated = "src/gateway/openresponses-http.test.ts"; + const mixedIncludeFile = patternFiles.writePatternFile("mixed-unit-fast-include.json", [ + "src/plugin-sdk/provider-entry.test.ts", + "src/system-agent/assistant.configured.test.ts", + "src/acp/control-plane/manager.test.ts", + unrelated, + ]); + const unrelatedIncludeFile = patternFiles.writePatternFile("unrelated-unit-fast-include.json", [ + unrelated, + ]); + + expect( + requireTestConfig(createConfig({ OPENCLAW_VITEST_INCLUDE_FILE: mixedIncludeFile })).include, + ).toEqual([owned]); + expect( + requireTestConfig(createConfig({ OPENCLAW_VITEST_INCLUDE_FILE: unrelatedIncludeFile })) + .include, + ).toEqual([]); + }); + it("covers each normal full-suite test file exactly once after configs cached filtered includes", async () => { const contractTestConfigs = [ contractChannelSurfaceConfig, diff --git a/test/vitest/vitest.pattern-file.ts b/test/vitest/vitest.pattern-file.ts index 3b45f99f6109..961acbb0d751 100644 --- a/test/vitest/vitest.pattern-file.ts +++ b/test/vitest/vitest.pattern-file.ts @@ -219,6 +219,9 @@ export function intersectIncludePatterns( return null; } + const literalIncludes = includePatterns.every(isPlainRepoRelativePath) + ? new Set(includePatterns) + : null; const result: string[] = []; for (const candidate of candidatePatterns) { if (!isPlainRepoRelativePath(candidate)) { @@ -231,7 +234,11 @@ export function intersectIncludePatterns( result.push(...intersection); continue; } - if (includePatterns.some((include) => path.matchesGlob(candidate, include))) { + if ( + literalIncludes + ? literalIncludes.has(candidate) + : includePatterns.some((include) => path.matchesGlob(candidate, include)) + ) { result.push(candidate); } } diff --git a/test/vitest/vitest.unit-fast-fake-timers.config.ts b/test/vitest/vitest.unit-fast-fake-timers.config.ts index df378d545df7..d59c631d3d54 100644 --- a/test/vitest/vitest.unit-fast-fake-timers.config.ts +++ b/test/vitest/vitest.unit-fast-fake-timers.config.ts @@ -1,6 +1,10 @@ // Vitest unit fast fake timers config wires the unit fast fake timers test shard. import { defineConfig } from "vitest/config"; -import { loadPatternListFromEnv, narrowIncludePatternsForCli } from "./vitest.pattern-file.ts"; +import { + intersectIncludePatterns, + loadPatternListFromEnv, + narrowIncludePatternsForCli, +} from "./vitest.pattern-file.ts"; import { nonIsolatedRunnerPath, resolveRepoRootPath, @@ -14,8 +18,11 @@ export function createUnitFastFakeTimersVitestConfig( ) { const sharedTest = sharedVitestConfig.test ?? {}; const sharedSequence = (sharedTest as { sequence?: { groupOrder?: number } }).sequence; - const includeFromEnv = loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env); const unitFastTimerTestFiles = getUnitFastTimerTestFiles(); + const includeFromEnv = intersectIncludePatterns( + unitFastTimerTestFiles, + loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env), + ); const cliInclude = narrowIncludePatternsForCli(unitFastTimerTestFiles, options.argv); return defineConfig({ diff --git a/test/vitest/vitest.unit-fast-isolated.config.ts b/test/vitest/vitest.unit-fast-isolated.config.ts index d3b6ab66d358..1b61731954cf 100644 --- a/test/vitest/vitest.unit-fast-isolated.config.ts +++ b/test/vitest/vitest.unit-fast-isolated.config.ts @@ -1,6 +1,10 @@ // Vitest unit fast isolated config wires audited stateful tests out of shared module caches. import { defineConfig } from "vitest/config"; -import { loadPatternListFromEnv, narrowIncludePatternsForCli } from "./vitest.pattern-file.ts"; +import { + intersectIncludePatterns, + loadPatternListFromEnv, + narrowIncludePatternsForCli, +} from "./vitest.pattern-file.ts"; import { resolveRepoRootPath, sharedVitestConfig } from "./vitest.shared.config.ts"; import { getUnitFastIsolatedTestFiles } from "./vitest.unit-fast-paths.mjs"; @@ -9,8 +13,11 @@ export function createUnitFastIsolatedVitestConfig( options: { argv?: string[] } = {}, ) { const sharedTest = sharedVitestConfig.test ?? {}; - const includeFromEnv = loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env); const isolatedTestFiles = getUnitFastIsolatedTestFiles(); + const includeFromEnv = intersectIncludePatterns( + isolatedTestFiles, + loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env), + ); const cliInclude = narrowIncludePatternsForCli(isolatedTestFiles, options.argv); return defineConfig({ diff --git a/test/vitest/vitest.unit-fast.config.ts b/test/vitest/vitest.unit-fast.config.ts index ab11e54c20ed..64486161529c 100644 --- a/test/vitest/vitest.unit-fast.config.ts +++ b/test/vitest/vitest.unit-fast.config.ts @@ -1,6 +1,10 @@ // Vitest unit fast config wires the unit fast test shard. import { defineConfig } from "vitest/config"; -import { loadPatternListFromEnv, narrowIncludePatternsForCli } from "./vitest.pattern-file.ts"; +import { + intersectIncludePatterns, + loadPatternListFromEnv, + narrowIncludePatternsForCli, +} from "./vitest.pattern-file.ts"; import { resolveRepoRootPath, sharedVitestConfig } from "./vitest.shared.config.ts"; import { getUnitFastIsolatedTestFiles, @@ -13,12 +17,15 @@ export function createUnitFastVitestConfig( options: { argv?: string[]; runner?: string } = {}, ) { const sharedTest = sharedVitestConfig.test ?? {}; - const includeFromEnv = loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env); const timerTestFiles = new Set(getUnitFastTimerTestFiles()); const isolatedTestFiles = new Set(getUnitFastIsolatedTestFiles()); const unitFastTestFiles = getUnitFastTestFiles().filter( (file) => !timerTestFiles.has(file) && !isolatedTestFiles.has(file), ); + const includeFromEnv = intersectIncludePatterns( + unitFastTestFiles, + loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env), + ); const cliInclude = narrowIncludePatternsForCli(unitFastTestFiles, options.argv); return defineConfig({