



























@@ -0,0 +1,103 @@
1+import { afterEach, beforeAll, beforeEach, expect, test, vi } from "vitest";
2+import type { ManagedRun, SpawnInput } from "../process/supervisor/index.js";
3+4+let listRunningSessions: typeof import("./bash-process-registry.js").listRunningSessions;
5+let resetProcessRegistryForTests: typeof import("./bash-process-registry.js").resetProcessRegistryForTests;
6+let runExecProcess: typeof import("./bash-tools.exec-runtime.js").runExecProcess;
7+8+const { supervisorSpawnMock } = vi.hoisted(() => ({
9+supervisorSpawnMock: vi.fn(),
10+}));
11+12+vi.mock("../process/supervisor/index.js", () => ({
13+getProcessSupervisor: () => ({
14+spawn: supervisorSpawnMock,
15+cancel: vi.fn(),
16+cancelScope: vi.fn(),
17+reconcileOrphans: vi.fn(),
18+getRecord: vi.fn(),
19+}),
20+}));
21+22+function createSuccessfulRun(input: SpawnInput): ManagedRun {
23+input.onStdout?.("ok");
24+return {
25+runId: input.runId ?? "test-run",
26+pid: 1234,
27+startedAtMs: Date.now(),
28+stdin: {
29+write: vi.fn(),
30+end: vi.fn(),
31+destroy: vi.fn(),
32+},
33+cancel: vi.fn(),
34+wait: vi.fn(async () => ({
35+reason: "exit" as const,
36+exitCode: 0,
37+exitSignal: null,
38+durationMs: 1,
39+stdout: "",
40+stderr: "",
41+timedOut: false,
42+noOutputTimedOut: false,
43+})),
44+};
45+}
46+47+beforeAll(async () => {
48+({ listRunningSessions, resetProcessRegistryForTests } =
49+await import("./bash-process-registry.js"));
50+({ runExecProcess } = await import("./bash-tools.exec-runtime.js"));
51+});
52+53+beforeEach(() => {
54+supervisorSpawnMock.mockReset();
55+});
56+57+afterEach(() => {
58+resetProcessRegistryForTests();
59+vi.clearAllMocks();
60+});
61+62+function runPtyFallback(warnings: string[] = []) {
63+return runExecProcess({
64+command: "printf ok",
65+workdir: process.cwd(),
66+env: {},
67+usePty: true,
68+ warnings,
69+maxOutput: 20_000,
70+pendingMaxOutput: 20_000,
71+notifyOnExit: false,
72+timeoutSec: 5,
73+});
74+}
75+76+test("exec falls back when PTY spawn fails", async () => {
77+supervisorSpawnMock
78+.mockRejectedValueOnce(new Error("pty spawn failed"))
79+.mockImplementationOnce(async (input: SpawnInput) => createSuccessfulRun(input));
80+81+const warnings: string[] = [];
82+const handle = await runPtyFallback(warnings);
83+const outcome = await handle.promise;
84+85+expect(outcome.status).toBe("completed");
86+expect(outcome.aggregated).toContain("ok");
87+expect(warnings.join("\n")).toContain("PTY spawn failed");
88+expect(supervisorSpawnMock).toHaveBeenNthCalledWith(1, expect.objectContaining({ mode: "pty" }));
89+expect(supervisorSpawnMock).toHaveBeenNthCalledWith(
90+2,
91+expect.objectContaining({ mode: "child" }),
92+);
93+});
94+95+test("exec cleans session state when PTY fallback spawn also fails", async () => {
96+supervisorSpawnMock
97+.mockRejectedValueOnce(new Error("pty spawn failed"))
98+.mockRejectedValueOnce(new Error("child fallback failed"));
99+100+await expect(runPtyFallback()).rejects.toThrow("child fallback failed");
101+102+expect(listRunningSessions()).toHaveLength(0);
103+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。