fix(e2e): clean plugin fixture servers on timeout

This commit is contained in:
Vincent Koc
2026-06-02 01:17:07 +02:00
parent 42d3acfc99
commit 7c52969d49
3 changed files with 166 additions and 4 deletions
+1 -1
View File
@@ -16,11 +16,11 @@ run_plugins_clawhub_scenario() {
node scripts/e2e/lib/clawhub-fixture-server.cjs plugins "$server_port_file" >"$server_log" 2>&1 &
local server_pid="$!"
echo "$server_pid" >"$server_pid_file"
openclaw_plugins_register_fixture_pid_file "$server_pid_file"
for _ in $(seq 1 100); do
if [[ -s "$server_port_file" ]]; then
export OPENCLAW_CLAWHUB_URL="http://127.0.0.1:$(cat "$server_port_file")"
openclaw_plugins_register_fixture_pid_file "$server_pid_file"
return 0
fi
if ! kill -0 "$server_pid" 2>/dev/null; then
+30 -2
View File
@@ -1,4 +1,6 @@
OPENCLAW_PLUGINS_FIXTURE_PID_FILES=()
OPENCLAW_PLUGINS_FIXTURE_EXIT_TRAP_INSTALLED=0
OPENCLAW_PLUGINS_FIXTURE_PREVIOUS_EXIT_ACTION=""
openclaw_plugins_cleanup_fixture_servers() {
local pid_file
@@ -16,7 +18,33 @@ openclaw_plugins_cleanup_fixture_servers() {
openclaw_plugins_register_fixture_pid_file() {
local pid_file="$1"
OPENCLAW_PLUGINS_FIXTURE_PID_FILES+=("$pid_file")
trap openclaw_plugins_cleanup_fixture_servers EXIT
openclaw_plugins_install_fixture_cleanup_trap
}
openclaw_plugins_install_fixture_cleanup_trap() {
if [[ "${OPENCLAW_PLUGINS_FIXTURE_EXIT_TRAP_INSTALLED:-0}" = "1" ]]; then
return
fi
local existing_trap
existing_trap="$(trap -p EXIT || true)"
if [[ -n "$existing_trap" && "$existing_trap" != *openclaw_plugins_fixture_exit_trap* ]]; then
local existing_action="${existing_trap#trap -- }"
existing_action="${existing_action% EXIT}"
eval "OPENCLAW_PLUGINS_FIXTURE_PREVIOUS_EXIT_ACTION=$existing_action"
fi
OPENCLAW_PLUGINS_FIXTURE_EXIT_TRAP_INSTALLED=1
trap openclaw_plugins_fixture_exit_trap EXIT
}
openclaw_plugins_fixture_exit_trap() {
local status="$?"
openclaw_plugins_cleanup_fixture_servers
if [[ -n "${OPENCLAW_PLUGINS_FIXTURE_PREVIOUS_EXIT_ACTION:-}" ]]; then
eval "$OPENCLAW_PLUGINS_FIXTURE_PREVIOUS_EXIT_ACTION"
fi
exit "$status"
}
record_fixture_plugin_trust() {
@@ -142,11 +170,11 @@ start_npm_fixture_registry() {
node scripts/e2e/lib/plugins/npm-registry-server.mjs "$server_port_file" "$package_name" "$version" "$tarball" "$@" >"$server_log" 2>&1 &
local server_pid="$!"
echo "$server_pid" >"$server_pid_file"
openclaw_plugins_register_fixture_pid_file "$server_pid_file"
for _ in $(seq 1 100); do
if [[ -s "$server_port_file" ]]; then
export NPM_CONFIG_REGISTRY="http://127.0.0.1:$(cat "$server_port_file")"
openclaw_plugins_register_fixture_pid_file "$server_pid_file"
return 0
fi
if ! kill -0 "$server_pid" 2>/dev/null; then
+135 -1
View File
@@ -1,5 +1,5 @@
import { spawn, spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { createServer } from "node:http";
import { tmpdir } from "node:os";
import path from "node:path";
@@ -7,6 +7,10 @@ import { describe, expect, it } from "vitest";
const ASSERTIONS_SCRIPT = "scripts/e2e/lib/plugins/assertions.mjs";
function shellQuote(value: string): string {
return `'${value.replace(/'/gu, `'\\''`)}'`;
}
function writeJson(filePath: string, value: unknown) {
mkdirSync(path.dirname(filePath), { recursive: true });
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
@@ -47,6 +51,43 @@ function runAssertionAsync(args: string[], env: NodeJS.ProcessEnv) {
);
}
function writeFixtureServerShims(binDir: string, pidPath: string): void {
mkdirSync(binDir, { recursive: true });
writeFileSync(
path.join(binDir, "node"),
[
"#!/bin/bash",
'printf "%s\\n" "$$" >"$OPENCLAW_TEST_FIXTURE_SERVER_PID"',
"trap 'exit 0' TERM",
"while true; do /bin/sleep 1; done",
"",
].join("\n"),
);
writeFileSync(path.join(binDir, "sleep"), "#!/bin/bash\nexit 0\n");
chmodSync(path.join(binDir, "node"), 0o755);
chmodSync(path.join(binDir, "sleep"), 0o755);
writeFileSync(pidPath, "");
}
function isProcessAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
function waitForDead(pid: number, timeoutMs = 2_000): void {
const startedAt = Date.now();
while (isProcessAlive(pid)) {
if (Date.now() - startedAt > timeoutMs) {
throw new Error(`pid ${pid} is still alive`);
}
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
}
}
describe("plugins Docker assertions", () => {
it("rejects loose ClawHub preflight limits instead of parsing prefixes", () => {
const timeoutResult = spawnSync(process.execPath, [ASSERTIONS_SCRIPT, "clawhub-preflight"], {
@@ -92,6 +133,99 @@ describe("plugins Docker assertions", () => {
}
});
it("cleans npm fixture registry children when readiness times out", () => {
const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-npm-fixture-cleanup-"));
try {
const binDir = path.join(root, "bin");
const fixtureDir = path.join(root, "fixture");
const cleanupPath = path.join(root, "caller-cleanup");
const pidPath = path.join(root, "server.pid");
mkdirSync(fixtureDir);
writeFixtureServerShims(binDir, pidPath);
const result = spawnSync(
"/bin/bash",
[
"-c",
[
"set -euo pipefail",
"source scripts/e2e/lib/plugins/fixtures.sh",
"set +e",
`( set -e; trap 'printf caller-cleanup > ${shellQuote(cleanupPath)}' EXIT; start_npm_fixture_registry fixture-pkg 1.0.0 ${shellQuote(path.join(root, "fixture.tgz"))} ${shellQuote(fixtureDir)} )`,
'status="$?"',
"set -e",
'[ "$status" != "0" ]',
].join("\n"),
],
{
cwd: process.cwd(),
encoding: "utf8",
env: {
...process.env,
OPENCLAW_TEST_FIXTURE_SERVER_PID: pidPath,
PATH: `${binDir}${path.delimiter}/usr/bin${path.delimiter}/bin`,
},
},
);
expect(result.status, result.stderr || result.stdout).toBe(0);
const pid = Number(readFileSync(pidPath, "utf8"));
expect(Number.isInteger(pid)).toBe(true);
waitForDead(pid);
expect(readFileSync(cleanupPath, "utf8")).toBe("caller-cleanup");
} finally {
rmSync(root, { force: true, recursive: true });
}
});
it("cleans ClawHub fixture children when readiness times out", () => {
const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-clawhub-fixture-cleanup-"));
try {
const binDir = path.join(root, "bin");
const cleanupPath = path.join(root, "caller-cleanup");
const tmpDir = path.join(root, "scratch");
const pidPath = path.join(root, "server.pid");
mkdirSync(tmpDir);
writeFixtureServerShims(binDir, pidPath);
const result = spawnSync(
"/bin/bash",
[
"-c",
[
"set -euo pipefail",
"source scripts/e2e/lib/plugins/fixtures.sh",
"source scripts/e2e/lib/plugins/clawhub.sh",
"set +e",
`( set -e; trap 'printf caller-cleanup > ${shellQuote(cleanupPath)}' EXIT; run_plugins_clawhub_scenario )`,
'status="$?"',
"set -e",
'[ "$status" != "0" ]',
].join("\n"),
],
{
cwd: process.cwd(),
encoding: "utf8",
env: {
...process.env,
OPENCLAW_PLUGINS_E2E_LIVE_CLAWHUB: "0",
OPENCLAW_PLUGINS_TMP_DIR: tmpDir,
OPENCLAW_TEST_FIXTURE_SERVER_PID: pidPath,
PATH: `${binDir}${path.delimiter}/usr/bin${path.delimiter}/bin`,
},
},
);
expect(result.status, result.stderr || result.stdout).toBe(0);
const pid = Number(readFileSync(pidPath, "utf8"));
expect(Number.isInteger(pid)).toBe(true);
waitForDead(pid);
expect(readFileSync(cleanupPath, "utf8")).toBe("caller-cleanup");
} finally {
rmSync(root, { force: true, recursive: true });
}
});
it("uses the configured scratch root and resolves Windows home-relative install paths", () => {
const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugins-assertions-"));
const home = path.join(root, "home");