mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-22 02:31:45 +00:00
test: isolate stateful unit-fast tests (#108334)
This commit is contained in:
committed by
GitHub
parent
dd149f01ba
commit
a748634aa9
@@ -902,6 +902,7 @@ const SPLIT_NODE_SHARDS = new Map([
|
||||
shardName: "core-unit-fast",
|
||||
configs: [
|
||||
"test/vitest/vitest.unit-fast.config.ts",
|
||||
"test/vitest/vitest.unit-fast-isolated.config.ts",
|
||||
"test/vitest/vitest.unit-fast-fake-timers.config.ts",
|
||||
],
|
||||
requiresDist: false,
|
||||
|
||||
@@ -51,8 +51,10 @@ import {
|
||||
toolingIsolatedTestFiles,
|
||||
} from "../test/vitest/vitest.tooling-isolated-paths.mjs";
|
||||
import {
|
||||
getUnitFastIsolatedTestFiles,
|
||||
getUnitFastTestFiles,
|
||||
getUnitFastTimerTestFiles,
|
||||
resolveUnitFastIsolatedTestIncludePattern,
|
||||
resolveUnitFastTestIncludePattern,
|
||||
resolveUnitFastTimerTestIncludePattern,
|
||||
} from "../test/vitest/vitest.unit-fast-paths.mjs";
|
||||
@@ -164,6 +166,7 @@ const PLUGIN_SDK_LIGHT_VITEST_CONFIG = "test/vitest/vitest.plugin-sdk-light.conf
|
||||
const PLUGIN_SDK_VITEST_CONFIG = "test/vitest/vitest.plugin-sdk.config.ts";
|
||||
const PLUGINS_VITEST_CONFIG = "test/vitest/vitest.plugins.config.ts";
|
||||
const UNIT_FAST_VITEST_CONFIG = "test/vitest/vitest.unit-fast.config.ts";
|
||||
const UNIT_FAST_ISOLATED_VITEST_CONFIG = "test/vitest/vitest.unit-fast-isolated.config.ts";
|
||||
const UNIT_FAST_FAKE_TIMERS_VITEST_CONFIG = "test/vitest/vitest.unit-fast-fake-timers.config.ts";
|
||||
const UNIT_SECURITY_VITEST_CONFIG = "test/vitest/vitest.unit-security.config.ts";
|
||||
const UNIT_SRC_VITEST_CONFIG = "test/vitest/vitest.unit-src.config.ts";
|
||||
@@ -201,6 +204,7 @@ const FULL_SUITE_CONFIG_WEIGHT = new Map([
|
||||
["test/vitest/vitest.tasks.config.ts", 165],
|
||||
[CHANNEL_VITEST_CONFIG, 164],
|
||||
[UNIT_FAST_VITEST_CONFIG, 160],
|
||||
[UNIT_FAST_ISOLATED_VITEST_CONFIG, 159],
|
||||
[AUTO_REPLY_REPLY_VITEST_CONFIG, 155],
|
||||
[INFRA_VITEST_CONFIG, 145],
|
||||
["test/vitest/vitest.secrets.config.ts", 140],
|
||||
@@ -385,6 +389,7 @@ const VITEST_CONFIG_BY_KIND = {
|
||||
pluginSdkLight: PLUGIN_SDK_LIGHT_VITEST_CONFIG,
|
||||
process: PROCESS_VITEST_CONFIG,
|
||||
unitFast: UNIT_FAST_VITEST_CONFIG,
|
||||
unitFastIsolated: UNIT_FAST_ISOLATED_VITEST_CONFIG,
|
||||
unitFastFakeTimers: UNIT_FAST_FAKE_TIMERS_VITEST_CONFIG,
|
||||
unitSecurity: UNIT_SECURITY_VITEST_CONFIG,
|
||||
unitSrc: UNIT_SRC_VITEST_CONFIG,
|
||||
@@ -2565,7 +2570,10 @@ function listToolingFullSuiteTestTargets(cwd) {
|
||||
|
||||
function listUnitFastFullSuiteTestTargets() {
|
||||
const timerTargets = new Set(getUnitFastTimerTestFiles());
|
||||
return getUnitFastTestFiles().filter((file) => !timerTargets.has(file));
|
||||
const isolatedTargets = new Set(getUnitFastIsolatedTestFiles());
|
||||
return getUnitFastTestFiles().filter(
|
||||
(file) => !timerTargets.has(file) && !isolatedTargets.has(file),
|
||||
);
|
||||
}
|
||||
|
||||
function listAgentsCoreFullSuiteTestTargets(cwd) {
|
||||
@@ -3798,6 +3806,9 @@ function classifyTarget(arg, cwd) {
|
||||
if (resolveUnitFastTimerTestIncludePattern(relative)) {
|
||||
return "unitFastFakeTimers";
|
||||
}
|
||||
if (resolveUnitFastIsolatedTestIncludePattern(relative)) {
|
||||
return "unitFastIsolated";
|
||||
}
|
||||
if (resolveUnitFastTestIncludePattern(relative)) {
|
||||
return "unitFast";
|
||||
}
|
||||
@@ -3991,6 +4002,10 @@ function resolveLightLaneIncludePatterns(kind, targetArg, cwd) {
|
||||
const includePattern = resolveUnitFastTimerTestIncludePattern(relative);
|
||||
return includePattern ? [includePattern] : null;
|
||||
}
|
||||
if (kind === "unitFastIsolated") {
|
||||
const includePattern = resolveUnitFastIsolatedTestIncludePattern(relative);
|
||||
return includePattern ? [includePattern] : null;
|
||||
}
|
||||
if (kind === "pluginSdkLight") {
|
||||
const includePattern = resolvePluginSdkLightIncludePattern(relative);
|
||||
return includePattern ? [includePattern] : null;
|
||||
@@ -4170,6 +4185,7 @@ export function buildVitestRunPlans(
|
||||
|
||||
const orderedKinds = [
|
||||
"unitFast",
|
||||
"unitFastIsolated",
|
||||
"unitFastFakeTimers",
|
||||
"default",
|
||||
"boundary",
|
||||
|
||||
@@ -312,6 +312,7 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => {
|
||||
{
|
||||
configs: [
|
||||
"test/vitest/vitest.unit-fast.config.ts",
|
||||
"test/vitest/vitest.unit-fast-isolated.config.ts",
|
||||
"test/vitest/vitest.unit-fast-fake-timers.config.ts",
|
||||
],
|
||||
requiresDist: false,
|
||||
|
||||
@@ -2707,12 +2707,18 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
|
||||
it("chunks the broad shell helper tooling shard after isolated targets", () => {
|
||||
const plans = buildVitestRunPlans(["test/scripts"], process.cwd());
|
||||
expect(plans.slice(0, 3)).toEqual([
|
||||
expect(plans.slice(0, 4)).toEqual([
|
||||
expect.objectContaining({
|
||||
config: "test/vitest/vitest.unit-fast.config.ts",
|
||||
includePatterns: expect.arrayContaining(["test/scripts/arg-utils.test.ts"]),
|
||||
watchMode: false,
|
||||
}),
|
||||
{
|
||||
config: "test/vitest/vitest.unit-fast-isolated.config.ts",
|
||||
forwardedArgs: [],
|
||||
includePatterns: ["test/scripts/android-version.test.ts"],
|
||||
watchMode: false,
|
||||
},
|
||||
{
|
||||
config: "test/vitest/vitest.tooling-docker.config.ts",
|
||||
forwardedArgs: [],
|
||||
@@ -2732,7 +2738,7 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
]);
|
||||
const e2ePlans = plans.filter((plan) => plan.config === "test/vitest/vitest.e2e.config.ts");
|
||||
const toolingPlans = plans
|
||||
.slice(3)
|
||||
.slice(4)
|
||||
.filter((plan) => plan.config === "test/vitest/vitest.tooling.config.ts");
|
||||
const toolingTargets = toolingPlans.flatMap((plan) => plan.includePatterns ?? []);
|
||||
|
||||
@@ -2818,6 +2824,10 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
config: "test/vitest/vitest.unit-fast.config.ts",
|
||||
includePatterns: expect.arrayContaining(["src/plugin-sdk/access-groups.test.ts"]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
config: "test/vitest/vitest.unit-fast-isolated.config.ts",
|
||||
includePatterns: ["src/plugin-sdk/memory-host-events.test.ts"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
config: "test/vitest/vitest.plugin-sdk-light.config.ts",
|
||||
includePatterns: expect.arrayContaining(["src/plugin-sdk/acp-runtime.test.ts"]),
|
||||
@@ -2855,12 +2865,18 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
|
||||
it("chunks broad shell helper globs after isolated targets", () => {
|
||||
const plans = buildVitestRunPlans(["test/scripts/*.test.ts"], process.cwd());
|
||||
expect(plans.slice(0, 3)).toEqual([
|
||||
expect(plans.slice(0, 4)).toEqual([
|
||||
expect.objectContaining({
|
||||
config: "test/vitest/vitest.unit-fast.config.ts",
|
||||
includePatterns: expect.arrayContaining(["test/scripts/arg-utils.test.ts"]),
|
||||
watchMode: false,
|
||||
}),
|
||||
{
|
||||
config: "test/vitest/vitest.unit-fast-isolated.config.ts",
|
||||
forwardedArgs: [],
|
||||
includePatterns: ["test/scripts/android-version.test.ts"],
|
||||
watchMode: false,
|
||||
},
|
||||
{
|
||||
config: "test/vitest/vitest.tooling-docker.config.ts",
|
||||
forwardedArgs: [],
|
||||
@@ -2880,7 +2896,7 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
]);
|
||||
const e2ePlans = plans.filter((plan) => plan.config === "test/vitest/vitest.e2e.config.ts");
|
||||
const toolingPlans = plans
|
||||
.slice(3)
|
||||
.slice(4)
|
||||
.filter((plan) => plan.config === "test/vitest/vitest.tooling.config.ts");
|
||||
const toolingTargets = toolingPlans.flatMap((plan) => plan.includePatterns ?? []);
|
||||
|
||||
@@ -3747,6 +3763,22 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("routes forced stateful unit-fast tests to the isolated lane", () => {
|
||||
const plans = buildVitestRunPlans(
|
||||
["src/system-agent/assistant.configured.test.ts"],
|
||||
process.cwd(),
|
||||
);
|
||||
|
||||
expect(plans).toEqual([
|
||||
{
|
||||
config: "test/vitest/vitest.unit-fast-isolated.config.ts",
|
||||
forwardedArgs: [],
|
||||
includePatterns: ["src/system-agent/assistant.configured.test.ts"],
|
||||
watchMode: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("routes fake-timer unit-fast tests to the serial fake-timer lane", () => {
|
||||
const plans = buildVitestRunPlans(["src/acp/control-plane/manager.test.ts"], process.cwd());
|
||||
|
||||
@@ -4457,6 +4489,7 @@ describe("scripts/test-projects full-suite sharding", () => {
|
||||
}
|
||||
expect(leafShardPlans.map((plan) => plan.config)).toEqual([
|
||||
...unitFastPlans.map(() => unitFastConfig),
|
||||
"test/vitest/vitest.unit-fast-isolated.config.ts",
|
||||
"test/vitest/vitest.unit-fast-fake-timers.config.ts",
|
||||
"test/vitest/vitest.unit-src.config.ts",
|
||||
"test/vitest/vitest.unit-security.config.ts",
|
||||
@@ -4573,7 +4606,7 @@ describe("scripts/test-projects full-suite sharding", () => {
|
||||
expect(unitFastPlans.every((plan) => plan.forwardedArgs.length <= 70)).toBe(true);
|
||||
expect(unitFastTargets.length).toBeGreaterThan(1_000);
|
||||
expect(new Set(unitFastTargets).size).toBe(unitFastTargets.length);
|
||||
expect(unitFastTargets).toContain("extensions/canvas/src/host/server.state-dir.test.ts");
|
||||
expect(unitFastTargets).not.toContain("extensions/canvas/src/host/server.state-dir.test.ts");
|
||||
expect(unitFastTargets).not.toContain("src/utils.test.ts");
|
||||
const toolingTargets = toolingPlans.flatMap((plan) => plan.forwardedArgs);
|
||||
expect(toolingPlans.length).toBeGreaterThan(1);
|
||||
|
||||
@@ -29,6 +29,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 { createUnitFastVitestConfig } from "./vitest/vitest.unit-fast.config.ts";
|
||||
import { createUnitVitestConfig } from "./vitest/vitest.unit.config.ts";
|
||||
|
||||
@@ -237,6 +238,13 @@ describe("projects vitest config", () => {
|
||||
expect(testConfig.runner).toBeUndefined();
|
||||
});
|
||||
|
||||
it("isolates forced unit-fast files from shared module caches", () => {
|
||||
const config = createUnitFastIsolatedVitestConfig();
|
||||
const testConfig = requireTestConfig(config);
|
||||
expect(testConfig.isolate).toBe(true);
|
||||
expect(testConfig.runner).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps fake-timer unit-fast files serial with the non-isolated runner", () => {
|
||||
const config = createUnitFastFakeTimersVitestConfig();
|
||||
const testConfig = requireTestConfig(config);
|
||||
|
||||
@@ -5,18 +5,22 @@ import { spawnNodeEvalSync } from "../src/test-utils/node-process.js";
|
||||
import { createCommandsLightVitestConfig } from "./vitest/vitest.commands-light.config.ts";
|
||||
import { createPluginSdkLightVitestConfig } from "./vitest/vitest.plugin-sdk-light.config.ts";
|
||||
import { createUnitFastFakeTimersVitestConfig } from "./vitest/vitest.unit-fast-fake-timers.config.ts";
|
||||
import { createUnitFastIsolatedVitestConfig } from "./vitest/vitest.unit-fast-isolated.config.ts";
|
||||
import {
|
||||
classifyUnitFastTestFileContent,
|
||||
collectBroadUnitFastTestCandidates,
|
||||
collectUnitFastTestCandidates,
|
||||
collectUnitFastTestFileAnalysis,
|
||||
forcedUnitFastTestFiles,
|
||||
getUnitFastIsolatedTestFiles,
|
||||
getUnitFastTestFiles,
|
||||
getUnitFastTestFilesForIncludePatterns,
|
||||
getUnitFastTimerTestFiles,
|
||||
isUnitFastTestFile,
|
||||
isUnitFastIsolatedTestFile,
|
||||
isUnitFastTimerTestFile,
|
||||
resolveUnitFastTestIncludePattern,
|
||||
resolveUnitFastIsolatedTestIncludePattern,
|
||||
resolveUnitFastTimerTestIncludePattern,
|
||||
} from "./vitest/vitest.unit-fast-paths.mjs";
|
||||
import { createUnitFastVitestConfig } from "./vitest/vitest.unit-fast.config.ts";
|
||||
@@ -62,6 +66,7 @@ describe("unit-fast vitest lane", () => {
|
||||
let configProbeResult: ReturnType<typeof spawnNodeEvalSync>;
|
||||
let unitFastConfig: ReturnType<typeof createUnitFastVitestConfig>;
|
||||
let unitFastTestFiles: ReturnType<typeof getUnitFastTestFiles>;
|
||||
let unitFastIsolatedTestFiles: ReturnType<typeof getUnitFastIsolatedTestFiles>;
|
||||
let unitFastTimerTestFiles: ReturnType<typeof getUnitFastTimerTestFiles>;
|
||||
let unitFastAnalysis: ReturnType<typeof collectUnitFastTestFileAnalysis>;
|
||||
let broadCandidates: ReturnType<typeof collectBroadUnitFastTestCandidates>;
|
||||
@@ -137,6 +142,7 @@ describe("unit-fast vitest lane", () => {
|
||||
});
|
||||
unitFastConfig = createUnitFastVitestConfig({});
|
||||
unitFastTestFiles = getUnitFastTestFiles();
|
||||
unitFastIsolatedTestFiles = getUnitFastIsolatedTestFiles();
|
||||
unitFastTimerTestFiles = getUnitFastTimerTestFiles();
|
||||
unitFastAnalysis = collectUnitFastTestFileAnalysis();
|
||||
currentCandidates = collectUnitFastTestCandidates();
|
||||
@@ -170,30 +176,10 @@ describe("unit-fast vitest lane", () => {
|
||||
expect(testConfig.include).toContain("src/acp/control-plane/runtime-cache.test.ts");
|
||||
expect(testConfig.include).toContain("src/acp/runtime/registry.test.ts");
|
||||
expect(testConfig.include).toContain("src/commands/status-overview-values.test.ts");
|
||||
expect(testConfig.include).toContain("src/entry.version-fast-path.test.ts");
|
||||
expect(testConfig.include).toContain("src/flows/doctor-startup-channel-maintenance.test.ts");
|
||||
expect(testConfig.include).toContain("src/system-agent/rescue-policy.test.ts");
|
||||
expect(testConfig.include).toContain("src/system-agent/assistant.configured.test.ts");
|
||||
expect(testConfig.include).toContain("src/flows/search-setup.test.ts");
|
||||
expect(testConfig.include).toContain("src/plugins/config-policy.test.ts");
|
||||
expect(testConfig.include).toContain("src/proxy-capture/proxy-server.test.ts");
|
||||
expect(testConfig.include).toContain("src/talk/agent-consult-tool.test.ts");
|
||||
expect(testConfig.include).toContain("src/sessions/session-lifecycle-events.test.ts");
|
||||
expect(testConfig.include).toContain("src/sessions/transcript-events.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/audit-channel-source-config-slack.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/audit-config-symlink.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/audit-exec-sandbox-host.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/audit-gateway.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/audit-gateway-auth-selection.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/audit-gateway-http-auth.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/audit-gateway-tools-http.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/audit-plugin-readonly-scope.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/audit-loopback-logging.test.ts");
|
||||
expect(testConfig.include).toContain("src/video-generation/provider-registry.test.ts");
|
||||
expect(testConfig.include).toContain("src/plugin-sdk/provider-entry.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/dangerous-config-flags.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/context-visibility.test.ts");
|
||||
expect(testConfig.include).toContain("src/security/safe-regex.test.ts");
|
||||
expect(testConfig.include).not.toEqual(expect.arrayContaining(unitFastIsolatedTestFiles));
|
||||
});
|
||||
|
||||
it("does not treat moved config paths as CLI include filters", () => {
|
||||
@@ -237,7 +223,7 @@ describe("unit-fast vitest lane", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("routes audited stateful-looking tests through the fast lane", () => {
|
||||
it("routes audited stateful-looking tests through the isolated fast lane", () => {
|
||||
const forcedFileSet = new Set(forcedUnitFastTestFiles);
|
||||
const forcedAnalysisCount = countMatching(unitFastAnalysis, (entry) =>
|
||||
forcedFileSet.has(entry.file),
|
||||
@@ -247,9 +233,39 @@ describe("unit-fast vitest lane", () => {
|
||||
for (const file of forcedUnitFastTestFiles) {
|
||||
expect(unitFastTestFiles).toContain(file);
|
||||
expect(isUnitFastTestFile(file)).toBe(true);
|
||||
if (unitFastTimerTestFiles.includes(file)) {
|
||||
expect(unitFastIsolatedTestFiles).not.toContain(file);
|
||||
} else {
|
||||
expect(unitFastIsolatedTestFiles).toContain(file);
|
||||
expect(isUnitFastIsolatedTestFile(file)).toBe(true);
|
||||
expect(resolveUnitFastTestIncludePattern(file)).toBeNull();
|
||||
expect(resolveUnitFastIsolatedTestIncludePattern(file)).toBe(file);
|
||||
}
|
||||
}
|
||||
const unroutedForcedFiles = collectUnroutedForcedFiles(unitFastAnalysis, forcedFileSet);
|
||||
expect(unroutedForcedFiles).toStrictEqual([]);
|
||||
|
||||
const isolatedConfig = requireTestConfig(createUnitFastIsolatedVitestConfig({}));
|
||||
expect(isolatedConfig.isolate).toBe(true);
|
||||
expect(isolatedConfig.runner).toBeUndefined();
|
||||
expect(isolatedConfig.include).toEqual(unitFastIsolatedTestFiles);
|
||||
expect(isolatedConfig.setupFiles).toStrictEqual([
|
||||
expect.stringMatching(ENV_ISOLATION_SETUP_PATH),
|
||||
]);
|
||||
});
|
||||
|
||||
it("isolates tests that import stateful test helpers", () => {
|
||||
const files = [
|
||||
"src/agents/auth-profiles/oauth-refresh-error.test.ts",
|
||||
"src/auto-reply/reply/agent-runner-execution-runtime.test.ts",
|
||||
];
|
||||
for (const file of files) {
|
||||
const analysis = unitFastAnalysis.find((entry) => entry.file === file);
|
||||
expect(analysis?.reasons).toContain("stateful-test-helper");
|
||||
expect(unitFastIsolatedTestFiles).toContain(file);
|
||||
expect(resolveUnitFastTestIncludePattern(file)).toBeNull();
|
||||
expect(resolveUnitFastIsolatedTestIncludePattern(file)).toBe(file);
|
||||
}
|
||||
});
|
||||
|
||||
it("routes fake-timer unit-fast tests through the serial fake-timer lane", () => {
|
||||
@@ -265,8 +281,10 @@ describe("unit-fast vitest lane", () => {
|
||||
}
|
||||
|
||||
const fastConfig = requireTestConfig(unitFastConfig);
|
||||
const isolatedConfig = requireTestConfig(createUnitFastIsolatedVitestConfig({}));
|
||||
const timerConfig = requireTestConfig(createUnitFastFakeTimersVitestConfig({}));
|
||||
expect(fastConfig.include).not.toEqual(expect.arrayContaining(unitFastTimerTestFiles));
|
||||
expect(isolatedConfig.include).not.toEqual(expect.arrayContaining(unitFastTimerTestFiles));
|
||||
expect(timerConfig.include).toEqual(unitFastTimerTestFiles);
|
||||
expect(timerConfig.fileParallelism).toBe(false);
|
||||
expect(timerConfig.maxWorkers).toBe(1);
|
||||
|
||||
@@ -40,6 +40,7 @@ export const rootVitestProjects = [
|
||||
"test/vitest/vitest.daemon.config.ts",
|
||||
"test/vitest/vitest.media.config.ts",
|
||||
"test/vitest/vitest.unit-fast.config.ts",
|
||||
"test/vitest/vitest.unit-fast-isolated.config.ts",
|
||||
"test/vitest/vitest.unit-fast-fake-timers.config.ts",
|
||||
"test/vitest/vitest.plugin-sdk-light.config.ts",
|
||||
"test/vitest/vitest.plugin-sdk.config.ts",
|
||||
|
||||
@@ -9,6 +9,7 @@ export default defineConfig({
|
||||
runner: undefined,
|
||||
projects: [
|
||||
"test/vitest/vitest.unit-fast.config.ts",
|
||||
"test/vitest/vitest.unit-fast-isolated.config.ts",
|
||||
"test/vitest/vitest.unit-fast-fake-timers.config.ts",
|
||||
],
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ export const fullSuiteVitestShards = [
|
||||
name: "core-unit-fast",
|
||||
projects: [
|
||||
"test/vitest/vitest.unit-fast.config.ts",
|
||||
"test/vitest/vitest.unit-fast-isolated.config.ts",
|
||||
"test/vitest/vitest.unit-fast-fake-timers.config.ts",
|
||||
],
|
||||
},
|
||||
|
||||
32
test/vitest/vitest.unit-fast-isolated.config.ts
Normal file
32
test/vitest/vitest.unit-fast-isolated.config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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 { resolveRepoRootPath, sharedVitestConfig } from "./vitest.shared.config.ts";
|
||||
import { getUnitFastIsolatedTestFiles } from "./vitest.unit-fast-paths.mjs";
|
||||
|
||||
export function createUnitFastIsolatedVitestConfig(
|
||||
env: Record<string, string | undefined> = process.env,
|
||||
options: { argv?: string[] } = {},
|
||||
) {
|
||||
const sharedTest = sharedVitestConfig.test ?? {};
|
||||
const includeFromEnv = loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
|
||||
const isolatedTestFiles = getUnitFastIsolatedTestFiles();
|
||||
const cliInclude = narrowIncludePatternsForCli(isolatedTestFiles, options.argv);
|
||||
|
||||
return defineConfig({
|
||||
...sharedVitestConfig,
|
||||
test: {
|
||||
...sharedTest,
|
||||
name: "unit-fast-isolated",
|
||||
// Forced stateful tests can mock modules imported by later files, so each gets a fresh graph.
|
||||
isolate: true,
|
||||
runner: undefined,
|
||||
setupFiles: [resolveRepoRootPath("test/setup.env.ts")],
|
||||
include: includeFromEnv ?? cliInclude ?? isolatedTestFiles,
|
||||
exclude: sharedTest.exclude ?? [],
|
||||
passWithNoTests: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default createUnitFastIsolatedVitestConfig();
|
||||
@@ -22,8 +22,11 @@ export function getUnitFastTestFilesForIncludePatterns(
|
||||
options?: { dir?: string },
|
||||
): string[];
|
||||
export function getUnitFastTestFiles(): string[];
|
||||
export function getUnitFastIsolatedTestFiles(): string[];
|
||||
export function getUnitFastTimerTestFiles(): string[];
|
||||
export function isUnitFastTestFile(file: string): boolean;
|
||||
export function isUnitFastIsolatedTestFile(file: string): boolean;
|
||||
export function isUnitFastTimerTestFile(file: string): boolean;
|
||||
export function resolveUnitFastTestIncludePattern(file: string): string | null;
|
||||
export function resolveUnitFastIsolatedTestIncludePattern(file: string): string | null;
|
||||
export function resolveUnitFastTimerTestIncludePattern(file: string): string | null;
|
||||
|
||||
@@ -306,6 +306,42 @@ const disqualifyingPatterns = [
|
||||
},
|
||||
];
|
||||
|
||||
const statefulTestHelperImportPattern =
|
||||
/\bfrom\s+["']([^"']*(?:test-support|\.harness)(?:\.js|\.ts)?)["']/gu;
|
||||
const statefulTestHelperByKey = new Map();
|
||||
|
||||
function importsStatefulTestHelper(cwd, file, source) {
|
||||
for (const match of source.matchAll(statefulTestHelperImportPattern)) {
|
||||
const specifier = match[1];
|
||||
if (!specifier.startsWith(".")) {
|
||||
continue;
|
||||
}
|
||||
const helperPath = path.join(
|
||||
path.dirname(file),
|
||||
specifier.endsWith(".js")
|
||||
? `${specifier.slice(0, -3)}.ts`
|
||||
: specifier.endsWith(".ts")
|
||||
? specifier
|
||||
: `${specifier}.ts`,
|
||||
);
|
||||
const cacheKey = `${normalizeRepoPath(cwd)}\0${normalizeRepoPath(helperPath)}`;
|
||||
let stateful = statefulTestHelperByKey.get(cacheKey);
|
||||
if (stateful === undefined) {
|
||||
try {
|
||||
const helperSource = fs.readFileSync(path.join(cwd, helperPath), "utf8");
|
||||
stateful = classifyUnitFastTestFileContent(helperSource).length > 0;
|
||||
} catch {
|
||||
stateful = false;
|
||||
}
|
||||
statefulTestHelperByKey.set(cacheKey, stateful);
|
||||
}
|
||||
if (stateful) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function matchesAnyGlob(file, patterns) {
|
||||
return patterns.some((pattern) => path.matchesGlob(file, pattern));
|
||||
}
|
||||
@@ -504,10 +540,18 @@ function analyzeUnitFastTestFile(cwd, file) {
|
||||
try {
|
||||
const source = fs.readFileSync(path.join(cwd, file), "utf8");
|
||||
const reasons = classifyUnitFastTestFileContent(source);
|
||||
if (importsStatefulTestHelper(cwd, file, source)) {
|
||||
// The helper executes in the importing file's module scope, so its mocks and
|
||||
// singleton mutations need the same isolation as stateful code in the test itself.
|
||||
reasons.push("stateful-test-helper");
|
||||
}
|
||||
const forced = forcedUnitFastTestFileSet.has(file);
|
||||
analysis = {
|
||||
file,
|
||||
unitFast: forced || reasons.length === 0,
|
||||
unitFast:
|
||||
forced ||
|
||||
reasons.length === 0 ||
|
||||
reasons.every((reason) => reason === "stateful-test-helper"),
|
||||
forced,
|
||||
reasons,
|
||||
};
|
||||
@@ -541,6 +585,8 @@ export function collectUnitFastTestFileAnalysis(cwd = process.cwd(), options = {
|
||||
|
||||
let cachedUnitFastTestFiles = null;
|
||||
let cachedUnitFastTestFileSet = null;
|
||||
let cachedUnitFastIsolatedTestFiles = null;
|
||||
let cachedUnitFastIsolatedTestFileSet = null;
|
||||
let cachedUnitFastTimerTestFiles = null;
|
||||
let cachedUnitFastTimerTestFileSet = null;
|
||||
const scopedUnitFastTestFilesByKey = new Map();
|
||||
@@ -607,6 +653,22 @@ export function getUnitFastTimerTestFiles() {
|
||||
return cachedUnitFastTimerTestFiles;
|
||||
}
|
||||
|
||||
export function getUnitFastIsolatedTestFiles() {
|
||||
if (cachedUnitFastIsolatedTestFiles !== null) {
|
||||
return cachedUnitFastIsolatedTestFiles;
|
||||
}
|
||||
const timerTestFiles = new Set(getUnitFastTimerTestFiles());
|
||||
cachedUnitFastIsolatedTestFiles = collectUnitFastTestFileAnalysis()
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.unitFast &&
|
||||
!timerTestFiles.has(entry.file) &&
|
||||
(entry.forced || entry.reasons.includes("stateful-test-helper")),
|
||||
)
|
||||
.map((entry) => entry.file);
|
||||
return cachedUnitFastIsolatedTestFiles;
|
||||
}
|
||||
|
||||
function getUnitFastTestFileSet() {
|
||||
if (cachedUnitFastTestFileSet !== null) {
|
||||
return cachedUnitFastTestFileSet;
|
||||
@@ -623,6 +685,14 @@ function getUnitFastTimerTestFileSet() {
|
||||
return cachedUnitFastTimerTestFileSet;
|
||||
}
|
||||
|
||||
function getUnitFastIsolatedTestFileSet() {
|
||||
if (cachedUnitFastIsolatedTestFileSet !== null) {
|
||||
return cachedUnitFastIsolatedTestFileSet;
|
||||
}
|
||||
cachedUnitFastIsolatedTestFileSet = new Set(getUnitFastIsolatedTestFiles());
|
||||
return cachedUnitFastIsolatedTestFileSet;
|
||||
}
|
||||
|
||||
function isUnitFastTestFileOnDemand(file, cwd = process.cwd()) {
|
||||
const normalized = normalizeRepoPath(file);
|
||||
if (!isUnitFastCandidateFile(normalized)) {
|
||||
@@ -639,11 +709,18 @@ export function isUnitFastTimerTestFile(file) {
|
||||
return getUnitFastTimerTestFileSet().has(normalizeRepoPath(file));
|
||||
}
|
||||
|
||||
export function isUnitFastIsolatedTestFile(file) {
|
||||
return getUnitFastIsolatedTestFileSet().has(normalizeRepoPath(file));
|
||||
}
|
||||
|
||||
export function resolveUnitFastTestIncludePattern(file) {
|
||||
const normalized = normalizeRepoPath(file);
|
||||
if (isUnitFastTimerTestFile(normalized)) {
|
||||
return null;
|
||||
}
|
||||
if (isUnitFastIsolatedTestFile(normalized)) {
|
||||
return null;
|
||||
}
|
||||
if (isUnitFastTestFileOnDemand(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
@@ -651,6 +728,9 @@ export function resolveUnitFastTestIncludePattern(file) {
|
||||
if (isUnitFastTimerTestFile(siblingTestFile)) {
|
||||
return null;
|
||||
}
|
||||
if (isUnitFastIsolatedTestFile(siblingTestFile)) {
|
||||
return null;
|
||||
}
|
||||
if (isUnitFastTestFileOnDemand(siblingTestFile)) {
|
||||
return siblingTestFile;
|
||||
}
|
||||
@@ -669,3 +749,12 @@ export function resolveUnitFastTimerTestIncludePattern(file) {
|
||||
const siblingTestFile = normalized.replace(/\.ts$/u, ".test.ts");
|
||||
return isUnitFastTimerTestFile(siblingTestFile) ? siblingTestFile : null;
|
||||
}
|
||||
|
||||
export function resolveUnitFastIsolatedTestIncludePattern(file) {
|
||||
const normalized = normalizeRepoPath(file);
|
||||
if (isUnitFastIsolatedTestFile(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
const siblingTestFile = normalized.replace(/\.ts$/u, ".test.ts");
|
||||
return isUnitFastIsolatedTestFile(siblingTestFile) ? siblingTestFile : null;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import { loadPatternListFromEnv, narrowIncludePatternsForCli } from "./vitest.pattern-file.ts";
|
||||
import { resolveRepoRootPath, sharedVitestConfig } from "./vitest.shared.config.ts";
|
||||
import { getUnitFastTestFiles, getUnitFastTimerTestFiles } from "./vitest.unit-fast-paths.mjs";
|
||||
import {
|
||||
getUnitFastIsolatedTestFiles,
|
||||
getUnitFastTestFiles,
|
||||
getUnitFastTimerTestFiles,
|
||||
} from "./vitest.unit-fast-paths.mjs";
|
||||
|
||||
export function createUnitFastVitestConfig(
|
||||
env: Record<string, string | undefined> = process.env,
|
||||
@@ -11,7 +15,10 @@ export function createUnitFastVitestConfig(
|
||||
const sharedTest = sharedVitestConfig.test ?? {};
|
||||
const includeFromEnv = loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
|
||||
const timerTestFiles = new Set(getUnitFastTimerTestFiles());
|
||||
const unitFastTestFiles = getUnitFastTestFiles().filter((file) => !timerTestFiles.has(file));
|
||||
const isolatedTestFiles = new Set(getUnitFastIsolatedTestFiles());
|
||||
const unitFastTestFiles = getUnitFastTestFiles().filter(
|
||||
(file) => !timerTestFiles.has(file) && !isolatedTestFiles.has(file),
|
||||
);
|
||||
const cliInclude = narrowIncludePatternsForCli(unitFastTestFiles, options.argv);
|
||||
|
||||
return defineConfig({
|
||||
|
||||
Reference in New Issue
Block a user