singularity-forge/packages/native/src/__tests__/ps.test.mjs
2026-04-15 13:38:15 +02:00

109 lines
3.4 KiB
JavaScript

import { test, describe } from "node:test";
import assert from "node:assert/strict";
import { createRequire } from "node:module";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
// Load the native addon directly
const addonDir = path.resolve(__dirname, "..", "..", "..", "..", "native", "addon");
const platformTag = `${process.platform}-${process.arch}`;
const candidates = [
path.join(addonDir, `gsd_engine.${platformTag}.node`),
path.join(addonDir, "gsd_engine.dev.node"),
];
let native;
for (const candidate of candidates) {
try {
native = require(candidate);
break;
} catch {
// try next
}
}
if (!native) {
console.error("Native addon not found. Run `npm run build:native -w @sf-run/native` first.");
process.exit(1);
}
describe("native ps: listDescendants()", () => {
test("returns an array for the current process", () => {
const descendants = native.listDescendants(process.pid);
assert.ok(Array.isArray(descendants));
});
test("returns empty array for non-existent PID", () => {
// PID 2147483647 is extremely unlikely to exist
const descendants = native.listDescendants(2147483647);
assert.ok(Array.isArray(descendants));
assert.equal(descendants.length, 0);
});
test("finds child processes", { skip: "proc_listchildpids unreliable on macOS — needs sysctl KERN_PROC implementation" }, async () => {
const child = spawn("sh", ["-c", "sleep 30 & sleep 30 & wait"], {
stdio: "ignore",
});
await new Promise((resolve) => setTimeout(resolve, 500));
try {
const descendants = native.listDescendants(child.pid);
assert.ok(descendants.length > 0, `expected descendants of sh (pid ${child.pid}), got: ${JSON.stringify(descendants)}`);
} finally {
native.killTree(child.pid, 9);
}
});
});
describe("native ps: killTree()", () => {
test("kills a process and its children", async () => {
// Spawn a shell that spawns a sleep subprocess
const child = spawn("sh", ["-c", "sleep 60"], { stdio: "ignore" });
await new Promise((resolve) => setTimeout(resolve, 100));
const killed = native.killTree(child.pid, 9);
assert.ok(killed >= 1, `should kill at least 1 process, killed: ${killed}`);
// Verify the child is actually dead
await new Promise((resolve) => {
child.on("exit", resolve);
// Timeout safety — if already exited, resolve immediately
setTimeout(resolve, 500);
});
});
test("returns 0 for non-existent PID", () => {
const killed = native.killTree(2147483647, 9);
assert.equal(killed, 0);
});
});
describe("native ps: processGroupId()", () => {
test("returns a number for the current process", () => {
const pgid = native.processGroupId(process.pid);
if (process.platform === "win32") {
assert.equal(pgid, null);
} else {
assert.equal(typeof pgid, "number");
assert.ok(pgid > 0);
}
});
test("returns null for non-existent PID", () => {
const pgid = native.processGroupId(2147483647);
assert.equal(pgid, null);
});
});
describe("native ps: killProcessGroup()", () => {
test("returns false for non-existent process group", () => {
const result = native.killProcessGroup(2147483647, 15);
assert.equal(result, false);
});
});