Skip to content

Commit d58f864

Browse files
committed
fix(e2e): bound HTTP readiness probes
1 parent a4e0b6e commit d58f864

2 files changed

Lines changed: 69 additions & 5 deletions

File tree

scripts/lib/openclaw-e2e-instance.sh

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,12 @@ openclaw_e2e_terminate_gateways() {
287287
}
288288
openclaw_e2e_start_mock_openai() { MOCK_PORT="$1" node scripts/e2e/mock-openai-server.mjs >"$2" 2>&1 & printf '%s\n' "$!"; }
289289
openclaw_e2e_wait_mock_openai() {
290-
local port="$1" attempts="${2:-80}" _
291-
local probe="fetch('http://127.0.0.1:' + process.argv[1] + '/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
290+
local port="$1" attempts="${2:-80}" timeout_ms="${3:-400}" _
292291
for _ in $(seq 1 "$attempts"); do
293-
node -e "$probe" "$port" && return 0
292+
openclaw_e2e_probe_http "http://127.0.0.1:${port}/health" ok "$timeout_ms" && return 0
294293
sleep 0.1
295294
done
296-
node -e "$probe" "$port"
295+
openclaw_e2e_probe_http "http://127.0.0.1:${port}/health" ok "$timeout_ms"
297296
}
298297
openclaw_e2e_start_gateway() { node "$1" gateway --port "$2" --bind loopback --allow-unconfigured >"$3" 2>&1 & printf '%s\n' "$!"; }
299298
openclaw_e2e_exec_gateway() { exec node "$1" gateway --port "$2" --bind "${3:-loopback}" --allow-unconfigured >"$4" 2>&1; }
@@ -322,8 +321,27 @@ openclaw_e2e_probe_tcp() {
322321
socket.on("error", () => { clearTimeout(timeout); process.exit(1); });
323322
' "$1" "$2" "${3:-400}"
324323
}
324+
openclaw_e2e_probe_http() {
325+
node --input-type=module -e '
326+
const expected = process.argv[2] ?? "ok";
327+
const timeoutMs = Number(process.argv[3] ?? 400);
328+
const controller = new AbortController();
329+
const timer = setTimeout(() => controller.abort(), timeoutMs);
330+
let exitCode = 1;
331+
try {
332+
const response = await fetch(process.argv[1], { signal: controller.signal });
333+
const passed = expected === "ok" ? response.ok : response.status === Number(expected);
334+
exitCode = passed ? 0 : 1;
335+
} catch {
336+
exitCode = 1;
337+
} finally {
338+
clearTimeout(timer);
339+
}
340+
process.exit(exitCode);
341+
' "$1" "${2:-ok}" "${3:-400}"
342+
}
325343
openclaw_e2e_probe_http_status() {
326-
node -e 'fetch(process.argv[1]).then(r=>process.exit(r.status===Number(process.argv[2])?0:1)).catch(()=>process.exit(1))' "$1" "${2:-200}"
344+
openclaw_e2e_probe_http "$1" "${2:-200}" "${3:-400}"
327345
}
328346
openclaw_e2e_assert_file() { [ -f "$1" ] || { echo "Missing file: $1"; exit 1; }; }
329347
openclaw_e2e_assert_dir() { [ -d "$1" ] || { echo "Missing dir: $1"; exit 1; }; }

test/scripts/openclaw-e2e-instance.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,52 @@ describe("scripts/lib/openclaw-e2e-instance.sh", () => {
322322
}
323323
});
324324

325+
it("bounds HTTP readiness probes when a server accepts connections but never responds", () => {
326+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-e2e-http-probe-"));
327+
try {
328+
const portPath = path.join(tempDir, "port.txt");
329+
const serverPath = path.join(tempDir, "stalling-server.cjs");
330+
fs.writeFileSync(
331+
serverPath,
332+
[
333+
"const fs = require('node:fs');",
334+
"const net = require('node:net');",
335+
"const server = net.createServer((socket) => socket.on('data', () => {}));",
336+
"server.listen(0, '127.0.0.1', () => {",
337+
" fs.writeFileSync(process.argv[2], String(server.address().port));",
338+
"});",
339+
"process.on('SIGTERM', () => server.close(() => process.exit(0)));",
340+
"",
341+
].join("\n"),
342+
);
343+
344+
const startedAt = Date.now();
345+
const result = spawnSync(
346+
"/bin/bash",
347+
[
348+
"-c",
349+
[
350+
"set -euo pipefail",
351+
`${shellQuote(process.execPath)} ${shellQuote(serverPath)} ${shellQuote(portPath)} & server_pid=$!`,
352+
'trap \'kill "$server_pid" 2>/dev/null || true; wait "$server_pid" 2>/dev/null || true\' EXIT',
353+
`for _ in $(seq 1 50); do [ -s ${shellQuote(portPath)} ] && break; sleep 0.02; done`,
354+
`port="$(cat ${shellQuote(portPath)})"`,
355+
`source ${shellQuote(helperPath)}`,
356+
'openclaw_e2e_probe_http_status "http://127.0.0.1:${port}/health" 200 100',
357+
].join("; "),
358+
],
359+
{ encoding: "utf8", timeout: 3_000 },
360+
);
361+
const elapsedMs = Date.now() - startedAt;
362+
363+
expect(result.error).toBeUndefined();
364+
expect(result.status).not.toBe(0);
365+
expect(elapsedMs).toBeLessThan(2_500);
366+
} finally {
367+
fs.rmSync(tempDir, { force: true, recursive: true });
368+
}
369+
});
370+
325371
it("wraps logged OpenClaw E2E commands with the configured timeout", () => {
326372
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-e2e-instance-run-logged-"));
327373
const logLabel = path.basename(tempDir);

0 commit comments

Comments
 (0)