From 262baedcf7fa3d66dc35cce7d0253e0a31f0cc0e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 11 Jul 2026 13:28:21 -0700 Subject: [PATCH] test(agents): isolate full-suite shared state (#104682) --- src/agents/bash-tools.exec-runtime.test.ts | 20 ++++++++++++++++++++ test/non-isolated-runner.ts | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/agents/bash-tools.exec-runtime.test.ts b/src/agents/bash-tools.exec-runtime.test.ts index 78035b65b286..d427af778032 100644 --- a/src/agents/bash-tools.exec-runtime.test.ts +++ b/src/agents/bash-tools.exec-runtime.test.ts @@ -4,6 +4,7 @@ * system events, and process lifecycle behavior. */ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import type { GatewayActiveWorkInspectors } from "../infra/gateway-active-work.js"; import type { RunExit } from "../process/supervisor/types.js"; import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../utils/timer-delay.js"; import type { BashSandboxConfig } from "./bash-tools.shared.js"; @@ -80,10 +81,29 @@ function createDeferred() { } function prepareSuspension(requestId: string) { + // This test owns only the background-exec registry. Other process-global + // activity counters may legitimately stay busy in the non-isolated suite. + const inspect: GatewayActiveWorkInspectors = { + getQueueSize: () => 0, + getPendingReplies: () => 0, + getEmbeddedRuns: () => 0, + getBackgroundExecSessions: getActiveBackgroundExecSessionCount, + getCronRuns: () => 0, + getActiveTasks: () => 0, + getTaskBlockers: () => [], + getRootRequests: () => 0, + getSessionAdmissions: () => 0, + getSessionMutations: () => 0, + getChatRuns: () => 0, + getQueuedTurns: () => 0, + getTerminalPersistence: () => 0, + getTerminalSessions: () => 0, + }; return prepareGatewaySuspend({ requestId, pauseScheduling: vi.fn(), resumeScheduling: vi.fn(), + inspect, }); } diff --git a/test/non-isolated-runner.ts b/test/non-isolated-runner.ts index cde179cbafb9..46c69705da5d 100644 --- a/test/non-isolated-runner.ts +++ b/test/non-isolated-runner.ts @@ -22,6 +22,7 @@ type TestRunnerInternals = { 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 DIAGNOSTIC_EVENTS_STATE = Symbol.for("openclaw.diagnosticEvents.state.v1"); const nativeTimerGlobals = { setTimeout: globalThis.setTimeout, clearTimeout: globalThis.clearTimeout, @@ -134,6 +135,13 @@ type ReplyRunStateForTest = { waitersByKey?: Map>; }; +type DiagnosticEventsStateForTest = { + listeners?: Set; + trustedListeners?: Set; + toolExecutionListeners?: Set; + asyncQueue?: unknown[]; +}; + function runCleanupActions(actions: CleanupAction[]): unknown { let firstError: unknown; for (const action of actions) { @@ -207,6 +215,18 @@ function resetOpenClawGlobalRunState(): void { replyRunState?.waitersByKey?.clear(); } +function resetOpenClawGlobalDiagnosticState(): void { + const globalStore = globalThis as Record; + const state = globalStore[DIAGNOSTIC_EVENTS_STATE] as DiagnosticEventsStateForTest | undefined; + // The dispatcher intentionally survives module reloads. Mirror isolate mode + // without duplicating its private state defaults in the test runner. + state?.listeners?.clear(); + state?.trustedListeners?.clear(); + state?.toolExecutionListeners?.clear(); + state?.asyncQueue?.splice(0); + Reflect.deleteProperty(globalStore, DIAGNOSTIC_EVENTS_STATE); +} + export default class OpenClawNonIsolatedRunner extends TestRunner { override onCollectStart(file: RunnerTestFile) { super.onCollectStart(file); @@ -252,6 +272,7 @@ export default class OpenClawNonIsolatedRunner extends TestRunner { restoreSharedTestHomeAfterEnvUnstub(testHome); vi.clearAllMocks(); resetOpenClawGlobalRunState(); + resetOpenClawGlobalDiagnosticState(); vi.resetModules(); const internals = this as unknown as TestRunnerInternals; internals.moduleRunner?.mocker?.reset?.();