File size: 5,481 Bytes
fc93158 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | import fsSync from "node:fs";
import { describe, expect, it, vi } from "vitest";
import { getProcessStartTime, isPidAlive } from "./pid-alive.js";
function mockProcReads(entries: Record<string, string>) {
const originalReadFileSync = fsSync.readFileSync;
vi.spyOn(fsSync, "readFileSync").mockImplementation((filePath, encoding) => {
const key = String(filePath);
if (Object.hasOwn(entries, key)) {
return entries[key] as never;
}
return originalReadFileSync(filePath as never, encoding as never) as never;
});
}
async function withLinuxProcessPlatform<T>(run: () => Promise<T>): Promise<T> {
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
if (!originalPlatformDescriptor) {
throw new Error("missing process.platform descriptor");
}
Object.defineProperty(process, "platform", {
...originalPlatformDescriptor,
value: "linux",
});
try {
vi.resetModules();
return await run();
} finally {
Object.defineProperty(process, "platform", originalPlatformDescriptor);
vi.restoreAllMocks();
}
}
describe("isPidAlive", () => {
it("returns true for the current running process", () => {
expect(isPidAlive(process.pid)).toBe(true);
});
it("returns false for a non-existent PID", () => {
expect(isPidAlive(2 ** 30)).toBe(false);
});
it("returns false for invalid PIDs", () => {
expect(isPidAlive(0)).toBe(false);
expect(isPidAlive(-1)).toBe(false);
expect(isPidAlive(1.5)).toBe(false);
expect(isPidAlive(Number.NaN)).toBe(false);
expect(isPidAlive(Number.POSITIVE_INFINITY)).toBe(false);
});
it("returns false for zombie processes on Linux", async () => {
const zombiePid = process.pid;
mockProcReads({
[`/proc/${zombiePid}/status`]: `Name:\tnode\nUmask:\t0022\nState:\tZ (zombie)\nTgid:\t${zombiePid}\nPid:\t${zombiePid}\n`,
});
await withLinuxProcessPlatform(async () => {
const { isPidAlive: freshIsPidAlive } = await import("./pid-alive.js");
expect(freshIsPidAlive(zombiePid)).toBe(false);
});
});
it("treats unreadable linux proc status as non-zombie when kill succeeds", async () => {
const readFileSyncSpy = vi.spyOn(fsSync, "readFileSync").mockImplementation(() => {
throw new Error("no proc status");
});
const killSpy = vi.spyOn(process, "kill").mockImplementation(() => true);
await withLinuxProcessPlatform(async () => {
const { isPidAlive: freshIsPidAlive } = await import("./pid-alive.js");
expect(freshIsPidAlive(42)).toBe(true);
});
expect(readFileSyncSpy).toHaveBeenCalledWith("/proc/42/status", "utf8");
expect(killSpy).toHaveBeenCalledWith(42, 0);
});
});
describe("getProcessStartTime", () => {
it("returns a number on Linux for the current process", async () => {
// Simulate a realistic /proc/<pid>/stat line
const fakeStat = `${process.pid} (node) S 1 ${process.pid} ${process.pid} 0 -1 4194304 12345 0 0 0 100 50 0 0 20 0 8 0 98765 123456789 5000 18446744073709551615 0 0 0 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0`;
mockProcReads({
[`/proc/${process.pid}/stat`]: fakeStat,
});
await withLinuxProcessPlatform(async () => {
const { getProcessStartTime: fresh } = await import("./pid-alive.js");
const starttime = fresh(process.pid);
expect(starttime).toBe(98765);
});
});
it("returns null on non-Linux platforms", () => {
if (process.platform === "linux") {
// On actual Linux, this test is trivially satisfied by the other tests.
expect(true).toBe(true);
return;
}
expect(getProcessStartTime(process.pid)).toBeNull();
});
it("returns null for invalid PIDs", () => {
expect(getProcessStartTime(0)).toBeNull();
expect(getProcessStartTime(-1)).toBeNull();
expect(getProcessStartTime(1.5)).toBeNull();
expect(getProcessStartTime(Number.NaN)).toBeNull();
expect(getProcessStartTime(Number.POSITIVE_INFINITY)).toBeNull();
});
it("returns null for malformed /proc stat content", async () => {
mockProcReads({
"/proc/42/stat": "42 node S malformed",
});
await withLinuxProcessPlatform(async () => {
const { getProcessStartTime: fresh } = await import("./pid-alive.js");
expect(fresh(42)).toBeNull();
});
});
it("handles comm fields containing spaces and parentheses", async () => {
// comm field with spaces and nested parens: "(My App (v2))"
const fakeStat = `42 (My App (v2)) S 1 42 42 0 -1 4194304 0 0 0 0 0 0 0 0 20 0 1 0 55555 0 0 0 0 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0`;
mockProcReads({
"/proc/42/stat": fakeStat,
});
await withLinuxProcessPlatform(async () => {
const { getProcessStartTime: fresh } = await import("./pid-alive.js");
expect(fresh(42)).toBe(55555);
});
});
it("returns null for negative or non-integer start times", async () => {
const fakeStatPrefix = "42 (node) S 1 42 42 0 -1 4194304 12345 0 0 0 100 50 0 0 20 0 8 0 ";
const fakeStatSuffix =
" 123456789 5000 18446744073709551615 0 0 0 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0";
mockProcReads({
"/proc/42/stat": `${fakeStatPrefix}-1${fakeStatSuffix}`,
"/proc/43/stat": `${fakeStatPrefix}1.5${fakeStatSuffix}`,
});
await withLinuxProcessPlatform(async () => {
const { getProcessStartTime: fresh } = await import("./pid-alive.js");
expect(fresh(42)).toBeNull();
expect(fresh(43)).toBeNull();
});
});
});
|