Skip to content

Commit 6433b82

Browse files
fix(qa-lab): surface gateway child stream failures (#102104)
Co-authored-by: Peter Steinberger <[email protected]>
1 parent fd34019 commit 6433b82

4 files changed

Lines changed: 258 additions & 55 deletions

File tree

extensions/qa-lab/src/gateway-child.test.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { spawn } from "node:child_process";
12
// Qa Lab tests cover gateway child plugin behavior.
2-
import { EventEmitter } from "node:events";
3+
import { EventEmitter, once } from "node:events";
34
import { lstat, mkdir, mkdtemp, readFile, readdir, rm, symlink, writeFile } from "node:fs/promises";
45
import os from "node:os";
56
import path from "node:path";
@@ -142,6 +143,51 @@ describe("runQaGatewayCliCommand", () => {
142143
}),
143144
).rejects.toThrow("OpenClaw CLI exited 7: fixture failure");
144145
});
146+
147+
it.each(["stdout", "stderr"] as const)(
148+
"rejects and stops the CLI child when its %s pipe fails",
149+
async (streamName) => {
150+
const child = spawn(process.execPath, ["--eval", "setInterval(() => {}, 1000)"], {
151+
stdio: ["ignore", "pipe", "pipe"],
152+
});
153+
const close = once(child, "close");
154+
const result = testing.readQaGatewayCliCommand(child);
155+
const message = `synthetic ${streamName} read failure`;
156+
157+
child[streamName]?.destroy(new Error(message));
158+
159+
await expect(result).rejects.toThrow(
160+
`qa gateway cli ${streamName} stream failed: ${message}`,
161+
);
162+
await close;
163+
},
164+
);
165+
});
166+
167+
describe("monitorQaGatewayChildFailure", () => {
168+
it("records the first pipe failure and stops the detached Gateway child", async () => {
169+
const child = spawn(process.execPath, ["--eval", "setInterval(() => {}, 1000)"], {
170+
detached: process.platform !== "win32",
171+
stdio: ["ignore", "pipe", "pipe"],
172+
});
173+
const close = once(child, "close");
174+
const output = testing.createQaGatewayChildLogCollector();
175+
const getFailure = testing.monitorQaGatewayChildFailure(child, output);
176+
const error = new Error("synthetic gateway stdout read failure");
177+
178+
child.stdout?.destroy(error);
179+
child.stderr?.destroy(new Error("later stderr read failure"));
180+
181+
await vi.waitFor(() => expect(getFailure()).toEqual({ source: "stdout", error }));
182+
await close;
183+
expect(output.text()).toContain(
184+
"gateway child stdout stream failed: synthetic gateway stdout read failure",
185+
);
186+
expect(output.text()).not.toContain("later stderr read failure");
187+
expect(() => testing.throwQaGatewayChildFailure(getFailure, () => output.text())).toThrow(
188+
"gateway child stdout stream failed: synthetic gateway stdout read failure",
189+
);
190+
});
145191
});
146192

147193
describe("Gateway child fixture helpers", () => {

0 commit comments

Comments
 (0)