refactor(agents): privatize runtime test seams (#108295)

Shrinks the dead-export baseline from 156 to 151 entries.
This commit is contained in:
Peter Steinberger
2026-07-15 05:36:35 -07:00
committed by GitHub
parent 69ef3ca980
commit 2808507c3b
19 changed files with 138 additions and 66 deletions

View File

@@ -36,15 +36,11 @@ export const KNIP_UNUSED_EXPORT_BASELINE = [
"src/agents/modes/interactive/theme/theme.ts: setTheme",
"src/agents/openai-completions-compat.ts: resolveOpenAICompletionsCompatDefaults",
"src/agents/openai-transport-stream.ts: testing",
"src/agents/run-wait.ts: testing",
"src/agents/session-model-auto-revert.ts: reconcileAgentPatchedSessionModel",
"src/agents/session-suspension.ts: testing",
"src/agents/session-write-lock.ts: resetSessionWriteLockStateForTest",
"src/agents/session-write-lock.ts: testing",
"src/agents/sessions/tools/bash.ts: resolveBashTimeoutMs",
"src/agents/tool-search.ts: testing",
"src/agents/tool-search.ts: ToolSearchCatalogSession",
"src/agents/tools/agent-step.ts: testing",
"src/agents/tools/image-generate-tool.ts: resolveImageGenerationModelConfigForTool",
"src/agents/tools/image-tool.ts: resolveImageModelConfigForTool",
"src/agents/tools/image-tool.ts: testing",
@@ -54,7 +50,6 @@ export const KNIP_UNUSED_EXPORT_BASELINE = [
"src/agents/tools/video-generate-tool.ts: resolveVideoGenerationModelConfigForTool",
"src/agents/tools/web-fetch.ts: sanitizeWebFetchUrl",
"src/agents/utils/tools-manager.ts: testing",
"src/agents/worktrees/run-lease.ts: testing",
"src/auto-reply/reply/abort.ts: testing",
"src/auto-reply/reply/acp-reset-target.ts: testing",
"src/auto-reply/reply/agent-runner-context-recovery.ts: computeContextAwareReserveTokensFloor",

View File

@@ -22,10 +22,8 @@ import {
SessionWriteLockStaleError,
SessionWriteLockTimeoutError,
} from "../../session-write-lock-error.js";
import {
acquireSessionWriteLock,
resetSessionWriteLockStateForTest,
} from "../../session-write-lock.js";
import { acquireSessionWriteLock } from "../../session-write-lock.js";
import { resetSessionWriteLockStateForTest } from "../../session-write-lock.test-support.js";
import { SessionManager } from "../../sessions/session-manager.js";
import {
acquireEmbeddedAttemptSessionFileOwner,

View File

@@ -42,7 +42,7 @@ import {
testing as embeddedRunsTesting,
setActiveEmbeddedRun,
} from "./embedded-agent-runner/runs.js";
import { testing as agentStepTesting } from "./tools/agent-step.js";
import { testing as agentStepTesting } from "./tools/agent-step.test-support.js";
import { createSessionsHistoryTool } from "./tools/sessions-history-tool.js";
import { createSessionsListTool } from "./tools/sessions-list-tool.js";
import { testing as sessionsResolutionTesting } from "./tools/sessions-resolution.js";

View File

@@ -15,7 +15,6 @@ vi.mock("../gateway/call.js", () => ({
}));
import {
testing,
isRecoverableAgentWaitError,
readLatestAssistantReply,
readLatestAssistantReplySnapshot,
@@ -77,9 +76,6 @@ function expectAgentWaitRequest(
describe("readLatestAssistantReply", () => {
beforeEach(() => {
callGatewayMock.mockReset();
testing.setDepsForTest({
callGateway: async (opts) => await callGatewayMock(opts),
});
});
it("returns the most recent assistant message when compaction markers trail history", async () => {
@@ -279,9 +275,6 @@ describe("readLatestAssistantReply", () => {
describe("waitForAgentRun", () => {
beforeEach(() => {
callGatewayMock.mockReset();
testing.setDepsForTest({
callGateway: async (opts) => await callGatewayMock(opts),
});
});
it("maps gateway timeouts to timeout status", async () => {
@@ -465,9 +458,6 @@ describe("waitForAgentRun", () => {
describe("waitForAgentRunAndReadUpdatedAssistantReply", () => {
beforeEach(() => {
callGatewayMock.mockReset();
testing.setDepsForTest({
callGateway: async (opts) => await callGatewayMock(opts),
});
});
it("returns undefined when the latest assistant fingerprint matches the baseline", async () => {
@@ -934,9 +924,6 @@ describe("waitForAgentRunAndReadUpdatedAssistantReply", () => {
describe("waitForAgentRunsToDrain", () => {
beforeEach(() => {
callGatewayMock.mockReset();
testing.setDepsForTest({
callGateway: async (opts) => await callGatewayMock(opts),
});
});
it("waits across rounds until descendant runs stop changing", async () => {

View File

@@ -34,14 +34,6 @@ import { extractAssistantText, stripToolMessages } from "./tools/chat-history-te
type GatewayCaller = typeof callGateway;
const defaultRunWaitDeps = {
callGateway,
};
let runWaitDeps: {
callGateway: GatewayCaller;
} = defaultRunWaitDeps;
function resolveRunWaitTimeoutMs(value: number | undefined): number {
return clampTimerTimeoutMs(parseFiniteNumber(value) ?? 1) ?? 1;
}
@@ -339,7 +331,7 @@ export async function readLatestAssistantReplySnapshot(params: {
stopAtTranscriptArtifact?: boolean;
callGateway?: GatewayCaller;
}): Promise<AssistantReplySnapshot> {
const history = await (params.callGateway ?? runWaitDeps.callGateway)<{
const history = await (params.callGateway ?? callGateway)<{
messages: Array<unknown>;
}>({
method: "chat.history",
@@ -374,7 +366,7 @@ export async function waitForAgentRun(params: {
}): Promise<AgentWaitResult> {
const timeoutMs = resolveRunWaitTimeoutMs(params.timeoutMs);
try {
const wait = await (params.callGateway ?? runWaitDeps.callGateway)({
const wait = await (params.callGateway ?? callGateway)({
method: "agent.wait",
params: {
runId: params.runId,
@@ -469,15 +461,3 @@ export async function waitForAgentRunsToDrain(params: {
deadlineAtMs,
};
}
/** Test-only dependency injection for gateway calls. */
export const testing = {
setDepsForTest(overrides?: Partial<{ callGateway: GatewayCaller }>) {
runWaitDeps = overrides
? {
...defaultRunWaitDeps,
...overrides,
}
: defaultRunWaitDeps;
},
};

View File

@@ -0,0 +1,44 @@
import type { SessionLockInspection } from "./session-write-lock.js";
import "./session-write-lock.js";
type CleanupSignal = "SIGINT" | "SIGTERM" | "SIGQUIT" | "SIGABRT";
type LockFilePayload = {
pid?: number;
createdAt?: string;
starttime?: number;
maxHoldMs?: number;
};
type LockInspectionDetails = Pick<
SessionLockInspection,
"pid" | "pidAlive" | "createdAt" | "ageMs" | "stale" | "staleReasons"
>;
type SessionWriteLockTestApi = {
resetSessionWriteLockStateForTest(): void;
testing: {
cleanupSignals: CleanupSignal[];
handleTerminationSignal(signal: CleanupSignal): void;
inspectLockPayloadForTest(
payload: LockFilePayload | null,
staleMs: number,
nowMs: number,
opts?: { respectMaxHold?: boolean },
): LockInspectionDetails;
releaseAllLocksSync(): void;
runLockWatchdogCheck(nowMs?: number): Promise<number>;
resolveRemainingAcquireTimeoutMs(timeoutMs: number, startedAtMs: number, nowMs: number): number;
setProcessStartTimeResolverForTest(resolver: ((pid: number) => number | null) | null): void;
};
};
function getTestApi(): SessionWriteLockTestApi {
return (globalThis as Record<PropertyKey, unknown>)[
Symbol.for("openclaw.sessionWriteLockTestApi")
] as SessionWriteLockTestApi;
}
export function resetSessionWriteLockStateForTest(): void {
getTestApi().resetSessionWriteLockStateForTest();
}
export const testing = getTestApi().testing;

View File

@@ -10,10 +10,10 @@ import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { SessionWriteLockStaleError } from "./session-write-lock-error.js";
const FAKE_STARTTIME = 12345;
let testing: typeof import("./session-write-lock.js").testing;
let testing: typeof import("./session-write-lock.test-support.js").testing;
let acquireSessionWriteLock: typeof import("./session-write-lock.js").acquireSessionWriteLock;
let cleanStaleLockFiles: typeof import("./session-write-lock.js").cleanStaleLockFiles;
let resetSessionWriteLockStateForTest: typeof import("./session-write-lock.js").resetSessionWriteLockStateForTest;
let resetSessionWriteLockStateForTest: typeof import("./session-write-lock.test-support.js").resetSessionWriteLockStateForTest;
let resolveSessionLockMaxHoldFromTimeout: typeof import("./session-write-lock.js").resolveSessionLockMaxHoldFromTimeout;
let resolveSessionWriteLockAcquireTimeoutMs: typeof import("./session-write-lock.js").resolveSessionWriteLockAcquireTimeoutMs;
let resolveSessionWriteLockOptions: typeof import("./session-write-lock.js").resolveSessionWriteLockOptions;
@@ -163,14 +163,14 @@ async function expectActiveInProcessLockIsNotReclaimed(params?: {
describe("acquireSessionWriteLock", () => {
beforeAll(async () => {
({
testing,
acquireSessionWriteLock,
cleanStaleLockFiles,
resetSessionWriteLockStateForTest,
resolveSessionLockMaxHoldFromTimeout,
resolveSessionWriteLockAcquireTimeoutMs,
resolveSessionWriteLockOptions,
} = await import("./session-write-lock.js"));
({ testing, resetSessionWriteLockStateForTest } =
await import("./session-write-lock.test-support.js"));
});
afterEach(() => {

View File

@@ -1077,7 +1077,7 @@ export async function acquireSessionWriteLock(params: {
}
}
export const testing = {
const testing = {
cleanupSignals: [...CLEANUP_SIGNALS],
handleTerminationSignal,
inspectLockPayloadForTest: inspectLockPayload,
@@ -1095,10 +1095,17 @@ export async function drainSessionWriteLockStateForTest(): Promise<void> {
unregisterCleanupHandlers();
}
export function resetSessionWriteLockStateForTest(): void {
function resetSessionWriteLockStateForTest(): void {
releaseAllLocksSync();
stopWatchdogTimer();
unregisterCleanupHandlers();
resolveProcessStartTimeForLock = getProcessStartTime;
}
if (process.env.VITEST || process.env.NODE_ENV === "test") {
(globalThis as Record<PropertyKey, unknown>)[Symbol.for("openclaw.sessionWriteLockTestApi")] = {
resetSessionWriteLockStateForTest,
testing,
};
}
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */

View File

@@ -0,0 +1,23 @@
import type { callGateway } from "../../gateway/call.js";
import "./agent-step.js";
type AgentCommandRunner = typeof import("../../commands/agent.js").agentCommandFromIngress;
type AgentStepTesting = {
setDepsForTest(
overrides?: Partial<{
agentCommandFromIngress: AgentCommandRunner;
callGateway: typeof callGateway;
}>,
): void;
};
type AgentStepTestApi = {
testing: AgentStepTesting;
};
function getTestApi(): AgentStepTestApi {
return (globalThis as Record<PropertyKey, unknown>)[
Symbol.for("openclaw.agentStepTestApi")
] as AgentStepTestApi;
}
export const testing = getTestApi().testing;

View File

@@ -2,7 +2,8 @@
// MCP runtime retirement after completed nested turns.
import { afterEach, describe, expect, it, vi } from "vitest";
import type { CallGatewayOptions } from "../../gateway/call.js";
import { runAgentStep, testing } from "./agent-step.js";
import { runAgentStep } from "./agent-step.js";
import { testing } from "./agent-step.test-support.js";
const runWaitMocks = vi.hoisted(() => ({
waitForAgentRunAndReadUpdatedAssistantReply: vi.fn(),

View File

@@ -137,7 +137,7 @@ export async function runAgentStep(params: {
}
/** Test-only dependency overrides for gateway and in-process command execution. */
export const testing = {
const testing = {
setDepsForTest(
overrides?: Partial<{
agentCommandFromIngress: AgentCommandRunner;
@@ -152,3 +152,9 @@ export const testing = {
: defaultAgentStepDeps;
},
};
if (process.env.VITEST || process.env.NODE_ENV === "test") {
(globalThis as Record<PropertyKey, unknown>)[Symbol.for("openclaw.agentStepTestApi")] = {
testing,
};
}

View File

@@ -0,0 +1,24 @@
import type { unlockWorktree } from "./git-lock.js";
import type { releaseWorktreeRunLeaseRow } from "./registry.js";
import "./run-lease.js";
type WorktreeRunLeaseTesting = {
setProcessStartTimeResolverForTest(resolver: ((pid: number) => number | null) | null): void;
setDeadPidResolverForTest(resolver: ((pid: number) => boolean) | null): void;
setReleaseRowImplForTest(impl: typeof releaseWorktreeRunLeaseRow | null): void;
setUnlockImplForTest(impl: typeof unlockWorktree | null): void;
drainPendingCleanupsForTest(): Promise<void>;
resetForTest(): void;
};
type WorktreeRunLeaseTestApi = {
testing: WorktreeRunLeaseTesting;
};
function getTestApi(): WorktreeRunLeaseTestApi {
return (globalThis as Record<PropertyKey, unknown>)[
Symbol.for("openclaw.worktreeRunLeaseTestApi")
] as WorktreeRunLeaseTestApi;
}
export const testing = getTestApi().testing;

View File

@@ -13,13 +13,13 @@ import {
releaseWorktreeRunLeaseRow,
} from "./registry.js";
import {
testing as runLeaseTesting,
abortWorktreeRemoval,
acquireWorktreeRunLease,
claimWorktreeRemoval,
hasLiveWorktreeRunLease,
resolveWorktreeIdForPath,
} from "./run-lease.js";
import { testing as runLeaseTesting } from "./run-lease.test-support.js";
import { ManagedWorktreeService } from "./service.js";
const execFileAsync = promisify(execFile);

View File

@@ -321,7 +321,7 @@ export function hasLiveWorktreeRunLease(env: NodeJS.ProcessEnv, worktreeId: stri
return hasLiveWorktreeRunLeaseRow(env, worktreeId, ownerChecks);
}
export const testing = {
const testing = {
setProcessStartTimeResolverForTest(resolver: ((pid: number) => number | null) | null): void {
resolveSelfStartTime = resolver ?? getFileLockProcessStartTime;
ownerChecks = { ...ownerChecks, getProcessStartTime: resolver ?? undefined };
@@ -348,3 +348,9 @@ export const testing = {
unlockWorktreeImpl = unlockWorktree;
},
};
if (process.env.VITEST || process.env.NODE_ENV === "test") {
(globalThis as Record<PropertyKey, unknown>)[Symbol.for("openclaw.worktreeRunLeaseTestApi")] = {
testing,
};
}

View File

@@ -6,7 +6,8 @@ import { promisify } from "node:util";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js";
import { getRegistryWorktree } from "./registry.js";
import { acquireWorktreeRunLease, testing as runLeaseTesting } from "./run-lease.js";
import { acquireWorktreeRunLease } from "./run-lease.js";
import { testing as runLeaseTesting } from "./run-lease.test-support.js";
import { IDLE_GC_MS, ManagedWorktreeService } from "./service.js";
const execFileAsync = promisify(execFile);

View File

@@ -31,8 +31,7 @@ vi.mock("../../gateway/call.js", () => ({
}));
const { listDescendantRunsForRequester } = await import("../../agents/subagent-registry-read.js");
const { testing: runWaitTesting, readLatestAssistantReply } =
await import("../../agents/run-wait.js");
const { readLatestAssistantReply } = await import("../../agents/run-wait.js");
const { callGateway } = await import("../../gateway/call.js");
async function resolveAfterAdvancingTimers<T>(promise: Promise<T>, advanceMs = 100): Promise<T> {
@@ -285,14 +284,10 @@ describe("waitForDescendantSubagentSummary", () => {
vi.mocked(listDescendantRunsForRequester).mockReturnValue([]);
vi.mocked(readLatestAssistantReply).mockResolvedValue(undefined);
vi.mocked(callGateway).mockResolvedValue({ status: "ok" });
runWaitTesting.setDepsForTest({
callGateway: ((opts) => vi.mocked(callGateway)(opts as never)) as typeof callGateway,
});
});
afterEach(() => {
vi.useRealTimers();
runWaitTesting.setDepsForTest();
});
it("returns initialReply immediately when no active descendants and observedActiveDescendants=false", async () => {

View File

@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
import { testing as agentStepTesting } from "../agents/tools/agent-step.js";
import { testing as agentStepTesting } from "../agents/tools/agent-step.test-support.js";
import { runSessionsSendA2AFlow } from "../agents/tools/sessions-send-tool.a2a.js";
import {
loadSessionEntry,

View File

@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { resetSessionWriteLockStateForTest } from "../agents/session-write-lock.js";
import { resetSessionWriteLockStateForTest } from "../agents/session-write-lock.test-support.js";
import { runExclusiveSessionStoreWrite } from "../config/sessions/store-writer.js";
import { clearSessionStoreCacheForTest } from "../config/sessions/store.js";
import { resetFileLockStateForTest } from "../infra/file-lock.js";

View File

@@ -32,7 +32,7 @@ type WorkerCleanupHelpers = {
resetContextWindowCacheForTest: typeof import("../src/agents/context-runtime-state.js").resetContextWindowCacheForTest;
resetFileLockStateForTest: typeof import("../src/infra/file-lock.js").resetFileLockStateForTest;
resetModelsJsonReadyCacheForTest: typeof import("../src/agents/models-config-state.test-support.js").resetModelsJsonReadyCacheForTest;
resetSessionWriteLockStateForTest: typeof import("../src/agents/session-write-lock.js").resetSessionWriteLockStateForTest;
resetSessionWriteLockStateForTest: typeof import("../src/agents/session-write-lock.test-support.js").resetSessionWriteLockStateForTest;
};
type ReplyToModeResolver = NonNullable<
@@ -79,6 +79,9 @@ function loadWorkerCleanupHelpers(): Promise<WorkerCleanupHelpers> {
vi.importActual<typeof import("../src/agents/session-write-lock.js")>(
"../src/agents/session-write-lock.js",
),
vi.importActual<typeof import("../src/agents/session-write-lock.test-support.js")>(
"../src/agents/session-write-lock.test-support.js",
),
vi.importActual<typeof import("../src/config/sessions/store-cache.js")>(
"../src/config/sessions/store-cache.js",
),
@@ -91,6 +94,7 @@ function loadWorkerCleanupHelpers(): Promise<WorkerCleanupHelpers> {
contextRuntimeState,
modelsConfigState,
sessionWriteLock,
sessionWriteLockTestSupport,
sessionStoreCache,
sessionStoreWriterState,
fileLock,
@@ -103,7 +107,8 @@ function loadWorkerCleanupHelpers(): Promise<WorkerCleanupHelpers> {
resetContextWindowCacheForTest: contextRuntimeState.resetContextWindowCacheForTest,
resetFileLockStateForTest: fileLock.resetFileLockStateForTest,
resetModelsJsonReadyCacheForTest: modelsConfigState.resetModelsJsonReadyCacheForTest,
resetSessionWriteLockStateForTest: sessionWriteLock.resetSessionWriteLockStateForTest,
resetSessionWriteLockStateForTest:
sessionWriteLockTestSupport.resetSessionWriteLockStateForTest,
}),
);
return globalState[WORKER_CLEANUP_HELPERS];