diff --git a/test/e2e/qa-lab/runtime/qa-otel-smoke-runtime.ts b/test/e2e/qa-lab/runtime/qa-otel-smoke-runtime.ts index e7212556259..68631c08e12 100644 --- a/test/e2e/qa-lab/runtime/qa-otel-smoke-runtime.ts +++ b/test/e2e/qa-lab/runtime/qa-otel-smoke-runtime.ts @@ -10,6 +10,7 @@ import { tmpdir } from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { gunzipSync } from "node:zlib"; +import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; import { stripLeadingPackageManagerSeparator } from "../../../../scripts/lib/arg-utils.mjs"; import { resolveWindowsTaskkillPath } from "../../../../scripts/lib/windows-taskkill.mjs"; @@ -1275,12 +1276,13 @@ async function waitForChild( timeoutMs = QA_SUITE_TIMEOUT_MS, killGraceMs = QA_SUITE_KILL_GRACE_MS, ): Promise { + const resolvedTimeoutMs = resolveTimerTimeoutMs(timeoutMs, QA_SUITE_TIMEOUT_MS); const childExit = new Promise((resolve) => { child.once("close", (code) => resolve(code ?? 1)); }); let timeoutHandle: NodeJS.Timeout | undefined; const timeout = new Promise<"timeout">((resolve) => { - timeoutHandle = setTimeout(() => resolve("timeout"), timeoutMs); + timeoutHandle = setTimeout(() => resolve("timeout"), resolvedTimeoutMs); timeoutHandle.unref(); }); const result = await Promise.race([childExit, timeout]).finally(() => { @@ -1298,7 +1300,7 @@ async function waitForChild( terminateChildTree(child, "SIGKILL", cleanupPids); await waitForProcessTreeExit(child, 1000, cleanupPids); } - throw new Error(`openclaw qa suite timed out after ${timeoutMs}ms`); + throw new Error(`openclaw qa suite timed out after ${resolvedTimeoutMs}ms`); } function collectChildProcessTreePids(child: ChildProcess): number[] { diff --git a/test/e2e/qa-lab/runtime/qa-otel-smoke.e2e.test.ts b/test/e2e/qa-lab/runtime/qa-otel-smoke.e2e.test.ts index 981db576227..dd01257c182 100644 --- a/test/e2e/qa-lab/runtime/qa-otel-smoke.e2e.test.ts +++ b/test/e2e/qa-lab/runtime/qa-otel-smoke.e2e.test.ts @@ -7,6 +7,7 @@ import os from "node:os"; import path from "node:path"; import { setTimeout as delay } from "node:timers/promises"; import { gzipSync } from "node:zlib"; +import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion"; import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { resolveWindowsTaskkillPath } from "../../../../scripts/lib/windows-taskkill.mjs"; import { testing } from "./qa-otel-smoke-runtime.js"; @@ -662,6 +663,16 @@ describe("qa-otel-smoke receiver bounds", () => { } }); + it("clamps oversized QA suite child timers before scheduling", async () => { + const child = spawn( + process.execPath, + ["--input-type=module", "--eval", "setTimeout(() => process.exit(0), 25);"], + { stdio: "ignore" }, + ); + + await expect(testing.waitForChild(child, MAX_TIMER_TIMEOUT_MS + 1, 100)).resolves.toBe(0); + }); + it("uses taskkill for Windows QA suite timeout cleanup", () => { const kill = vi.fn(); const runTaskkill = vi.fn(() => ({ status: 0 }));