Files
reverse-engineering-anything/src/process/ProcessOwnership.listCancellation.test.ts
T
morluto 17a5f14f98 fix(process): run ps from fixed system locations, not REA's PATH (#10755)
* fix(process): run macOS ps from /bin/ps, not REA's PATH

Owned process cleanup and process sampling ran "ps" through REA's PATH, so
an unusable PATH turned a confirmable cleanup into an incomplete one
(spawn ps ENOENT). macOS ships ps at /bin/ps on the sealed system volume.
Linux keeps the documented procps-on-REA's-PATH contract.

* test(process): stop the ps fixture only after its descendant spawned

* fix(process): run Linux ps from fixed system locations before REA's PATH

Linux process ownership now runs the first executable of /bin/ps, /usr/bin/ps and /run/current-system/sw/bin/ps, searching REA's PATH only when none exists. Admission refusals name the ps that ran in their reason and ps_command/ps_source diagnostics; cleanup failure reasons keep the executed command. Doctor and remediation text follow the same selection. The macOS cleanup test now releases its fixture group even when the descendant wait fails.

* fix(application): keep which ps ran in provider availability

ownedProcessPrerequisiteAvailability copied only the admission and system codes from a Linux ps refusal, so Ghidra, JADX and Apktool status lost ps_command and ps_source. Copy the refusal's prerequisite facts from one key list.
2026-11-15 16:28:46 +08:00

69 lines
2.7 KiB
TypeScript

import { expect, it, vi } from "vitest";
const processList = vi.hoisted(() => vi.fn());
vi.mock("./ExecFileOutput.js", async (original) => ({
...(await original<typeof import("./ExecFileOutput.js")>()),
execFileOutput: processList,
}));
import { AnalysisOutputError } from "../domain/analysisErrorCore.js";
import { ExecFileOutputCancelledError } from "./ExecFileOutput.js";
import { ProcessOwnershipInspectionCancelled } from "./ProcessInspectionCancellation.js";
import { createSystemProcessOwnershipHost } from "./ProcessOwnershipObservation.js";
import { readRunTokenCarriers } from "./ProcessRunTokenObservations.js";
const routes = ["list", "carriers"] as const;
const reasons = [
null,
{ caller: "opaque" },
new AnalysisOutputError("authored-read", "opaque error"),
] as const;
it.each(
routes.flatMap((route) => reasons.map((reason) => ({ route, reason }))),
)(
"retains explicit native command cancellation before $route parsing with opaque reason $reason",
async ({ route, reason }) => {
const controller = new AbortController();
controller.abort(reason);
const native = new ExecFileOutputCancelledError(reason);
processList.mockReset().mockRejectedValue(native);
const host = createSystemProcessOwnershipHost("linux", {});
const pending =
route === "list"
? host.listProcesses(controller.signal)
: readRunTokenCarriers(host, "authored-run", controller.signal);
const failure = await pending.catch((cause: unknown) => cause);
expect(failure).toBeInstanceOf(ProcessOwnershipInspectionCancelled);
if (!(failure instanceof ProcessOwnershipInspectionCancelled))
throw failure;
expect(failure.reason).toBe(reason);
expect(failure.cause).toBe(native);
expect(processList).toHaveBeenCalledTimes(1);
expect(processList.mock.calls[0]?.[0]).toMatch(/(?:^|\/)ps$/u);
expect(processList.mock.calls[0]?.[2]?.signal).toBe(controller.signal);
},
);
it.each(routes)(
"preserves an independent physical AbortError-shaped rejection before %s parsing when reused as the abort reason",
async (route) => {
const controller = new AbortController();
const physical = Object.assign(
new Error("physical process table read failed"),
{
name: "AbortError",
cause: { native: "independent fault" },
},
);
controller.abort(physical);
processList.mockReset().mockRejectedValue(physical);
const host = createSystemProcessOwnershipHost("linux", {});
const pending =
route === "list"
? host.listProcesses(controller.signal)
: readRunTokenCarriers(host, "authored-run", controller.signal);
await expect(pending).rejects.toBe(physical);
},
);