Open History to see the last commit
1// @effect-diagnostics nodeBuiltinImport:off - Spawns a real process tree for /proc.
2import * as NodeChildProcess from "node:child_process";
3import * as NodeFS from "node:fs";
4import { assert, describe, it } from "@effect/vitest";
5
6import { stopChildProcessesBeforeExit, stopZygotesBeforeExit } from "./DesktopExitChildren.ts";
7
8const isRunning = (pid: number) => {
9 try {
10 const stat = NodeFS.readFileSync(`/proc/${pid}/stat`, "utf8");
11 return !"ZX".includes(stat.charAt(stat.lastIndexOf(")") + 2));
12 } catch {
13 return false;
14 }
15};
16
17describe.runIf(NodeFS.existsSync("/proc/self/task"))("stopChildProcessesBeforeExit", () => {
18 it("terminates every child of the given process before returning", async () => {
19 const parent = NodeChildProcess.spawn(
20 "sh",
21 ["-c", "sleep 60 & echo $!; sleep 60 & echo $!; wait"],
22 { stdio: ["ignore", "pipe", "ignore"] },
23 );
24 try {
25 const childPids = await new Promise<Array<number>>((resolve) => {
26 let output = "";
27 parent.stdout.on("data", (chunk: Buffer) => {
28 output += chunk.toString();
29 const pids = output.split("\n").filter(Boolean).map(Number);
30 if (pids.length === 2) resolve(pids);
31 });
32 });
33 assert.isTrue(childPids.every(isRunning));
34
35 assert.deepStrictEqual(stopChildProcessesBeforeExit(parent.pid!), []);
36
37 assert.isFalse(childPids.some(isRunning));
38 } finally {
39 parent.kill("SIGKILL");
40 }
41 });
42
43 it("kills zygote children before returning and leaves other helpers running", async () => {
44 const spawnTitled = async (title: string) => {
45 const child = NodeChildProcess.spawn("sleep", ["60"], { argv0: title, stdio: "ignore" });
46 await new Promise((resolve) => child.once("spawn", resolve));
47 return child;
48 };
49 // Chromium rewrites a zygote's argv into one space-separated title.
50 const zygote = await spawnTitled("/opt/T3 Code/t3code --type=zygote --no-sandbox");
51 const utility = await spawnTitled("/opt/T3 Code/t3code --type=utility");
52 try {
53 stopZygotesBeforeExit(process.pid);
54
55 assert.isFalse(isRunning(zygote.pid!));
56 assert.isTrue(isRunning(utility.pid!));
57 } finally {
58 zygote.kill("SIGKILL");
59 utility.kill("SIGKILL");
60 }
61 });
62});