1// @effect-diagnostics nodeBuiltinImport:off
2import * as NodeChildProcess from "node:child_process";
3
4import { describe, expect, it } from "@effect/vitest";
5import * as HostProcess from "@t3tools/shared/HostProcess";
6
7import { signalProcessGroup } from "./processGroup.ts";
8
9const errorCode = (run: () => void) => {
10 try {
11 run();
12 return undefined;
13 } catch (cause) {
14 return (cause as NodeJS.ErrnoException).code;
15 }
16};
17
18describe.skipIf(HostProcess.Platform.defaultValue() === "win32")("signalProcessGroup", () => {
19 // Signal 0 only probes, so this is safe to run unguarded: `kill(-1, 0)` and
20 // `kill(0, 0)` succeed, which is how a fake spawner's pid 1 became
21 // `kill(-1, SIGKILL)` and took down every process the user owned.
22 it("never reaches this server's group or every process the user owns", () => {
23 for (const pid of [1, 0, -1, Number.NaN, 1.5]) {
24 expect(
25 errorCode(() => signalProcessGroup(pid, 0)),
26 `pid ${pid}`,
27 ).toBe("ESRCH");
28 }
29 });
30
31 it("signals a spawned process group", async () => {
32 const child = NodeChildProcess.spawn("/bin/sh", ["-c", "sleep 600 & wait"], {
33 detached: true,
34 stdio: "ignore",
35 });
36 const exited = new Promise<NodeJS.Signals | null>((resolve) =>
37 child.once("exit", (_code, signal) => resolve(signal)),
38 );
39 const pid = child.pid!;
40
41 expect(errorCode(() => signalProcessGroup(pid, 0))).toBeUndefined();
42 signalProcessGroup(pid, "SIGKILL");
43 expect(await exited).toBe("SIGKILL");
44 });
45});