mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 02:45:58 +00:00
127 lines
4.4 KiB
TypeScript
127 lines
4.4 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
cleanupOwnedProcessGroup,
|
|
type ProcessOwnershipHost,
|
|
} from "./ProcessOwnership.js";
|
|
import { parseProcessEnvironment } from "./ProcessOwnershipObservation.js";
|
|
import { selectCapturedProcessGroupIds } from "./ProcessOwnershipProcessTree.js";
|
|
import { matchesOwnedProcessCommand } from "./ProcessCommandIdentity.js";
|
|
import { host, ownership } from "./ProcessOwnership.fixture.js";
|
|
describe("owned process-group cleanup discovery", () => {
|
|
it("excludes sampled groups whose leader was outside the captured tree", () => {
|
|
expect(
|
|
selectCapturedProcessGroupIds(100, [
|
|
{ pid: 100, process_group_id: 42 },
|
|
{ pid: 100, process_group_id: 100 },
|
|
{ pid: 101, process_group_id: 42 },
|
|
{ pid: 102, process_group_id: 102 },
|
|
{ pid: 103, process_group_id: 102 },
|
|
]),
|
|
).toEqual([100, 102]);
|
|
});
|
|
it("drops nameless Linux environment entries", () => {
|
|
expect(
|
|
parseProcessEnvironment("=ignored\0REA_PROCESS_RUN_ID=owned\0EMPTY=\0"),
|
|
).toEqual({ REA_PROCESS_RUN_ID: "owned", EMPTY: "" });
|
|
});
|
|
it("matches macOS executable names after process-table normalization", () => {
|
|
expect(
|
|
matchesOwnedProcessCommand(
|
|
"node /tmp/fake-launcher.mjs",
|
|
"/opt/node/bin/node",
|
|
"darwin",
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
matchesOwnedProcessCommand("(node)", "/opt/node/bin/node", "darwin"),
|
|
).toBe(true);
|
|
expect(
|
|
matchesOwnedProcessCommand(
|
|
"node /tmp/fake-launcher.mjs",
|
|
"/opt/python/bin/python3",
|
|
"darwin",
|
|
),
|
|
).toBe(false);
|
|
expect(
|
|
matchesOwnedProcessCommand(
|
|
"node /tmp/fake-launcher.mjs",
|
|
"/opt/node/bin/node",
|
|
"linux",
|
|
),
|
|
).toBe(false);
|
|
expect(
|
|
matchesOwnedProcessCommand("(node)", "/opt/node/bin/node", "linux"),
|
|
).toBe(false);
|
|
});
|
|
it("signals only a group whose every member carries the run token", async () => {
|
|
const { adapter, signalGroup } = host({
|
|
100: { REA_PROCESS_RUN_ID: "run-token" },
|
|
101: { REA_PROCESS_RUN_ID: "run-token" },
|
|
});
|
|
expect(await cleanupOwnedProcessGroup(ownership, adapter)).toEqual({
|
|
cleaned: true,
|
|
signaled: true,
|
|
});
|
|
expect(signalGroup).toHaveBeenCalledWith(100, "SIGKILL");
|
|
});
|
|
it("validates and signals rooted descendant groups once, root first", async () => {
|
|
const processes = [
|
|
{ pid: 100, parentPid: 1, processGroupId: 100 },
|
|
{ pid: 110, parentPid: 100, processGroupId: 100 },
|
|
{ pid: 101, parentPid: 100, processGroupId: 101 },
|
|
{ pid: 102, parentPid: 101, processGroupId: 102 },
|
|
{ pid: 103, parentPid: 102, processGroupId: 102 },
|
|
].map((process) => ({
|
|
...process,
|
|
state: "S",
|
|
command: "fixture",
|
|
}));
|
|
const signalGroup = vi.fn();
|
|
const adapter: ProcessOwnershipHost = {
|
|
listProcesses: () => Promise.resolve(processes),
|
|
environment: () => Promise.resolve({ REA_PROCESS_RUN_ID: "run-token" }),
|
|
signalGroup,
|
|
};
|
|
await expect(cleanupOwnedProcessGroup(ownership, adapter)).resolves.toEqual(
|
|
{ cleaned: true, signaled: true },
|
|
);
|
|
expect(signalGroup.mock.calls).toEqual([
|
|
[100, "SIGKILL"],
|
|
[101, "SIGKILL"],
|
|
[102, "SIGKILL"],
|
|
]);
|
|
});
|
|
it("leaves an unrelated Hopper process group outside the rooted tree untouched", async () => {
|
|
const processes = [
|
|
{ pid: 100, parentPid: 1, processGroupId: 100, command: "rea-hopper" },
|
|
{ pid: 101, parentPid: 100, processGroupId: 101, command: "owned-child" },
|
|
{
|
|
pid: 900,
|
|
parentPid: 1,
|
|
processGroupId: 900,
|
|
command:
|
|
"/Applications/Hopper Disassembler.app/Contents/MacOS/Hopper Disassembler",
|
|
},
|
|
].map((process) => ({ ...process, state: "S" }));
|
|
const environment = vi.fn((pid: number) =>
|
|
Promise.resolve({
|
|
REA_PROCESS_RUN_ID: pid === 900 ? "unrelated-run" : "run-token",
|
|
}),
|
|
);
|
|
const signalGroup = vi.fn();
|
|
const adapter: ProcessOwnershipHost = {
|
|
listProcesses: () => Promise.resolve(processes),
|
|
environment,
|
|
signalGroup,
|
|
};
|
|
await expect(cleanupOwnedProcessGroup(ownership, adapter)).resolves.toEqual(
|
|
{ cleaned: true, signaled: true },
|
|
);
|
|
expect(signalGroup.mock.calls).toEqual([
|
|
[100, "SIGKILL"],
|
|
[101, "SIGKILL"],
|
|
]);
|
|
expect(environment.mock.calls.some(([pid]) => pid === 900)).toBe(false);
|
|
});
|
|
});
|