215 lines
5.4 KiB
TypeScript
215 lines
5.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
containsProcess,
|
|
getDescendants,
|
|
maxTreeDepth,
|
|
reconstructProcessTree,
|
|
type ProcessTreeEvent,
|
|
} from "./eventProcessTree.js";
|
|
import {
|
|
exitEvent,
|
|
processNode,
|
|
reExecEvent,
|
|
reparentEvent,
|
|
rootSpawn,
|
|
spawnEvent,
|
|
} from "./eventProcessTree.fixture.js";
|
|
|
|
describe("event-backed process tree reconstruction", () => {
|
|
it("walks trees larger than ten thousand nodes", () => {
|
|
const childPids = Array.from({ length: 10_001 }, (_, index) => index + 2);
|
|
const root = processNode({
|
|
pid: 1,
|
|
ppid: null,
|
|
process_name: "root",
|
|
children: childPids,
|
|
});
|
|
const children = childPids.map((pid) =>
|
|
processNode({ pid, ppid: 1, process_name: "root" }),
|
|
);
|
|
|
|
expect(maxTreeDepth([root, ...children], 1)).toBe(1);
|
|
expect(getDescendants([root, ...children], 1)).toHaveLength(
|
|
childPids.length,
|
|
);
|
|
});
|
|
|
|
it("reconstructs a simple process tree", () => {
|
|
const events: ProcessTreeEvent[] = [
|
|
rootSpawn(0),
|
|
spawnEvent({
|
|
sequence: 1,
|
|
pid: 2,
|
|
ppid: 1,
|
|
process_name: "child",
|
|
executable: "/child",
|
|
}),
|
|
exitEvent({
|
|
sequence: 2,
|
|
at_ms: 20,
|
|
pid: 2,
|
|
ppid: 1,
|
|
process_name: "child",
|
|
}),
|
|
exitEvent({
|
|
sequence: 3,
|
|
at_ms: 30,
|
|
pid: 1,
|
|
ppid: 0,
|
|
process_name: "root",
|
|
}),
|
|
];
|
|
const result = reconstructProcessTree(events, 1);
|
|
expect(result.processes).toHaveLength(2);
|
|
expect(result.events_consumed).toBe(4);
|
|
expect(result.events_unmatched).toBe(0);
|
|
expect(result.short_lived_descendants).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("short-lived process descendants", () => {
|
|
it("detects short-lived descendants", () => {
|
|
const events: ProcessTreeEvent[] = [
|
|
rootSpawn(0),
|
|
spawnEvent({
|
|
sequence: 1,
|
|
pid: 2,
|
|
ppid: 1,
|
|
process_name: "short",
|
|
executable: "/short",
|
|
}),
|
|
exitEvent({
|
|
sequence: 2,
|
|
at_ms: 11,
|
|
pid: 2,
|
|
ppid: 1,
|
|
process_name: "short",
|
|
}),
|
|
];
|
|
const result = reconstructProcessTree(events, 1);
|
|
expect(result.short_lived_descendants).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("event-backed process tree transitions", () => {
|
|
it("detects re-exec", () => {
|
|
const events: ProcessTreeEvent[] = [
|
|
rootSpawn(0),
|
|
reExecEvent({
|
|
sequence: 1,
|
|
pid: 1,
|
|
ppid: 0,
|
|
process_name: "root",
|
|
executable: "/newroot",
|
|
arguments: ["--new"],
|
|
}),
|
|
];
|
|
const result = reconstructProcessTree(events, 1);
|
|
expect(result.has_re_exec).toBe(true);
|
|
const root = result.processes.find((p) => p.pid === 1);
|
|
expect(root?.is_re_exec).toBe(true);
|
|
expect(root?.executable).toBe("/newroot");
|
|
});
|
|
|
|
it("detects reparenting", () => {
|
|
const events: ProcessTreeEvent[] = [
|
|
rootSpawn(0),
|
|
spawnEvent({
|
|
sequence: 1,
|
|
pid: 2,
|
|
ppid: 1,
|
|
process_name: "child",
|
|
executable: "/child",
|
|
}),
|
|
reparentEvent({
|
|
sequence: 2,
|
|
pid: 2,
|
|
ppid: 0,
|
|
process_name: "child",
|
|
previous_ppid: 1,
|
|
}),
|
|
];
|
|
const result = reconstructProcessTree(events, 1);
|
|
expect(result.has_reparenting).toBe(true);
|
|
const child = result.processes.find((p) => p.pid === 2);
|
|
expect(child?.is_reparented).toBe(true);
|
|
expect(child?.previous_ppid).toBe(1);
|
|
expect(child?.ppid).toBe(0);
|
|
});
|
|
|
|
it("reports unmatched events for unknown exits", () => {
|
|
const events: ProcessTreeEvent[] = [
|
|
exitEvent({
|
|
sequence: 0,
|
|
at_ms: 0,
|
|
pid: 999,
|
|
ppid: null,
|
|
process_name: null,
|
|
exit_code: 1,
|
|
}),
|
|
];
|
|
const result = reconstructProcessTree(events, 1);
|
|
expect(result.events_unmatched).toBe(1);
|
|
expect(result.processes).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe("event-backed process tree queries", () => {
|
|
it("gets descendants", () => {
|
|
const events: ProcessTreeEvent[] = [
|
|
rootSpawn(0),
|
|
spawnEvent({
|
|
sequence: 1,
|
|
pid: 2,
|
|
ppid: 1,
|
|
process_name: "child1",
|
|
executable: "/c1",
|
|
}),
|
|
spawnEvent({
|
|
sequence: 2,
|
|
pid: 3,
|
|
ppid: 2,
|
|
process_name: "grandchild",
|
|
executable: "/gc",
|
|
}),
|
|
];
|
|
const result = reconstructProcessTree(events, 1);
|
|
const descendants = getDescendants(result.processes, 1);
|
|
expect(descendants).toHaveLength(2);
|
|
expect(descendants).toContain(2);
|
|
expect(descendants).toContain(3);
|
|
});
|
|
|
|
it("checks process containment", () => {
|
|
const events: ProcessTreeEvent[] = [rootSpawn(0)];
|
|
const result = reconstructProcessTree(events, 1);
|
|
expect(containsProcess(result.processes, 1)).toBe(true);
|
|
expect(containsProcess(result.processes, 999)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("event-backed process tree depth and types", () => {
|
|
it("computes max tree depth", () => {
|
|
const events: ProcessTreeEvent[] = [
|
|
rootSpawn(0),
|
|
spawnEvent({
|
|
sequence: 1,
|
|
pid: 2,
|
|
ppid: 1,
|
|
process_name: "child",
|
|
executable: "/c",
|
|
}),
|
|
spawnEvent({
|
|
sequence: 2,
|
|
pid: 3,
|
|
ppid: 2,
|
|
process_name: "gc",
|
|
executable: "/gc",
|
|
}),
|
|
];
|
|
const result = reconstructProcessTree(events, 1);
|
|
expect(maxTreeDepth(result.processes, 1)).toBe(2);
|
|
});
|
|
});
|