mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
test: slim pty fallback coverage
This commit is contained in:
103
src/agents/bash-tools.exec-runtime.pty-fallback.test.ts
Normal file
103
src/agents/bash-tools.exec-runtime.pty-fallback.test.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { afterEach, beforeAll, beforeEach, expect, test, vi } from "vitest";
|
||||
import type { ManagedRun, SpawnInput } from "../process/supervisor/index.js";
|
||||
|
||||
let listRunningSessions: typeof import("./bash-process-registry.js").listRunningSessions;
|
||||
let resetProcessRegistryForTests: typeof import("./bash-process-registry.js").resetProcessRegistryForTests;
|
||||
let runExecProcess: typeof import("./bash-tools.exec-runtime.js").runExecProcess;
|
||||
|
||||
const { supervisorSpawnMock } = vi.hoisted(() => ({
|
||||
supervisorSpawnMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../process/supervisor/index.js", () => ({
|
||||
getProcessSupervisor: () => ({
|
||||
spawn: supervisorSpawnMock,
|
||||
cancel: vi.fn(),
|
||||
cancelScope: vi.fn(),
|
||||
reconcileOrphans: vi.fn(),
|
||||
getRecord: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
function createSuccessfulRun(input: SpawnInput): ManagedRun {
|
||||
input.onStdout?.("ok");
|
||||
return {
|
||||
runId: input.runId ?? "test-run",
|
||||
pid: 1234,
|
||||
startedAtMs: Date.now(),
|
||||
stdin: {
|
||||
write: vi.fn(),
|
||||
end: vi.fn(),
|
||||
destroy: vi.fn(),
|
||||
},
|
||||
cancel: vi.fn(),
|
||||
wait: vi.fn(async () => ({
|
||||
reason: "exit" as const,
|
||||
exitCode: 0,
|
||||
exitSignal: null,
|
||||
durationMs: 1,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
timedOut: false,
|
||||
noOutputTimedOut: false,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
({ listRunningSessions, resetProcessRegistryForTests } =
|
||||
await import("./bash-process-registry.js"));
|
||||
({ runExecProcess } = await import("./bash-tools.exec-runtime.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
supervisorSpawnMock.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetProcessRegistryForTests();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
function runPtyFallback(warnings: string[] = []) {
|
||||
return runExecProcess({
|
||||
command: "printf ok",
|
||||
workdir: process.cwd(),
|
||||
env: {},
|
||||
usePty: true,
|
||||
warnings,
|
||||
maxOutput: 20_000,
|
||||
pendingMaxOutput: 20_000,
|
||||
notifyOnExit: false,
|
||||
timeoutSec: 5,
|
||||
});
|
||||
}
|
||||
|
||||
test("exec falls back when PTY spawn fails", async () => {
|
||||
supervisorSpawnMock
|
||||
.mockRejectedValueOnce(new Error("pty spawn failed"))
|
||||
.mockImplementationOnce(async (input: SpawnInput) => createSuccessfulRun(input));
|
||||
|
||||
const warnings: string[] = [];
|
||||
const handle = await runPtyFallback(warnings);
|
||||
const outcome = await handle.promise;
|
||||
|
||||
expect(outcome.status).toBe("completed");
|
||||
expect(outcome.aggregated).toContain("ok");
|
||||
expect(warnings.join("\n")).toContain("PTY spawn failed");
|
||||
expect(supervisorSpawnMock).toHaveBeenNthCalledWith(1, expect.objectContaining({ mode: "pty" }));
|
||||
expect(supervisorSpawnMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({ mode: "child" }),
|
||||
);
|
||||
});
|
||||
|
||||
test("exec cleans session state when PTY fallback spawn also fails", async () => {
|
||||
supervisorSpawnMock
|
||||
.mockRejectedValueOnce(new Error("pty spawn failed"))
|
||||
.mockRejectedValueOnce(new Error("child fallback failed"));
|
||||
|
||||
await expect(runPtyFallback()).rejects.toThrow("child fallback failed");
|
||||
|
||||
expect(listRunningSessions()).toHaveLength(0);
|
||||
});
|
||||
@@ -1,60 +0,0 @@
|
||||
import { afterEach, beforeAll, beforeEach, expect, test, vi } from "vitest";
|
||||
let createExecTool: typeof import("./bash-tools.exec.js").createExecTool;
|
||||
let listRunningSessions: typeof import("./bash-process-registry.js").listRunningSessions;
|
||||
let resetProcessRegistryForTests: typeof import("./bash-process-registry.js").resetProcessRegistryForTests;
|
||||
|
||||
const { supervisorSpawnMock } = vi.hoisted(() => ({
|
||||
supervisorSpawnMock: vi.fn(),
|
||||
}));
|
||||
|
||||
const makeSupervisor = () => {
|
||||
const noop = vi.fn();
|
||||
return {
|
||||
spawn: (...args: unknown[]) => supervisorSpawnMock(...args),
|
||||
cancel: noop,
|
||||
cancelScope: noop,
|
||||
reconcileOrphans: noop,
|
||||
getRecord: noop,
|
||||
};
|
||||
};
|
||||
|
||||
vi.mock("../process/supervisor/index.js", () => ({
|
||||
getProcessSupervisor: () => makeSupervisor(),
|
||||
}));
|
||||
|
||||
beforeAll(async () => {
|
||||
({ createExecTool } = await import("./bash-tools.exec.js"));
|
||||
({ listRunningSessions, resetProcessRegistryForTests } =
|
||||
await import("./bash-process-registry.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
supervisorSpawnMock.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetProcessRegistryForTests();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("exec cleans session state when PTY fallback spawn also fails", async () => {
|
||||
supervisorSpawnMock
|
||||
.mockRejectedValueOnce(new Error("pty spawn failed"))
|
||||
.mockRejectedValueOnce(new Error("child fallback failed"));
|
||||
|
||||
const tool = createExecTool({
|
||||
allowBackground: false,
|
||||
host: "gateway",
|
||||
security: "full",
|
||||
ask: "off",
|
||||
});
|
||||
|
||||
await expect(
|
||||
tool.execute("toolcall", {
|
||||
command: "echo ok",
|
||||
pty: true,
|
||||
}),
|
||||
).rejects.toThrow("child fallback failed");
|
||||
|
||||
expect(listRunningSessions()).toHaveLength(0);
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
import { afterEach, beforeAll, expect, test, vi } from "vitest";
|
||||
let createExecTool: typeof import("./bash-tools.exec.js").createExecTool;
|
||||
let resetProcessRegistryForTests: typeof import("./bash-process-registry.js").resetProcessRegistryForTests;
|
||||
|
||||
vi.mock("@lydell/node-pty", () => ({
|
||||
spawn: () => {
|
||||
const err = new Error("spawn EBADF");
|
||||
(err as NodeJS.ErrnoException).code = "EBADF";
|
||||
throw err;
|
||||
},
|
||||
}));
|
||||
|
||||
beforeAll(async () => {
|
||||
({ createExecTool } = await import("./bash-tools.exec.js"));
|
||||
({ resetProcessRegistryForTests } = await import("./bash-process-registry.js"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetProcessRegistryForTests();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("exec falls back when PTY spawn fails", async () => {
|
||||
const tool = createExecTool({
|
||||
allowBackground: false,
|
||||
host: "gateway",
|
||||
security: "full",
|
||||
ask: "off",
|
||||
});
|
||||
const result = await tool.execute("toolcall", {
|
||||
command: "printf ok",
|
||||
pty: true,
|
||||
});
|
||||
|
||||
expect(result.details.status).toBe("completed");
|
||||
const text = result.content?.find((item) => item.type === "text")?.text ?? "";
|
||||
expect(text).toContain("ok");
|
||||
expect(text).toContain("PTY spawn failed");
|
||||
});
|
||||
Reference in New Issue
Block a user