use super::*;
use crate::test_support::{fixture, remove};
use std::path::{Path, PathBuf};
fn stat_line(command: &str, state: &str) -> String {
return format!(
"4242 ({command}) {state} 1 4242 4242 0 -1 4194560 100 0 0 0 1 2 0 0 20 0 1 0 4200 \
1000 100 18446744073709551615 1 1 0 0 0 0 0 0 0 0 0 0 17 3 0 0 0 0 0\n"
);
}
fn plant_proc(root: &Path, pid: i64, stat: &str, uptime: &str) {
let directory: PathBuf = root.join(pid.to_string());
std::fs::create_dir_all(&directory).expect("fake process directory");
std::fs::write(directory.join("stat"), stat).expect("fake stat");
std::fs::write(root.join("uptime"), uptime).expect("fake uptime");
}
#[test]
fn the_start_tick_follows_the_last_parenthesis() {
assert_eq!(
parse_linux_stat(stat_line("git", "S").as_str()),
Some((String::from("S"), 4200))
);
assert_eq!(
parse_linux_stat(stat_line("a) b (c) d", "R").as_str()),
Some((String::from("R"), 4200))
);
assert_eq!(
parse_linux_stat(stat_line("", "D").as_str()),
Some((String::from("D"), 4200))
);
}
#[test]
fn malformed_stat_lines_are_refused() {
assert_eq!(parse_linux_stat("4242 git S 1 2 3"), None);
assert_eq!(parse_linux_stat("4242 (git) S 1 2 3"), None);
assert_eq!(parse_linux_stat("4242 (git)"), None);
assert_eq!(parse_linux_stat(")"), None);
assert_eq!(parse_linux_stat(stat_line("git", "S").replace("4200", "-4200").as_str()), None);
assert_eq!(parse_linux_stat(stat_line("git", "S").replace("4200", "42x").as_str()), None);
}
#[test]
fn a_start_tick_beyond_the_safe_integers_is_refused() {
let beyond: String = stat_line("git", "S").replace("4200", "9007199254740992");
assert_eq!(parse_linux_stat(beyond.as_str()), None);
let largest: String = stat_line("git", "S").replace("4200", "9007199254740991");
assert_eq!(
parse_linux_stat(largest.as_str()),
Some((String::from("S"), 9_007_199_254_740_991))
);
}
#[test]
fn uptime_reads_its_first_field_only() {
assert_eq!(parse_uptime("12345.67 890.12\n"), Some(12345.67));
assert_eq!(parse_uptime(" 7.5 1.0"), Some(0.0));
assert_eq!(parse_uptime("\t7.5 1.0"), Some(7.5));
assert_eq!(parse_uptime("abc 1.0"), None);
assert_eq!(parse_uptime(""), Some(0.0));
assert_eq!(parse_uptime("inf 1.0"), None);
assert_eq!(parse_uptime("NaN 1.0"), None);
}
#[test]
fn lstart_fields_are_read_in_order() {
assert_eq!(
parse_lstart("Fri Sep 26 10:00:00 2026"),
Some((2026, 8, 26, 10, 0, 0))
);
assert_eq!(
parse_lstart("Mon Jan 5 09:08:07 2026"),
Some((2026, 0, 5, 9, 8, 7))
);
assert_eq!(
parse_lstart("Thu Dec 31 23:59:58 2026"),
Some((2026, 11, 31, 23, 59, 58))
);
}
#[test]
fn malformed_lstart_text_is_refused() {
assert_eq!(parse_lstart(""), None);
assert_eq!(parse_lstart("Fri Sep 26 10:00:00"), None);
assert_eq!(parse_lstart("Fri Smarch 26 10:00:00 2026"), None);
assert_eq!(parse_lstart("Fri Sep 26 10:00 2026"), None);
assert_eq!(parse_lstart("Fri Sep xx 10:00:00 2026"), None);
assert_eq!(parse_lstart("Fri Sep 26 10:00:xx 2026"), None);
assert_eq!(parse_lstart("Fri Sep 26 10:00:00 year"), None);
}
#[test]
fn a_running_process_starts_at_now_minus_uptime_plus_its_ticks() {
let root: PathBuf = fixture("process-start-running");
plant_proc(&root, 4242, stat_line("git", "S").as_str(), "100.00 0.00\n");
let start: ProcessStart = linux_start(4242, root.as_path(), 1_000_000.0);
assert_eq!(
start,
ProcessStart::Running {
started_at_ms: 1_000_000.0 - 100_000.0 + 42_000.0,
resolution_ms: LINUX_RESOLUTION_MS,
}
);
remove(root.as_path());
}
#[test]
fn zombie_and_dead_processes_are_missing() {
let root: PathBuf = fixture("process-start-exited");
plant_proc(&root, 4242, stat_line("git", "Z").as_str(), "100.00 0.00\n");
assert_eq!(linux_start(4242, root.as_path(), 1.0), ProcessStart::Missing);
plant_proc(&root, 4242, stat_line("git", "X").as_str(), "100.00 0.00\n");
assert_eq!(linux_start(4242, root.as_path(), 1.0), ProcessStart::Missing);
remove(root.as_path());
}
#[test]
fn an_absent_stat_file_is_missing() {
let root: PathBuf = fixture("process-start-absent");
assert_eq!(linux_start(4242, root.as_path(), 1.0), ProcessStart::Missing);
remove(root.as_path());
}
#[test]
fn malformed_proc_files_are_unknown() {
let root: PathBuf = fixture("process-start-malformed");
plant_proc(&root, 4242, "4242 git S 1\n", "100.00 0.00\n");
assert_eq!(
linux_start(4242, root.as_path(), 1.0),
ProcessStart::Unknown(String::from("Malformed Linux process stat."))
);
plant_proc(&root, 4242, stat_line("git", "S").as_str(), "not-a-number\n");
assert_eq!(
linux_start(4242, root.as_path(), 1.0),
ProcessStart::Unknown(String::from("Malformed /proc/uptime."))
);
remove(root.as_path());
}
#[test]
#[cfg(target_os = "linux")]
fn this_process_has_a_start_on_the_host() {
let own: i64 = i64::from(std::process::id());
assert!(process_exists(own));
match resolve_process_start(own) {
ProcessStart::Running {
started_at_ms,
resolution_ms,
} => {
assert!(started_at_ms <= now_ms() + (LINUX_RESOLUTION_MS as f64));
assert_eq!(resolution_ms, LINUX_RESOLUTION_MS);
}
other => panic!("this process should be running, got {other:?}"),
}
assert_eq!(resolve_process_start(4_000_000_000), ProcessStart::Missing);
assert!(!process_exists(4_000_000_000));
}