Files
openclaw/test/non-isolated-runner.ts
Peter Steinberger fe261b0f59 chore(tooling): typecheck root test/** with a dedicated tsgo lane (#104475)
* chore(types): add declaration files for scripts/lib and scripts/e2e modules

* chore(types): add declaration files for top-level script modules (a-m)

* chore(types): add declaration files for top-level script modules (n-z)

* test: use a non-secret-shaped gateway token fixture

* test: type ci workflow guard helpers for the root test lane

* chore(tooling): typecheck root test/** with a dedicated tsgo lane

- test/tsconfig/tsconfig.test.root.json: root-test program (strict unused checks,
  fixtures excluded; two Docker E2E clients that import built dist/** stay out,
  same rationale as the scripts/e2e exclusion in tsconfig.scripts.json)
- tsgo:test:root wired into tsgo:test, check:test-types, scripts/check.mjs, and
  the ci.yml test-types shard, mirroring the tsgo:scripts lane (#104348)
- changed-lane routing: test/**/*.ts (excluding fixtures) and the lane tsconfig
  now trigger 'typecheck test root' in check:changed; previously test/ paths ran
  lint only, so harness type errors surfaced first in CI (#104287 envDir case)
- burn down all 1071 latent type errors in the program: precise param/local
  types across test/scripts, test/vitest, test/e2e, and transitive scripts/e2e
  program members; 205 sibling .d.mts declaration files for imported .mjs
  modules (committed separately); zero any, zero ts-expect-error
- resolve the pre-existing testing star-export ambiguity in
  scripts/e2e/parallels/common.ts with an explicit re-export

Closes #104388

* chore(types): correct declaration fidelity per structured review

- re-derive 51 .d.mts files from implementation data flow instead of
  initializers: fix a wrong never return (runTestProjectsDelegation returns
  the child), add encoding-sensitive exec/spawn overloads (plain-gh), restore
  the full release profile union, make parsed paths string | null, add missing
  parseArgs fields via help/non-help unions, add a missing sibling declaration
  (budget-number-args), drop 15 unused lint directives
- precise install-record/tuple typing removes the type-aware oxlint
  regressions the first declarations caused in scripts/e2e implementations
- route .mts declaration edits under test/ to the testRoot lane and reference
  the test-root project from tsconfig.projects.json so tsgo:all covers it
  (closes both review findings against the lane wiring)

* chore(scripts): keep telegram runner dist typing structural for the boundary guard

* chore(types): declare runtime pack and gateway readiness exports added on main

* test: pin the importTargetPlan form of the plugin-contract plan import

The guard expectation still referenced the raw await import( form that
7ae5996bb3 (#103975) replaced with the importTargetPlan fallback helper;
the assertion fails on current main.
2026-07-11 06:15:41 -07:00

261 lines
8.1 KiB
TypeScript

// Non-isolated runner helps execute tests without Vitest isolation.
import fs from "node:fs";
import path from "node:path";
import { TestRunner, type RunnerTask, type RunnerTestFile, type RunnerTestSuite, vi } from "vitest";
type EvaluatedModuleNode = {
promise?: unknown;
exports?: unknown;
evaluated?: boolean;
importers: Set<string>;
};
type EvaluatedModules = {
idToModuleMap: Map<string, EvaluatedModuleNode>;
};
type TestRunnerInternals = {
moduleRunner?: { mocker?: { reset?: () => void } };
workerState: { evaluatedModules: unknown };
};
const SHARED_TEST_SETUP = Symbol.for("openclaw.sharedTestSetup");
const EMBEDDED_RUN_STATE = Symbol.for("openclaw.embeddedRunState");
const REPLY_RUN_REGISTRY = Symbol.for("openclaw.replyRunRegistry");
const nativeTimerGlobals = {
setTimeout: globalThis.setTimeout,
clearTimeout: globalThis.clearTimeout,
setInterval: globalThis.setInterval,
clearInterval: globalThis.clearInterval,
setImmediate: globalThis.setImmediate,
clearImmediate: globalThis.clearImmediate,
Date: globalThis.Date,
};
function getSharedTestHome(): string | undefined {
const globalState = globalThis as typeof globalThis & {
[SHARED_TEST_SETUP]?: { tempHome?: string };
};
return globalState[SHARED_TEST_SETUP]?.tempHome ?? process.env.OPENCLAW_TEST_HOME;
}
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();
});
}
function restoreSharedTestHomeAfterEnvUnstub(testHomeRaw: string | undefined): void {
const testHome = testHomeRaw?.trim();
if (!testHome) {
return;
}
process.env.HOME = testHome;
process.env.USERPROFILE = testHome;
process.env.OPENCLAW_TEST_HOME = testHome;
delete process.env.OPENCLAW_CONFIG_PATH;
delete process.env.OPENCLAW_STATE_DIR;
delete process.env.OPENCLAW_AGENT_DIR;
process.env.XDG_CONFIG_HOME = path.join(testHome, ".config");
process.env.XDG_DATA_HOME = path.join(testHome, ".local", "share");
process.env.XDG_STATE_HOME = path.join(testHome, ".local", "state");
process.env.XDG_CACHE_HOME = path.join(testHome, ".cache");
}
function restoreRealTimers(): void {
if (vi.isFakeTimers()) {
vi.useRealTimers();
}
}
function restoreNativeTimerGlobals(): void {
Object.assign(globalThis, nativeTimerGlobals);
}
function restoreMocksThenRealTimers(): void {
// A spy created while fake timers are active captures the fake timer as its
// "original" implementation. Restore spies first, then swap timers back.
vi.restoreAllMocks();
restoreRealTimers();
restoreNativeTimerGlobals();
}
type CleanupAction = () => void;
type EmbeddedRunHandle = {
abort?: () => void;
cancel?: (reason?: "user_abort" | "restart" | "superseded") => void;
};
type EmbeddedRunWaiter = {
timer?: NodeJS.Timeout;
resolve?: (ended: boolean) => void;
};
type EmbeddedRunStateForTest = {
activeRuns?: Map<unknown, EmbeddedRunHandle>;
snapshots?: Map<unknown, unknown>;
sessionIdsByKey?: Map<unknown, unknown>;
sessionIdsByFile?: Map<unknown, unknown>;
abandonedRunsBySessionId?: Map<unknown, unknown>;
abandonedRunSessionIdsByKey?: Map<unknown, unknown>;
abandonedRunSessionIdsByFile?: Map<unknown, unknown>;
waiters?: Map<unknown, Set<EmbeddedRunWaiter>>;
modelSwitchRequests?: Map<unknown, unknown>;
};
type ReplyRunWaiter = {
finish?: (ended: boolean) => void;
};
type ReplyRunOperation = {
abortForRestart?: () => void;
};
type ReplyRunStateForTest = {
activeRunsByKey?: Map<unknown, ReplyRunOperation>;
activeSessionIdsByKey?: Map<unknown, unknown>;
activeKeysBySessionId?: Map<unknown, unknown>;
waitKeysBySessionId?: Map<unknown, unknown>;
waitersByKey?: Map<unknown, Set<ReplyRunWaiter>>;
};
function runCleanupActions(actions: CleanupAction[]): unknown {
let firstError: unknown;
for (const action of actions) {
try {
action();
} catch (error) {
firstError ??= error;
}
}
return firstError;
}
function resetOpenClawGlobalRunState(): void {
const cleanupActions: CleanupAction[] = [];
const globalStore = globalThis as Record<PropertyKey, unknown>;
const embeddedRunState = globalStore[EMBEDDED_RUN_STATE] as EmbeddedRunStateForTest | undefined;
for (const handle of embeddedRunState?.activeRuns?.values() ?? []) {
cleanupActions.push(() => {
if (handle.cancel) {
handle.cancel("restart");
return;
}
handle.abort?.();
});
}
for (const waiters of embeddedRunState?.waiters?.values() ?? []) {
for (const waiter of waiters) {
cleanupActions.push(() => {
if (waiter.timer) {
clearTimeout(waiter.timer);
}
waiter.resolve?.(true);
});
}
}
const replyRunState = globalStore[REPLY_RUN_REGISTRY] as ReplyRunStateForTest | undefined;
for (const operation of replyRunState?.activeRunsByKey?.values() ?? []) {
cleanupActions.push(() => {
operation.abortForRestart?.();
});
}
for (const waiters of replyRunState?.waitersByKey?.values() ?? []) {
for (const waiter of waiters) {
cleanupActions.push(() => {
waiter.finish?.(false);
});
}
}
const cleanupError = runCleanupActions(cleanupActions);
if (cleanupError) {
// oxlint-disable-next-line typescript/only-throw-error -- cleanup hooks may throw their original non-Error value; preserve that test-runner behavior.
throw cleanupError;
}
embeddedRunState?.activeRuns?.clear();
embeddedRunState?.snapshots?.clear();
embeddedRunState?.sessionIdsByKey?.clear();
embeddedRunState?.sessionIdsByFile?.clear();
embeddedRunState?.abandonedRunsBySessionId?.clear();
embeddedRunState?.abandonedRunSessionIdsByKey?.clear();
embeddedRunState?.abandonedRunSessionIdsByFile?.clear();
embeddedRunState?.waiters?.clear();
embeddedRunState?.modelSwitchRequests?.clear();
replyRunState?.activeRunsByKey?.clear();
replyRunState?.activeSessionIdsByKey?.clear();
replyRunState?.activeKeysBySessionId?.clear();
replyRunState?.waitKeysBySessionId?.clear();
replyRunState?.waitersByKey?.clear();
}
export default class OpenClawNonIsolatedRunner extends TestRunner {
override onCollectStart(file: RunnerTestFile) {
super.onCollectStart(file);
restoreRealTimers();
restoreNativeTimerGlobals();
restoreSharedTestHomeAfterEnvUnstub(getSharedTestHome());
const orderLogPath = process.env.OPENCLAW_VITEST_FILE_ORDER_LOG?.trim();
if (orderLogPath) {
fs.appendFileSync(orderLogPath, `START ${file.filepath}\n`);
}
}
override async onBeforeRunTask(test: RunnerTask) {
restoreRealTimers();
restoreNativeTimerGlobals();
await super.onBeforeRunTask(test);
}
override onBeforeTryTask(test: RunnerTask) {
restoreRealTimers();
restoreNativeTimerGlobals();
super.onBeforeTryTask(test);
}
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.
restoreMocksThenRealTimers();
vi.unstubAllGlobals();
const testHome = getSharedTestHome();
vi.unstubAllEnvs();
restoreSharedTestHomeAfterEnvUnstub(testHome);
vi.clearAllMocks();
resetOpenClawGlobalRunState();
vi.resetModules();
const internals = this as unknown as TestRunnerInternals;
internals.moduleRunner?.mocker?.reset?.();
resetEvaluatedModules(internals.workerState.evaluatedModules as EvaluatedModules, true);
}
}