test: harden vitest no-isolate coverage

This commit is contained in:
Peter Steinberger
2026-03-22 10:47:52 -07:00
parent 719bfb46ff
commit 1ceaad18a6
39 changed files with 827 additions and 295 deletions

View File

@@ -1,7 +1,7 @@
import { setTimeout as nativeSetTimeout } from "node:timers";
import { vi } from "vitest";
export function useFastShortTimeouts(maxDelayMs = 2000): () => void {
const realSetTimeout = setTimeout;
const spy = vi.spyOn(global, "setTimeout").mockImplementation(((
handler: TimerHandler,
timeout?: number,
@@ -9,9 +9,9 @@ export function useFastShortTimeouts(maxDelayMs = 2000): () => void {
) => {
const delay = typeof timeout === "number" ? timeout : 0;
if (delay > 0 && delay <= maxDelayMs) {
return realSetTimeout(handler, 0, ...args);
return nativeSetTimeout(handler, 0, ...args);
}
return realSetTimeout(handler, delay, ...args);
return nativeSetTimeout(handler, delay, ...args);
}) as typeof setTimeout);
return () => spy.mockRestore();
}

View File

@@ -0,0 +1,68 @@
import fs from "node:fs";
import { TestRunner, type RunnerTestSuite, vi } from "vitest";
type EvaluatedModuleNode = {
promise?: unknown;
exports?: unknown;
evaluated?: boolean;
importers: Set<string>;
};
type EvaluatedModules = {
idToModuleMap: Map<string, EvaluatedModuleNode>;
};
function resetEvaluatedModules(modules: EvaluatedModules, resetMocks: boolean) {
const skipPaths = [
/\/vitest\/dist\//,
/vitest-virtual-\w+\/dist/u,
/@vitest\/dist/u,
...(resetMocks ? [] : [/^mock:/u]),
];
modules.idToModuleMap.forEach((node, modulePath) => {
if (skipPaths.some((pattern) => pattern.test(modulePath))) {
return;
}
node.promise = undefined;
node.exports = undefined;
node.evaluated = false;
node.importers.clear();
});
}
export default class OpenClawNonIsolatedRunner extends TestRunner {
override onCollectStart(file: { filepath: string }) {
super.onCollectStart(file);
const orderLogPath = process.env.OPENCLAW_VITEST_FILE_ORDER_LOG?.trim();
if (orderLogPath) {
fs.appendFileSync(orderLogPath, `START ${file.filepath}\n`);
}
}
override async onAfterRunSuite(suite: RunnerTestSuite) {
await super.onAfterRunSuite(suite);
if (this.config.isolate || !("filepath" in suite) || typeof suite.filepath !== "string") {
return;
}
const orderLogPath = process.env.OPENCLAW_VITEST_FILE_ORDER_LOG?.trim();
if (orderLogPath) {
fs.appendFileSync(orderLogPath, `END ${suite.filepath}\n`);
}
// Mirror the missing cleanup from Vitest isolate mode so shared workers do
// not carry file-scoped timers, stubs, spies, or stale module state
// forward into the next file.
if (vi.isFakeTimers()) {
vi.useRealTimers();
}
vi.restoreAllMocks();
vi.unstubAllGlobals();
vi.unstubAllEnvs();
vi.clearAllMocks();
vi.resetModules();
this.moduleRunner?.mocker?.reset?.();
resetEvaluatedModules(this.workerState.evaluatedModules as EvaluatedModules, true);
}
}

View File

@@ -75,6 +75,60 @@ const pickSendFn = (id: ChannelId, deps?: OutboundSendDeps) => {
return deps?.[id] as ((...args: unknown[]) => Promise<unknown>) | undefined;
};
type VitestEvaluatedModuleNode = {
promise?: unknown;
exports?: unknown;
evaluated?: boolean;
importers: Set<string>;
};
type VitestEvaluatedModules = {
idToModuleMap: Map<string, VitestEvaluatedModuleNode>;
};
const resetVitestWorkerModules = (resetMocks: boolean) => {
const workerState = (
globalThis as typeof globalThis & {
__vitest_worker__?: {
evaluatedModules?: VitestEvaluatedModules;
};
}
).__vitest_worker__;
const modules = workerState?.evaluatedModules;
if (!modules) {
return;
}
const skipPaths = [
/\/vitest\/dist\//,
/vitest-virtual-\w+\/dist/u,
/@vitest\/dist/u,
...(resetMocks ? [] : [/^mock:/u]),
];
modules.idToModuleMap.forEach((node, modulePath) => {
if (skipPaths.some((pattern) => pattern.test(modulePath))) {
return;
}
node.promise = undefined;
node.exports = undefined;
node.evaluated = false;
node.importers.clear();
});
};
const resetVitestWorkerFileState = () => {
const mocker = (
globalThis as typeof globalThis & {
__vitest_mocker__?: {
reset?: () => void;
};
}
).__vitest_mocker__;
mocker?.reset?.();
resetVitestWorkerModules(true);
};
const createStubOutbound = (
id: ChannelId,
deliveryMode: ChannelOutboundAdapter["deliveryMode"] = "direct",
@@ -274,8 +328,17 @@ afterEach(() => {
globalRegistryState.key = null;
globalRegistryState.version += 1;
}
// Guard against leaked fake timers across test files/workers.
if (vi.isFakeTimers()) {
vi.useRealTimers();
}
// Always normalize timer/date state. Some suites call `vi.setSystemTime()`
// without leaving fake timers enabled, which still leaks mocked time into
// later files under `--isolate=false`.
vi.useRealTimers();
// Non-isolated runs reuse the same module graph across files. Clear it so
// hoisted per-file mocks still apply when later files import the same modules.
vi.resetModules();
});
afterAll(() => {
// Mirror Vitest's isolate-mode file cleanup so `--isolate=false` does not
// carry hoisted mocks or stale module graphs into the next test file.
resetVitestWorkerFileState();
});