From 50abf91c1538e28c82330401b85f7a48ea650dde Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Mon, 13 Jul 2026 16:35:32 +0100 Subject: [PATCH] =?UTF-8?q?test(compile):=20portable=20process=20probes=20?= =?UTF-8?q?=E2=80=94=20macOS=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two supervisor unit tests failed on the macOS CI matrix (both Lua flavors; Linux green): - pgid_of read /proc//stat, which has no macOS equivalent — now probes via `ps -o pgid=` (portable, still avoids widening the nix feature set with `process` for getpgid). - the setsid escape-hatch test requires util-linux's setsid(1), absent on macOS — now skips per-test when setsid isn't on PATH (the m6_5 selective-skip precedent); the escape hatch is a Linux-production behavior and the other group-lifecycle tests still run everywhere. Also fixed while here: the acceptance suite's pid_alive was a /proc existence check, which on macOS made every "descendant is dead" assertion vacuously TRUE (passing, but toothless) — now a portable `kill -0` probe, so the group-kill assertions bite on both OSes. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VoiEyuPjoBhvwACf8HAnLB --- src/process.rs | 40 ++++++++++++++++++++++++-------- tests/compile_mode_acceptance.rs | 9 ++++++- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/process.rs b/src/process.rs index 5b513cf..da42b94 100644 --- a/src/process.rs +++ b/src/process.rs @@ -2723,18 +2723,30 @@ mod tests { nix::sys::signal::kill(Pid::from_raw(pid), None).is_ok() } - /// Process group of `pid` per /proc//stat field 5 (Linux; - /// avoids widening the nix feature set with `process`). The comm - /// field may contain spaces, so parse after the closing paren. + /// Process group of `pid` via `ps` (portable across Linux and + /// macOS CI — the previous /proc//stat read has no macOS + /// equivalent; `ps -o pgid=` avoids widening the nix feature set + /// with `process` for `getpgid`). fn pgid_of(pid: u32) -> i32 { - let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).expect("read stat"); - let after_comm = &stat[stat.rfind(')').expect("comm paren") + 2..]; - after_comm - .split_whitespace() - .nth(2) // state ppid pgrp → index 2 - .expect("pgrp field") + let out = std::process::Command::new("ps") + .args(["-o", "pgid=", "-p", &pid.to_string()]) + .output() + .expect("run ps"); + String::from_utf8_lossy(&out.stdout) + .trim() .parse() - .expect("pgrp parses") + .expect("pgid parses") + } + + /// True when `name` resolves on PATH. Fixture-dependency gate: + /// the setsid escape-hatch test needs util-linux's setsid(1), + /// absent on macOS — skip per-test rather than fail (the + /// `m6_5_repl_acceptance` selective-skip precedent). + fn binary_available(name: &str) -> bool { + std::process::Command::new("which") + .arg(name) + .output() + .is_ok_and(|o| o.status.success()) } /// Poll `path` until it holds a parseable pid. Fixture scripts @@ -3034,6 +3046,14 @@ mod tests { // joins must complete, and the per-runtime active-reader // count must return to zero — across repeated cycles, so // nothing accumulates. + if !binary_available("setsid") { + // util-linux's setsid(1) is absent on macOS CI; the + // escape hatch is a Linux-production behavior. Skip + // rather than fail — the other group-lifecycle tests + // still run everywhere. + eprintln!("skipping: setsid(1) not on PATH"); + return; + } let dir = tempfile::tempdir().expect("tempdir"); let mut escapees = Vec::new(); for round in 0..3 { diff --git a/tests/compile_mode_acceptance.rs b/tests/compile_mode_acceptance.rs index 44b2fa4..146e47d 100644 --- a/tests/compile_mode_acceptance.rs +++ b/tests/compile_mode_acceptance.rs @@ -184,7 +184,14 @@ fn wait_pidfile(s: &mut EditorState, path: &Path) -> i32 { } fn pid_alive(pid: i32) -> bool { - Path::new(&format!("/proc/{pid}")).exists() + // `kill -0` probe via /bin/kill: portable across Linux and macOS + // (a /proc existence check has no macOS equivalent and would + // make every "descendant is dead" assertion vacuously true + // there). + std::process::Command::new("kill") + .args(["-0", &pid.to_string()]) + .output() + .is_ok_and(|o| o.status.success()) } /// Errors getter marshalled to a comparable Rust shape (encoded as