


























11// Check Deadcode Unused Files tests cover check deadcode unused files script behavior.
2+import { spawn } from "node:child_process";
23import { EventEmitter } from "node:events";
3-import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
4+import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
45import os from "node:os";
56import path from "node:path";
67import { describe, expect, it } from "vitest";
@@ -27,6 +28,43 @@ function finishFakeProcess(
2728child.emit("close", status, signal);
2829}
293031+function isProcessAlive(pid: number): boolean {
32+try {
33+process.kill(pid, 0);
34+return true;
35+} catch {
36+return false;
37+}
38+}
39+40+async function sleep(ms: number): Promise<void> {
41+await new Promise((resolve) => {
42+setTimeout(resolve, ms);
43+});
44+}
45+46+async function waitForFile(filePath: string, timeoutMs: number): Promise<void> {
47+const deadlineAt = Date.now() + timeoutMs;
48+while (Date.now() < deadlineAt) {
49+if (existsSync(filePath)) {
50+return;
51+}
52+await sleep(25);
53+}
54+throw new Error(`timeout waiting for ${filePath}`);
55+}
56+57+async function waitForDead(pid: number, timeoutMs: number): Promise<void> {
58+const deadlineAt = Date.now() + timeoutMs;
59+while (Date.now() < deadlineAt) {
60+if (!isProcessAlive(pid)) {
61+return;
62+}
63+await sleep(25);
64+}
65+throw new Error(`process still alive: ${pid}`);
66+}
67+3068describe("check-deadcode-unused-files", () => {
3169it("parses the compact Knip unused-file section", () => {
3270expect(
@@ -243,6 +281,9 @@ src/a.ts: src/a.ts
243281const kills: Array<NodeJS.Signals | number | undefined> = [];
244282process.kill = ((pid: number, signal?: NodeJS.Signals | number) => {
245283if (Math.abs(pid) === child.pid) {
284+if (signal === 0) {
285+throw Object.assign(new Error("gone"), { code: "ESRCH" });
286+}
246287kills.push(signal);
247288finishFakeProcess(child, null, (signal as NodeJS.Signals | undefined) ?? "SIGTERM");
248289return true;
@@ -274,6 +315,57 @@ src/a.ts: src/a.ts
274315}
275316});
276317318+it.skipIf(process.platform === "win32")(
319+"waits for timed-out Knip process groups after the wrapper exits",
320+async () => {
321+const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-knip-timeout-"));
322+const childPidPath = path.join(root, "child.pid");
323+let childPid = 0;
324+325+try {
326+const childScript = [
327+"process.on('SIGTERM', () => {});",
328+"setInterval(() => {}, 1000);",
329+].join("");
330+const parentScript = [
331+"const { spawn } = require('node:child_process');",
332+"const fs = require('node:fs');",
333+`const child = spawn(process.execPath, ['-e', ${JSON.stringify(childScript)}], { stdio: 'ignore' });`,
334+"fs.writeFileSync(process.env.OPENCLAW_TEST_CHILD_PID, String(child.pid));",
335+"process.on('SIGTERM', () => process.exit(0));",
336+"setInterval(() => {}, 1000);",
337+].join("");
338+339+const resultPromise = runKnipUnusedFiles({
340+env: { ...process.env, OPENCLAW_TEST_CHILD_PID: childPidPath },
341+killGraceMs: 50,
342+spawnCommand(_command: string, _args: string[], options: unknown) {
343+return spawn(process.execPath, ["-e", parentScript], {
344+ ...(options as Parameters<typeof spawn>[2]),
345+env: { ...process.env, OPENCLAW_TEST_CHILD_PID: childPidPath },
346+});
347+},
348+timeoutMs: 100,
349+writeStatus: () => {},
350+});
351+352+await waitForFile(childPidPath, 2_000);
353+childPid = Number.parseInt(readFileSync(childPidPath, "utf8"), 10);
354+expect(isProcessAlive(childPid)).toBe(true);
355+356+await expect(resultPromise).resolves.toMatchObject({
357+errorCode: "ETIMEDOUT",
358+});
359+await waitForDead(childPid, 2_000);
360+} finally {
361+if (childPid && isProcessAlive(childPid)) {
362+process.kill(childPid, "SIGKILL");
363+}
364+rmSync(root, { recursive: true, force: true });
365+}
366+},
367+);
368+277369it("keeps output delivered after process exit but before stdio close", async () => {
278370const child = new FakeKnipProcess();
279371const resultPromise = runKnipUnusedFiles({
@@ -300,6 +392,9 @@ src/a.ts: src/a.ts
300392const originalKill = process.kill.bind(process);
301393process.kill = ((pid: number, signal?: NodeJS.Signals | number) => {
302394if (Math.abs(pid) === child.pid) {
395+if (signal === 0) {
396+throw Object.assign(new Error("gone"), { code: "ESRCH" });
397+}
303398finishFakeProcess(child, null, (signal as NodeJS.Signals | undefined) ?? "SIGTERM");
304399return true;
305400}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。