diff --git a/scripts/e2e/lib/plugins/fixtures.sh b/scripts/e2e/lib/plugins/fixtures.sh index 0b6aee977e92..4776954ee91e 100644 --- a/scripts/e2e/lib/plugins/fixtures.sh +++ b/scripts/e2e/lib/plugins/fixtures.sh @@ -9,12 +9,45 @@ openclaw_plugins_cleanup_fixture_servers() { [[ -f "$pid_file" ]] || continue pid="$(cat "$pid_file" 2>/dev/null || true)" if [[ "$pid" =~ ^[0-9]+$ ]]; then - kill "$pid" 2>/dev/null || true + openclaw_plugins_stop_fixture_process "$pid" fi rm -f "$pid_file" done } +openclaw_plugins_signal_fixture_process() { + local pid="$1" + local signal="$2" + if kill -0 -- "-$pid" >/dev/null 2>&1; then + kill "-$signal" -- "-$pid" >/dev/null 2>&1 || true + return + fi + kill "-$signal" "$pid" >/dev/null 2>&1 || true +} + +openclaw_plugins_fixture_process_alive() { + local pid="$1" + kill -0 "$pid" >/dev/null 2>&1 || kill -0 -- "-$pid" >/dev/null 2>&1 +} + +openclaw_plugins_stop_fixture_process() { + local pid="$1" + local _ + local attempts="${OPENCLAW_PLUGINS_FIXTURE_STOP_ATTEMPTS:-40}" + local interval="${OPENCLAW_PLUGINS_FIXTURE_STOP_INTERVAL_SECONDS:-0.25}" + if declare -F openclaw_e2e_stop_process >/dev/null 2>&1; then + openclaw_e2e_stop_process "$pid" + return + fi + openclaw_plugins_signal_fixture_process "$pid" TERM + for _ in $(seq 1 "$attempts"); do + ! openclaw_plugins_fixture_process_alive "$pid" && { wait "$pid" >/dev/null 2>&1 || true; return; } + sleep "$interval" + done + openclaw_plugins_signal_fixture_process "$pid" KILL + wait "$pid" >/dev/null 2>&1 || true +} + openclaw_plugins_print_fixture_log() { local log_file="$1" if declare -F docker_e2e_print_log >/dev/null 2>&1; then diff --git a/test/scripts/plugins-assertions.test.ts b/test/scripts/plugins-assertions.test.ts index fa324069346d..490e9281a7bb 100644 --- a/test/scripts/plugins-assertions.test.ts +++ b/test/scripts/plugins-assertions.test.ts @@ -79,6 +79,24 @@ function writeFixtureServerShims(binDir: string, pidPath: string): void { writeFileSync(pidPath, ""); } +function writeStubbornFixtureServerShims(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 ':' 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 writeCrashingFixtureServerShim(binDir: string): void { mkdirSync(binDir, { recursive: true }); writeFileSync( @@ -327,6 +345,51 @@ test -d "$OPENCLAW_PLUGINS_TMP_DIR" } }); + it("force-kills stubborn npm fixture registry children during cleanup", () => { + const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-npm-fixture-kill-")); + try { + const binDir = path.join(root, "bin"); + const fixtureDir = path.join(root, "fixture"); + const pidPath = path.join(root, "server.pid"); + mkdirSync(fixtureDir); + writeStubbornFixtureServerShims(binDir, pidPath); + + const result = spawnSync( + "/bin/bash", + [ + "-c", + [ + "set -euo pipefail", + "source scripts/e2e/lib/plugins/fixtures.sh", + "set +e", + `( 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_PLUGINS_FIXTURE_STOP_ATTEMPTS: "2", + OPENCLAW_PLUGINS_FIXTURE_STOP_INTERVAL_SECONDS: "0.05", + 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); + } finally { + rmSync(root, { force: true, recursive: true }); + } + }); + it("bounds npm fixture registry logs when readiness fails", () => { const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-npm-fixture-log-")); try {