use std::{
error::Error,
io, thread,
time::{Duration, Instant},
};
use fork::{
Fork, ProcessFork, ProcessId, Signal, WEXITSTATUS, WIFEXITED, WIFSIGNALED, WTERMSIG, fork,
getpgrp, getpid, getppid, signal_process, waitpid, waitpid_nohang,
};
const CHILD_EXIT_CODE: libc::c_int = 42;
const TEST_TIMEOUT: Duration = Duration::from_secs(3);
const POLL_INTERVAL: Duration = Duration::from_millis(5);
struct ChildGuard {
process: libc::pid_t,
reaped: bool,
}
impl ChildGuard {
const fn new(process: libc::pid_t) -> Self {
Self {
process,
reaped: false,
}
}
fn mark_reaped(&mut self) {
self.reaped = true;
}
}
impl Drop for ChildGuard {
fn drop(&mut self) {
if self.reaped {
return;
}
if let Some(pid) = ProcessId::new(self.process) {
let _ = signal_process(pid, Signal::KILL);
}
let _ = waitpid(self.process);
}
}
fn child_exit(code: libc::c_int) -> ! {
unsafe { libc::_exit(code) }
}
fn child_wait_for_kill() -> ! {
loop {
unsafe {
libc::pause();
}
}
}
#[test]
fn identity_accessors_return_positive_values() {
assert!(getpid() > 0);
assert!(getppid() > 0);
assert!(getpgrp() > 0);
}
#[test]
fn fork_enum_accessors_are_stable() {
assert!(Fork::Child.is_child());
assert!(!Fork::Child.is_parent());
assert_eq!(Fork::Child.child_pid(), None);
assert_eq!(Fork::Child.child_process_id(), None);
let parent = Fork::Parent(4321);
assert!(parent.is_parent());
assert!(!parent.is_child());
assert_eq!(parent.child_pid(), Some(4321));
assert_eq!(parent.child_process_id(), ProcessId::new(4321));
}
#[test]
fn process_fork_enum_shape_is_stable() {
assert_eq!(ProcessFork::Child, ProcessFork::Child);
if let Some(pid) = ProcessId::new(4321) {
let parent = ProcessFork::Parent(pid);
assert_eq!(parent, ProcessFork::Parent(pid));
assert_ne!(parent, ProcessFork::Child);
if let ProcessFork::Parent(inner) = parent {
assert_eq!(inner.get(), 4321);
}
}
}
#[test]
fn waitpid_reports_exact_exit_status() -> Result<(), Box<dyn Error>> {
match fork()? {
Fork::Parent(child) => {
let mut guard = ChildGuard::new(child);
let status = waitpid(child)?;
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), CHILD_EXIT_CODE);
guard.mark_reaped();
Ok(())
}
Fork::Child => child_exit(CHILD_EXIT_CODE),
}
}
#[test]
fn waitpid_nohang_reports_none_while_running_then_terminal() -> Result<(), Box<dyn Error>> {
match fork()? {
Fork::Parent(child) => {
let mut guard = ChildGuard::new(child);
assert_eq!(waitpid_nohang(child)?, None);
let pid = ProcessId::new(child).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"fork returned a nonpositive pid",
)
})?;
signal_process(pid, Signal::KILL)?;
let deadline = Instant::now() + TEST_TIMEOUT;
let status = loop {
if let Some(status) = waitpid_nohang(child)? {
break status;
}
if Instant::now() >= deadline {
return Err(io::Error::new(
io::ErrorKind::TimedOut,
"child did not terminate within the test deadline",
)
.into());
}
thread::sleep(POLL_INTERVAL);
};
assert!(WIFSIGNALED(status));
assert_eq!(WTERMSIG(status), libc::SIGKILL);
guard.mark_reaped();
Ok(())
}
Fork::Child => child_wait_for_kill(),
}
}