rea/tests/eventProcessTree.test.ts

419 lines
9.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
containsProcess,
getDescendants,
maxTreeDepth,
reconstructProcessTree,
type ProcessTreeEvent,
type ProcessEventType,
type ProcessNode,
type ProcessTreeReconstruction,
} from "../src/domain/eventProcessTree.js";
describe("event-backed process tree capture", () => {
it("reconstructs a simple process tree", () => {
const events: ProcessTreeEvent[] = [
{
sequence: 0,
at_ms: 0,
type: "spawn",
pid: 1,
ppid: 0,
process_name: "root",
executable: "/root",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 1,
at_ms: 10,
type: "spawn",
pid: 2,
ppid: 1,
process_name: "child",
executable: "/child",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 2,
at_ms: 20,
type: "exit",
pid: 2,
ppid: 1,
process_name: "child",
executable: null,
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: 0,
signal: null,
tid: null,
},
{
sequence: 3,
at_ms: 30,
type: "exit",
pid: 1,
ppid: 0,
process_name: "root",
executable: null,
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: 0,
signal: null,
tid: null,
},
];
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);
});
it("detects short-lived descendants", () => {
const events: ProcessTreeEvent[] = [
{
sequence: 0,
at_ms: 0,
type: "spawn",
pid: 1,
ppid: 0,
process_name: "root",
executable: "/root",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 1,
at_ms: 10,
type: "spawn",
pid: 2,
ppid: 1,
process_name: "short",
executable: "/short",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 2,
at_ms: 11,
type: "exit",
pid: 2,
ppid: 1,
process_name: "short",
executable: null,
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: 0,
signal: null,
tid: null,
},
];
const result = reconstructProcessTree(events, 1);
expect(result.short_lived_descendants).toBe(1);
});
it("detects re-exec", () => {
const events: ProcessTreeEvent[] = [
{
sequence: 0,
at_ms: 0,
type: "spawn",
pid: 1,
ppid: 0,
process_name: "root",
executable: "/root",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 1,
at_ms: 10,
type: "re_exec",
pid: 1,
ppid: 0,
process_name: "root",
executable: "/newroot",
arguments: ["--new"],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
];
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[] = [
{
sequence: 0,
at_ms: 0,
type: "spawn",
pid: 1,
ppid: 0,
process_name: "root",
executable: "/root",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 1,
at_ms: 10,
type: "spawn",
pid: 2,
ppid: 1,
process_name: "child",
executable: "/child",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 2,
at_ms: 20,
type: "reparent",
pid: 2,
ppid: 0,
process_name: "child",
executable: null,
arguments: [],
previous_pid: null,
previous_ppid: 1,
exit_code: null,
signal: null,
tid: null,
},
];
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[] = [
{
sequence: 0,
at_ms: 0,
type: "exit",
pid: 999,
ppid: null,
process_name: null,
executable: null,
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: 1,
signal: null,
tid: null,
},
];
const result = reconstructProcessTree(events, 1);
expect(result.events_unmatched).toBe(1);
expect(result.processes).toHaveLength(0);
});
it("gets descendants", () => {
const events: ProcessTreeEvent[] = [
{
sequence: 0,
at_ms: 0,
type: "spawn",
pid: 1,
ppid: 0,
process_name: "root",
executable: "/root",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 1,
at_ms: 10,
type: "spawn",
pid: 2,
ppid: 1,
process_name: "child1",
executable: "/c1",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 2,
at_ms: 20,
type: "spawn",
pid: 3,
ppid: 2,
process_name: "grandchild",
executable: "/gc",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
];
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[] = [
{
sequence: 0,
at_ms: 0,
type: "spawn",
pid: 1,
ppid: 0,
process_name: "root",
executable: "/root",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
];
const result = reconstructProcessTree(events, 1);
expect(containsProcess(result.processes, 1)).toBe(true);
expect(containsProcess(result.processes, 999)).toBe(false);
});
it("computes max tree depth", () => {
const events: ProcessTreeEvent[] = [
{
sequence: 0,
at_ms: 0,
type: "spawn",
pid: 1,
ppid: 0,
process_name: "root",
executable: "/root",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 1,
at_ms: 10,
type: "spawn",
pid: 2,
ppid: 1,
process_name: "child",
executable: "/c",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
{
sequence: 2,
at_ms: 20,
type: "spawn",
pid: 3,
ppid: 2,
process_name: "gc",
executable: "/gc",
arguments: [],
previous_pid: null,
previous_ppid: null,
exit_code: null,
signal: null,
tid: null,
},
];
const result = reconstructProcessTree(events, 1);
expect(maxTreeDepth(result.processes, 1)).toBe(2);
});
it("uses exported types", () => {
const node: ProcessNode = {
pid: 1,
ppid: null,
process_name: "test",
executable: "/test",
arguments: [],
children: [],
spawn_time_ms: 0,
exit_time_ms: null,
exit_code: null,
is_re_exec: false,
is_reparented: false,
previous_ppid: null,
};
expect(node.pid).toBe(1);
const recon: ProcessTreeReconstruction = {
root_pid: 1,
processes: [node],
events_consumed: 1,
events_unmatched: 0,
short_lived_descendants: 0,
has_re_exec: false,
has_reparenting: false,
};
expect(recon.root_pid).toBe(1);
});
it("uses ProcessEventType", () => {
const t: ProcessEventType = "spawn";
expect(t).toBe("spawn");
});
});