mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-07 10:34:44 +00:00
fix(e2e): preserve bun smoke timeout grace
This commit is contained in:
@@ -35,6 +35,31 @@ const signalChild = (child, signal) => {
|
||||
}
|
||||
};
|
||||
|
||||
const processGroupAlive = (child) => {
|
||||
if (process.platform === "win32" || !child.pid) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
process.kill(-child.pid, 0);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return error?.code === "EPERM";
|
||||
}
|
||||
};
|
||||
|
||||
const waitForProcessGroupExit = async (child, timeout) => {
|
||||
const deadlineAt = Date.now() + timeout;
|
||||
while (Date.now() < deadlineAt) {
|
||||
if (!processGroupAlive(child)) {
|
||||
return true;
|
||||
}
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 25);
|
||||
});
|
||||
}
|
||||
return !processGroupAlive(child);
|
||||
};
|
||||
|
||||
const runWithTimeout = async (timeout, command, commandArgs) => {
|
||||
const killGrace = parsePositiveNumber(
|
||||
process.env.OPENCLAW_BUN_GLOBAL_SMOKE_TIMEOUT_KILL_GRACE_MS ??
|
||||
@@ -48,6 +73,7 @@ const runWithTimeout = async (timeout, command, commandArgs) => {
|
||||
});
|
||||
let timedOut = false;
|
||||
let killTimer;
|
||||
let killDeadlineAt = 0;
|
||||
|
||||
child.stdout.setEncoding("utf8");
|
||||
child.stderr.setEncoding("utf8");
|
||||
@@ -57,6 +83,7 @@ const runWithTimeout = async (timeout, command, commandArgs) => {
|
||||
const timeoutTimer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
signalChild(child, "SIGTERM");
|
||||
killDeadlineAt = Date.now() + killGrace;
|
||||
killTimer = setTimeout(() => signalChild(child, "SIGKILL"), killGrace);
|
||||
killTimer.unref();
|
||||
}, timeout);
|
||||
@@ -71,11 +98,20 @@ const runWithTimeout = async (timeout, command, commandArgs) => {
|
||||
});
|
||||
|
||||
clearTimeout(timeoutTimer);
|
||||
clearTimeout(killTimer);
|
||||
if (timedOut) {
|
||||
const remainingGraceMs = Math.max(0, killDeadlineAt - Date.now());
|
||||
if (remainingGraceMs > 0) {
|
||||
await waitForProcessGroupExit(child, remainingGraceMs);
|
||||
}
|
||||
if (processGroupAlive(child)) {
|
||||
signalChild(child, "SIGKILL");
|
||||
await waitForProcessGroupExit(child, 100);
|
||||
}
|
||||
clearTimeout(killTimer);
|
||||
console.error(`command timed out after ${timeout}ms: ${command}`);
|
||||
process.exit(1);
|
||||
}
|
||||
clearTimeout(killTimer);
|
||||
if (result.error) {
|
||||
console.error(`command failed: ${command}: ${result.error.message}`);
|
||||
process.exit(1);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Test Install Sh Docker tests cover test install sh docker script behavior.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { runInNewContext } from "node:vm";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createTempDirTracker } from "../helpers/temp-dir.js";
|
||||
|
||||
const SCRIPT_PATH = "scripts/test-install-sh-docker.sh";
|
||||
const INSTALL_E2E_DOCKER_PATH = "scripts/test-install-sh-e2e-docker.sh";
|
||||
@@ -17,6 +19,11 @@ const BUN_GLOBAL_ASSERTIONS_PATH = "scripts/e2e/lib/bun-global-install/assertion
|
||||
const INSTALL_SMOKE_WORKFLOW_PATH = ".github/workflows/install-smoke.yml";
|
||||
const RELEASE_CHECKS_WORKFLOW_PATH = ".github/workflows/openclaw-release-checks.yml";
|
||||
const LIVE_E2E_WORKFLOW_PATH = ".github/workflows/openclaw-live-and-e2e-checks-reusable.yml";
|
||||
const tempDirs = createTempDirTracker();
|
||||
|
||||
afterEach(() => {
|
||||
tempDirs.cleanup();
|
||||
});
|
||||
|
||||
class ScriptExit extends Error {
|
||||
constructor(readonly status: number) {
|
||||
@@ -757,6 +764,54 @@ describe("bun global install smoke", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.runIf(process.platform !== "win32" && existsSync("/usr/bin/time"))(
|
||||
"preserves Bun global timeout kill grace after the leader exits",
|
||||
() => {
|
||||
const tempDir = tempDirs.make("openclaw-bun-global-timeout-grace-");
|
||||
const readyPath = path.join(tempDir, "ready");
|
||||
const drainedPath = path.join(tempDir, "drained");
|
||||
const childScript = [
|
||||
"const fs = require('node:fs');",
|
||||
"process.on('SIGTERM', () => {",
|
||||
" setTimeout(() => {",
|
||||
" fs.writeFileSync(process.argv[2], 'drained');",
|
||||
" process.exit(0);",
|
||||
" }, 50);",
|
||||
"});",
|
||||
"fs.writeFileSync(process.argv[1], 'ready');",
|
||||
"setInterval(() => {}, 1000);",
|
||||
].join("\n");
|
||||
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
BUN_GLOBAL_ASSERTIONS_PATH,
|
||||
"run-with-timeout",
|
||||
"500",
|
||||
"/usr/bin/time",
|
||||
process.execPath,
|
||||
"-e",
|
||||
childScript,
|
||||
readyPath,
|
||||
drainedPath,
|
||||
],
|
||||
{
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_BUN_GLOBAL_SMOKE_TIMEOUT_KILL_GRACE_MS: "1000",
|
||||
},
|
||||
timeout: 5_000,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain("command timed out after 500ms: /usr/bin/time");
|
||||
expect(readFileSync(readyPath, "utf8")).toBe("ready");
|
||||
expect(readFileSync(drainedPath, "utf8")).toBe("drained");
|
||||
},
|
||||
);
|
||||
|
||||
it("gates workflow Bun install smoke to scheduled and release-check runs", () => {
|
||||
const workflow = readFileSync(INSTALL_SMOKE_WORKFLOW_PATH, "utf8");
|
||||
const releaseChecks = readFileSync(RELEASE_CHECKS_WORKFLOW_PATH, "utf8");
|
||||
|
||||
Reference in New Issue
Block a user