#[cfg(unix)]
mod daemon_liveness {
use cc_switch::daemon::pidfile::{Pidfile, process_alive, process_name};
use tempfile::TempDir;
fn make_pidfile() -> (TempDir, Pidfile) {
let dir = TempDir::new().unwrap();
let path = dir.path().join("daemon.pid");
(dir, Pidfile::new(path))
}
#[test]
fn pidfile_missing_means_daemon_not_running() {
let (_dir, pidfile) = make_pidfile();
assert!(pidfile.read().unwrap().is_none());
}
#[test]
fn pidfile_present_with_our_pid_means_running() {
let (_dir, pidfile) = make_pidfile();
pidfile.acquire().unwrap();
let pid = pidfile.read().unwrap().unwrap();
assert_eq!(pid, std::process::id());
assert!(process_alive(pid).unwrap());
}
#[test]
fn pidfile_present_dead_pid_is_stale() {
let (dir, _pidfile) = make_pidfile();
let path = dir.path().join("daemon.pid");
std::fs::write(&path, "4294967294\n").unwrap();
let pidfile = Pidfile::new(path);
let pid = pidfile.read().unwrap().unwrap();
assert_eq!(pid, 4_294_967_294);
assert!(!process_alive(pid).unwrap());
}
#[test]
fn pidfile_unparseable_is_corrupted() {
let (dir, _pidfile) = make_pidfile();
let path = dir.path().join("daemon.pid");
std::fs::write(&path, "not-a-number\n").unwrap();
let pidfile = Pidfile::new(path);
assert!(pidfile.read().is_err());
}
#[test]
fn pidfile_empty_is_corrupted() {
let (dir, _pidfile) = make_pidfile();
let path = dir.path().join("daemon.pid");
std::fs::write(&path, "").unwrap();
let pidfile = Pidfile::new(path);
assert!(pidfile.read().is_err());
}
#[test]
fn pidfile_negative_is_corrupted() {
let (dir, _pidfile) = make_pidfile();
let path = dir.path().join("daemon.pid");
std::fs::write(&path, "-1\n").unwrap();
let pidfile = Pidfile::new(path);
assert!(pidfile.read().is_err());
}
#[test]
fn self_process_is_alive() {
assert!(process_alive(std::process::id()).unwrap());
}
#[test]
fn pid_1_is_alive_on_unix() {
assert!(process_alive(1).unwrap());
}
#[test]
fn impossibly_high_pid_is_not_alive() {
assert!(!process_alive(0xFFFF_FFFE).unwrap());
}
#[test]
fn process_name_of_self_is_not_empty() {
let name = process_name(std::process::id());
assert!(name.is_some(), "should be able to read own process name");
assert!(!name.unwrap().is_empty());
}
#[test]
fn process_name_of_pid_1_is_init_or_launchd() {
let name = process_name(1);
assert!(name.is_some(), "PID 1 should have a readable process name");
}
#[test]
fn process_name_of_dead_pid_is_none() {
let name = process_name(0xFFFF_FFFE);
assert!(name.is_none());
}
#[test]
fn acquire_then_release_allows_re_acquire() {
let (_dir, pidfile) = make_pidfile();
pidfile.acquire().unwrap();
pidfile.release().unwrap();
pidfile.acquire().unwrap();
}
#[test]
fn double_acquire_fails() {
let (_dir, pidfile) = make_pidfile();
pidfile.acquire().unwrap();
assert!(pidfile.acquire().is_err());
}
#[test]
fn release_is_idempotent() {
let (_dir, pidfile) = make_pidfile();
pidfile.acquire().unwrap();
pidfile.release().unwrap();
pidfile.release().unwrap(); }
#[cfg(unix)]
#[test]
fn pidfile_has_0600_permissions() {
use std::os::unix::fs::PermissionsExt;
let (dir, pidfile) = make_pidfile();
pidfile.acquire().unwrap();
let path = dir.path().join("daemon.pid");
let mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, 0o600, "pidfile should be 0600, got {:o}", mode);
}
}