





















@@ -1,5 +1,7 @@
11import { spawnSync } from "node:child_process";
22import fs from "node:fs";
3+import { createServer as createHttpServer, type Server as HttpServer } from "node:http";
4+import { createServer as createNetServer, type Server as NetServer, type Socket } from "node:net";
35import os from "node:os";
46import path from "node:path";
57import { pathToFileURL } from "node:url";
@@ -93,6 +95,37 @@ function runRuntimeSmoke(root: string, args: string[]) {
9395});
9496}
959798+async function listenOnLoopback(server: HttpServer | NetServer): Promise<number> {
99+return new Promise((resolve, reject) => {
100+const onError = (error: Error) => {
101+server.off("error", onError);
102+reject(error);
103+};
104+server.once("error", onError);
105+server.listen(0, "127.0.0.1", () => {
106+server.off("error", onError);
107+const address = server.address();
108+if (!address || typeof address === "string") {
109+reject(new Error("server did not bind to a TCP port"));
110+return;
111+}
112+resolve(address.port);
113+});
114+});
115+}
116+117+async function closeServer(server: HttpServer | NetServer): Promise<void> {
118+await new Promise<void>((resolve, reject) => {
119+server.close((error?: Error) => {
120+if (error) {
121+reject(error);
122+return;
123+}
124+resolve();
125+});
126+});
127+}
128+96129afterEach(() => {
97130for (const dir of tempDirs.splice(0)) {
98131fs.rmSync(dir, { force: true, recursive: true });
@@ -135,6 +168,47 @@ describe("bundled plugin install/uninstall probe", () => {
135168expect(Date.now() - startedAt).toBeLessThan(2_500);
136169});
137170171+it("accepts successful runtime HTTP probes", async () => {
172+const runtimeSmoke = await import(pathToFileURL(runtimeSmokePath).href);
173+const server = createHttpServer((_request, response) => {
174+response.writeHead(204);
175+response.end();
176+});
177+178+try {
179+const port = await listenOnLoopback(server);
180+181+await expect(runtimeSmoke.httpOk(port, "/healthz", { timeoutMs: 1000 })).resolves.toBe(true);
182+} finally {
183+await closeServer(server);
184+}
185+});
186+187+it("bounds stalled runtime HTTP probes", async () => {
188+const runtimeSmoke = await import(pathToFileURL(runtimeSmokePath).href);
189+const sockets = new Set<Socket>();
190+const server = createNetServer((socket) => {
191+sockets.add(socket);
192+socket.on("close", () => {
193+sockets.delete(socket);
194+});
195+});
196+197+try {
198+const port = await listenOnLoopback(server);
199+const startedAt = Date.now();
200+201+await expect(runtimeSmoke.httpOk(port, "/healthz", { timeoutMs: 100 })).resolves.toBe(false);
202+203+expect(Date.now() - startedAt).toBeLessThan(2_500);
204+} finally {
205+for (const socket of sockets) {
206+socket.destroy();
207+}
208+await closeServer(server);
209+}
210+});
211+138212it("creates runtime smoke state with OPENCLAW_HOME at the test home", async () => {
139213const runtimeSmoke = await import(pathToFileURL(runtimeSmokePath).href);
140214const env = runtimeSmoke.createIsolatedStateEnv("runtime-env");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。