

























11// Plugin Lifecycle Probe tests cover QA Lab plugin lifecycle evidence.
22import { EventEmitter } from "node:events";
3-import { mkdirSync, writeFileSync } from "node:fs";
3+import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
44import path from "node:path";
55import { afterEach, describe, expect, it, vi } from "vitest";
66import { createTempDirTracker } from "../../../helpers/temp-dir.js";
@@ -17,6 +17,32 @@ function makeTempDir(): string {
1717return tempDirs.make("openclaw-plugin-lifecycle-probe-");
1818}
191920+function isProcessRunning(pid: number): boolean {
21+try {
22+process.kill(pid, 0);
23+return true;
24+} catch {
25+return false;
26+}
27+}
28+29+async function sleep(ms: number): Promise<void> {
30+await new Promise((resolve) => {
31+setTimeout(resolve, ms);
32+});
33+}
34+35+async function waitForFile(pathToCheck: string, timeoutMs: number): Promise<void> {
36+const deadline = Date.now() + timeoutMs;
37+while (Date.now() < deadline) {
38+if (existsSync(pathToCheck)) {
39+return;
40+}
41+await sleep(25);
42+}
43+throw new Error(`Timed out waiting for ${pathToCheck}`);
44+}
45+2046class FakeCommandChild extends EventEmitter {
2147readonly signals: string[] = [];
2248@@ -109,4 +135,47 @@ describe("plugin lifecycle matrix probe", () => {
109135vi.useRealTimers();
110136}
111137});
138+139+it("keeps fallback SIGKILL armed for ignored-stdio descendants", async () => {
140+if (process.platform === "win32") {
141+return;
142+}
143+144+const dir = makeTempDir();
145+const descendantPidPath = path.join(dir, "descendant.pid");
146+let descendantPid: number | undefined;
147+try {
148+const childScript = "process.on('SIGTERM', () => {}); setInterval(() => {}, 1000);";
149+const parentScript = [
150+"import { spawn } from 'node:child_process';",
151+"import { writeFileSync } from 'node:fs';",
152+`const child = spawn(process.execPath, ['-e', ${JSON.stringify(childScript)}], { stdio: 'ignore' });`,
153+"child.unref();",
154+"writeFileSync(process.env.OPENCLAW_TEST_DESCENDANT_PID, String(child.pid));",
155+"process.on('SIGTERM', () => process.exit(0));",
156+"setInterval(() => {}, 1000);",
157+].join("\n");
158+159+const run = probeTesting.runCommand(
160+process.execPath,
161+["--input-type=module", "-e", parentScript],
162+{
163+env: { ...process.env, OPENCLAW_TEST_DESCENDANT_PID: descendantPidPath },
164+timeoutKillGraceMs: 250,
165+timeoutMs: 500,
166+},
167+);
168+await waitForFile(descendantPidPath, 2_000);
169+await sleep(300);
170+171+await expect(run).rejects.toThrow(/timed out after 500ms/u);
172+173+descendantPid = Number(readFileSync(descendantPidPath, "utf8"));
174+expect(isProcessRunning(descendantPid)).toBe(false);
175+} finally {
176+if (descendantPid && isProcessRunning(descendantPid)) {
177+process.kill(descendantPid, "SIGKILL");
178+}
179+}
180+});
112181});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。