test(daemon): poll for the child's exec before asserting its path

Command::spawn returns after the fork, possibly before the child has
exec'd sleep; until then /proc/<pid>/exe still points at the test
binary itself, so asserting the basename immediately is a race. This
intermittently failed the Linux CI job on main (runs 29549626571,
29560431777, 29563049775). Poll process_path for up to 5s and carry
the last-seen basename into the failure message.
This commit is contained in:
l0ng-ai
2026-07-17 15:48:25 +08:00
parent 5b67d79805
commit 4d2094ff90
+17 -5
View File
@@ -528,6 +528,10 @@ mod tests {
/// match — this is what keeps a stale pidfile with a recycled pid from
/// getting an innocent process killed. Driven with a real `sleep` child:
/// alive, path readable, basename `sleep` ≠ the test binary's.
///
/// `spawn` returns after the fork, possibly before the child has exec'd —
/// until then its executable path still reads as *this* test binary — so
/// the path assertions poll until the exec is visible.
#[cfg(any(target_os = "macos", target_os = "linux"))]
#[test]
fn reap_guard_rejects_a_live_process_of_another_executable() {
@@ -538,11 +542,19 @@ mod tests {
let pid = child.id() as libc::pid_t;
assert!(process_alive(pid), "the sleep child is alive and ours");
assert_eq!(
process_path(pid).and_then(|p| p.file_name().map(|n| n.to_os_string())),
Some("sleep".into()),
"process_path resolves an arbitrary pid, not just our parent"
);
let deadline = Instant::now() + Duration::from_secs(5);
loop {
let basename = process_path(pid).and_then(|p| p.file_name().map(|n| n.to_os_string()));
if basename == Some("sleep".into()) {
break;
}
assert!(
Instant::now() < deadline,
"process_path resolves an arbitrary pid, not just our parent \
(still {basename:?} after 5s)"
);
std::thread::sleep(Duration::from_millis(10));
}
assert!(
!process_matches_own_exe(pid),
"sleep must not match the test binary; matching here would mean the reap could kill it"