Files
reverse-engineering-anything/src/process/ProcessStartIdentity.test.ts
T
c1efa6467d feat(process): bound a finalization interval before forced capture timeout
Add optional finalization_ms to keep observing a captured root after SIGTERM, record the actual finalizer exit and each signal attempt, and escalate once when needed. Bind the initial root identity to captured ownership and preserve observations through cleanup uncertainty.

Fixes #1253.

Co-authored-by: Mykola Dementiev <16836724+nikolay-dementiev@users.noreply.github.com>
Co-authored-by: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com>
2026-10-11 09:55:43 +08:00

137 lines
4.2 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { ProcessOwnershipHost } from "./ProcessOwnership.js";
import {
observeProcessStartIdentity,
signalProcessWithStartIdentity,
} from "./ProcessOwnershipObservation.js";
const host = (identity: string | undefined): ProcessOwnershipHost => ({
platform: "linux",
listProcesses: async () =>
identity === undefined
? []
: [
{
pid: 42,
parentPid: 1,
processGroupId: 42,
uid: 1000,
state: "S",
command: "/bin/fixture",
},
],
environment: async () => ({}),
processIdentities: async () =>
new Map([[42, { state: "readable", identity: identity ?? "" }]]),
signalGroup: () => undefined,
});
describe("process start identity lease", () => {
it("captures a readable identity for a live PID", async () => {
await expect(
observeProcessStartIdentity(42, host("start-1")),
).resolves.toEqual({
state: "readable",
identity: "start-1",
});
});
it("does not adopt a replacement PID's first identity for an owned launch", async () => {
const replacement: ProcessOwnershipHost = {
...host("replacement-start"),
environment: async () => ({ REA_PROCESS_RUN_ID: "another-run" }),
};
await expect(
observeProcessStartIdentity(42, replacement, "captured-run"),
).resolves.toMatchObject({
state: "unavailable",
reason: expect.stringContaining("ownership"),
});
});
it("does not establish an owned launch when its token is unreadable", async () => {
const unreadable: ProcessOwnershipHost = {
...host("start-1"),
runTokens: async () =>
new Map([
[42, { state: "unavailable", reason: "environment_errno_EACCES" }],
]),
};
await expect(
observeProcessStartIdentity(42, unreadable, "captured-run"),
).resolves.toMatchObject({
state: "unavailable",
reason: expect.stringContaining("environment_errno_EACCES"),
});
});
it("rejects an identity that changes while the captured run token is read", async () => {
let identity = "start-1";
const changing: ProcessOwnershipHost = {
...host("start-1"),
processIdentities: async () =>
new Map([[42, { state: "readable", identity }]]),
environment: async () => {
identity = "replacement-start";
return { REA_PROCESS_RUN_ID: "captured-run" };
},
};
await expect(
observeProcessStartIdentity(42, changing, "captured-run"),
).resolves.toMatchObject({
state: "unavailable",
reason: expect.stringContaining("identity changed"),
});
});
it("signals the PID when its launch identity still matches", async () => {
const sendSignal = vi.fn();
await expect(
signalProcessWithStartIdentity(42, "start-1", "SIGKILL", {
host: host("start-1"),
sendSignal,
}),
).resolves.toBe("signaled");
expect(sendSignal).toHaveBeenCalledWith(42, "SIGKILL");
});
it("does not signal a reused PID even when its command is unchanged", async () => {
const sendSignal = vi.fn();
await expect(
signalProcessWithStartIdentity(42, "start-1", "SIGKILL", {
host: host("start-2"),
sendSignal,
}),
).resolves.toBe("identity-changed");
expect(sendSignal).not.toHaveBeenCalled();
});
it("does not signal when the PID identity is unavailable", async () => {
const unavailable: ProcessOwnershipHost = {
...host("start-1"),
processIdentities: async () =>
new Map([[42, { state: "unavailable", reason: "reader unavailable" }]]),
};
const sendSignal = vi.fn();
await expect(
signalProcessWithStartIdentity(42, "start-1", "SIGKILL", {
host: unavailable,
sendSignal,
}),
).resolves.toBe("unverified");
expect(sendSignal).not.toHaveBeenCalled();
});
it("reports an already-gone PID without signaling", async () => {
const sendSignal = vi.fn();
await expect(
signalProcessWithStartIdentity(42, "start-1", "SIGKILL", {
host: host(undefined),
sendSignal,
}),
).resolves.toBe("gone");
expect(sendSignal).not.toHaveBeenCalled();
});
});