Public: anyone can read this; the crew can change it.
A test pins the /proc readers behind /system: real text parses, and truncated or garbage text gives null, never NaN #6922
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6905-a-test-pins-the-proc-readers-behind-syst → main 1 commit fork
2 files
changed +72
−3
src/system/proc.ts +3 −3
| @@ -80,9 +80,9 @@ export function parseMemory(text: string | null): Memory | null { | ||
| 80 | 80 | return m ? Number(m[1]) * 1024 : 0; |
| 81 | 81 | }; |
| 82 | 82 | const total = kb("MemTotal"); |
| 83 | return total === 0 | |
| 84 | ? null | |
| 85 | : { total, available: kb("MemAvailable"), swapTotal: kb("SwapTotal"), swapFree: kb("SwapFree") }; | |
| 83 | const available = kb("MemAvailable"); | |
| 84 | // No MemAvailable (an old kernel, a cut file) is unknown, not zero bytes free. | |
| 85 | return total === 0 || available === 0 ? null : { total, available, swapTotal: kb("SwapTotal"), swapFree: kb("SwapFree") }; | |
| 86 | 86 | } |
| 87 | 87 | |
| 88 | 88 | export function parseLoad(text: string | null): [number, number, number] | null { |
test/system-proc.test.ts added +69
| @@ -0,0 +1,69 @@ | ||
| 1 | import { describe, expect, test } from "bun:test"; | |
| 2 | import { parseLoad, parseMemory, parseProcStat } from "../src/system/proc"; | |
| 3 | ||
| 4 | const MEMINFO = `MemTotal: 16318412 kB | |
| 5 | MemFree: 1234567 kB | |
| 6 | MemAvailable: 9876543 kB | |
| 7 | Buffers: 123456 kB | |
| 8 | SwapTotal: 2097148 kB | |
| 9 | SwapFree: 2097000 kB | |
| 10 | `; | |
| 11 | ||
| 12 | const stat = (comm: string, tail?: string[]) => | |
| 13 | `4242 (${comm}) S 1 4242 4242 0 -1 4194560 100 0 0 0 150 50 0 0 20 0 3 0 ${tail ? tail.join(" ") : "987654 123456789 2500 18446744073709551615"} 1 1 0 0 0 0 0\n`; | |
| 14 | ||
| 15 | describe("parseMemory", () => { | |
| 16 | test("real meminfo gives bytes", () => { | |
| 17 | expect(parseMemory(MEMINFO)).toEqual({ | |
| 18 | total: 16318412 * 1024, | |
| 19 | available: 9876543 * 1024, | |
| 20 | swapTotal: 2097148 * 1024, | |
| 21 | swapFree: 2097000 * 1024, | |
| 22 | }); | |
| 23 | }); | |
| 24 | test("a machine without swap has zero swap, still a reading", () => { | |
| 25 | const m = parseMemory("MemTotal: 1000 kB\nMemAvailable: 500 kB\n"); | |
| 26 | expect(m).toEqual({ total: 1024000, available: 512000, swapTotal: 0, swapFree: 0 }); | |
| 27 | }); | |
| 28 | test("no MemAvailable is unknown, not 0 bytes free", () => { | |
| 29 | expect(parseMemory(MEMINFO.replace(/MemAvailable:.*\n/, ""))).toBeNull(); | |
| 30 | }); | |
| 31 | test("empty, null, truncated and garbage give null", () => { | |
| 32 | for (const t of [null, "", "MemTotal:", "MemTotal: 163184", "MemTotal: abc kB\nMemAvailable: x kB", "\0\u{1F600}garbage\n"]) { | |
| 33 | expect(parseMemory(t)).toBeNull(); | |
| 34 | } | |
| 35 | }); | |
| 36 | }); | |
| 37 | ||
| 38 | describe("parseLoad", () => { | |
| 39 | test("real loadavg", () => { | |
| 40 | expect(parseLoad("0.52 0.61 0.70 2/845 123456\n")).toEqual([0.52, 0.61, 0.7]); | |
| 41 | }); | |
| 42 | test("empty, null, truncated and garbage give null, never NaN", () => { | |
| 43 | for (const t of [null, "", " \n", "0.5", "0.5 0.6", "a b c", "0.5 NaN 0.7", "1 2 Infinity", "\0\0\0"]) { | |
| 44 | expect(parseLoad(t)).toBeNull(); | |
| 45 | } | |
| 46 | }); | |
| 47 | }); | |
| 48 | ||
| 49 | describe("parseProcStat", () => { | |
| 50 | test("real stat line", () => { | |
| 51 | expect(parseProcStat(stat("bun"))).toEqual({ comm: "bun", ppid: 1, ticks: 200, rss: 2500 * 4096, start: 9876.54 }); | |
| 52 | }); | |
| 53 | test("comm with spaces and a )", () => { | |
| 54 | const s = parseProcStat(stat("my (odd) proc) x")); | |
| 55 | expect(s).toEqual({ comm: "my (odd) proc) x", ppid: 1, ticks: 200, rss: 2500 * 4096, start: 9876.54 }); | |
| 56 | }); | |
| 57 | test("a long comm is cut to 32", () => { | |
| 58 | expect(parseProcStat(stat("x".repeat(60)))?.comm).toHaveLength(32); | |
| 59 | }); | |
| 60 | test("empty, truncated and garbage give null without throwing", () => { | |
| 61 | for (const t of ["", "4242", "4242 (bun", "4242 bun) S 1", "4242 (bun) S 1 4242", ")(", "garbage ) ( text", "\0\0"]) { | |
| 62 | expect(parseProcStat(t)).toBeNull(); | |
| 63 | } | |
| 64 | }); | |
| 65 | test("non-numeric fields become 0, never NaN", () => { | |
| 66 | const s = parseProcStat(`1 (a) S ${Array(30).fill("zz").join(" ")}`); | |
| 67 | expect(s).toEqual({ comm: "a", ppid: 0, ticks: 0, rss: 0, start: 0 }); | |
| 68 | }); | |
| 69 | }); |
new above
offline