use std::process::Command;
fn lsofrs() -> Command {
Command::new(env!("CARGO_BIN_EXE_lsofrs"))
}
#[test]
fn help_flag_short() {
let out = lsofrs().arg("-h").output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("FILE DESCRIPTOR SCANNER"));
assert!(stdout.contains("USAGE:"));
assert!(stdout.contains("SELECTION"));
assert!(stdout.contains("NETWORK"));
assert!(stdout.contains("EXAMPLES"));
}
#[test]
fn help_flag_long() {
let out = lsofrs().arg("--help").output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("FILE DESCRIPTOR SCANNER"));
}
#[test]
fn version_flag() {
let out = lsofrs().arg("-V").output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains(env!("CARGO_PKG_VERSION")));
}
#[test]
fn default_run_produces_output() {
let out = lsofrs().output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("COMMAND"));
assert!(stdout.contains("PID"));
assert!(stdout.contains("USER"));
assert!(stdout.contains("FD"));
assert!(stdout.contains("TYPE"));
assert!(stdout.contains("NAME"));
}
#[test]
fn default_run_has_processes() {
let out = lsofrs().output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
let lines: Vec<&str> = stdout.lines().collect();
assert!(
lines.len() > 5,
"expected output lines, got {}",
lines.len()
);
}
#[test]
fn pid_filter_self() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-p", &my_pid]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
for line in stdout.lines().skip(1) {
if line.trim().is_empty() {
continue;
}
if !line.starts_with(' ') {
assert!(
line.contains(&my_pid),
"line doesn't contain our pid: {line}"
);
}
}
}
#[test]
fn pid_filter_nonexistent() {
let out = lsofrs().args(["-p", "9999999"]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
let lines: Vec<&str> = stdout.lines().collect();
assert!(lines.len() <= 1, "expected no data for nonexistent pid");
}
#[test]
fn terse_output_pids_only() {
let out = lsofrs().arg("-t").output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
for line in stdout.lines() {
if line.trim().is_empty() {
continue;
}
assert!(
line.trim().parse::<i32>().is_ok(),
"terse line should be a PID: '{line}'"
);
}
}
#[test]
fn terse_with_pid_filter() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-t", "-p", &my_pid]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
let pids: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect();
assert!(!pids.is_empty());
assert!(pids.contains(&my_pid.as_str()));
}
#[test]
fn json_output_valid() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-J", "-p", &my_pid]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("JSON output should be valid JSON");
assert!(parsed.is_array());
}
#[test]
fn json_output_has_fields() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["--json", "-p", &my_pid]).output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap();
assert!(!parsed.is_empty());
let proc = &parsed[0];
assert!(proc.get("command").is_some());
assert!(proc.get("pid").is_some());
assert!(proc.get("uid").is_some());
assert!(proc.get("files").is_some());
assert_eq!(proc["pid"].as_i64().unwrap(), std::process::id() as i64);
}
#[test]
fn json_files_have_fd_and_name() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-J", "-p", &my_pid]).output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap();
let files = parsed[0]["files"].as_array().unwrap();
assert!(!files.is_empty());
for f in files {
assert!(f.get("fd").is_some(), "file missing fd field");
assert!(f.get("name").is_some(), "file missing name field");
assert!(f.get("type").is_some(), "file missing type field");
}
}
#[test]
fn field_output_pid_and_name() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-F", "pn", "-p", &my_pid]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains(&format!("p{my_pid}")));
assert!(stdout.lines().any(|l| l.starts_with('n')));
}
#[test]
fn field_output_nul_terminator() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["-F", "p", "-0", "-p", &my_pid])
.output()
.unwrap();
assert!(out.status.success());
let stdout = &out.stdout;
assert!(stdout.contains(&0u8), "NUL terminator should be present");
}
#[test]
fn command_filter_prefix() {
let out = lsofrs().args(["-c", "lsofrs"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn inet_flag_no_crash() {
let out = lsofrs().arg("-i").output().unwrap();
assert!(out.status.success());
}
#[test]
fn inet_tcp_no_crash() {
let out = lsofrs().args(["-i", "TCP"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn inet_udp_no_crash() {
let out = lsofrs().args(["-i", "UDP"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn inet_port_no_crash() {
let out = lsofrs().args(["-i", ":443"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn inet_4_no_crash() {
let out = lsofrs().args(["-i", "4"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn inet_6_no_crash() {
let out = lsofrs().args(["-i", "6"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn inet_4tcp_no_crash() {
let out = lsofrs().args(["-i", "4TCP"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn unix_socket_flag_no_crash() {
let out = lsofrs().arg("-U").output().unwrap();
assert!(out.status.success());
}
#[test]
fn nfs_flag_no_crash() {
let out = lsofrs().arg("-N").output().unwrap();
assert!(out.status.success());
}
#[test]
fn summary_mode() {
let out = lsofrs().arg("--summary").output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("FD") || stdout.contains("summary") || stdout.contains("TYPE"));
}
#[test]
fn summary_json() {
let out = lsofrs().args(["--summary", "--json"]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("summary --json should produce valid JSON");
assert!(parsed.is_object() || parsed.is_array());
}
#[test]
fn fd_filter_range() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["-d", "0-2", "-p", &my_pid])
.output()
.unwrap();
assert!(out.status.success());
}
#[test]
fn fd_filter_cwd() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["-d", "cwd", "-p", &my_pid])
.output()
.unwrap();
assert!(out.status.success());
}
#[test]
fn and_mode_narrows_results() {
let my_pid = std::process::id().to_string();
let out_or = lsofrs()
.args(["-p", &my_pid, "-c", "nonexistent_cmd"])
.output()
.unwrap();
let count_or = String::from_utf8_lossy(&out_or.stdout).lines().count();
let out_and = lsofrs()
.args(["-a", "-p", &my_pid, "-c", "nonexistent_cmd"])
.output()
.unwrap();
let count_and = String::from_utf8_lossy(&out_and.stdout).lines().count();
assert!(
count_and < count_or,
"AND mode should produce fewer results"
);
}
#[test]
fn pgid_show_flag() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["--pgid-show", "-p", &my_pid])
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("PGID") || stdout.contains("PG/ID"));
}
#[test]
fn ppid_show_flag() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-R", "-p", &my_pid]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("PPID") || stdout.contains("P/PID"));
}
#[test]
fn pid_exclude() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["-p", &format!("^{my_pid}")])
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
for line in stdout.lines().skip(1) {
if line.starts_with(' ') || line.trim().is_empty() {
continue;
}
assert!(
!line.contains(&format!(" {my_pid} ")),
"excluded pid should not appear: {line}"
);
}
}
#[test]
fn file_arg_no_crash() {
let out = lsofrs().arg("/dev/null").output().unwrap();
assert!(out.status.success());
}
#[test]
fn combined_flags_no_crash() {
let out = lsofrs().args(["-n", "-P", "-w"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn tree_mode() {
let out = lsofrs().arg("--tree").output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("PID"));
assert!(stdout.contains("FDs"));
assert!(stdout.contains("CMD"));
}
#[test]
fn tree_mode_with_pid_filter() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["--tree", "-p", &my_pid]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains(&my_pid));
}
#[test]
fn tree_mode_json() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["--tree", "--json", "-p", &my_pid])
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("tree --json should produce valid JSON");
assert!(parsed.is_array());
let arr = parsed.as_array().unwrap();
assert!(!arr.is_empty());
assert!(arr[0].get("pid").is_some());
assert!(arr[0].get("fd_count").is_some());
assert!(arr[0].get("children").is_some());
}
#[test]
fn tree_mode_with_command_filter() {
let out = lsofrs().args(["--tree", "-c", "lsofrs"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn tree_mode_with_user_filter() {
let out = lsofrs().args(["--tree", "-u", &whoami()]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("PID"));
}
#[test]
fn json_empty_result_valid() {
let out = lsofrs().args(["-J", "-p", "9999999"]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap();
assert!(parsed.is_empty());
}
#[test]
fn json_file_has_type_field() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-J", "-p", &my_pid]).output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap();
if !parsed.is_empty() {
let files = parsed[0]["files"].as_array().unwrap();
if !files.is_empty() {
let t = files[0]["type"].as_str().unwrap();
assert!(!t.is_empty());
}
}
}
#[test]
fn field_output_command_field() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-F", "pc", "-p", &my_pid]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.lines().any(|l| l.starts_with('c')));
}
#[test]
fn field_output_type_field() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-F", "pt", "-p", &my_pid]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.lines().any(|l| l.starts_with('t')));
}
#[test]
fn field_output_uid_field() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-F", "pu", "-p", &my_pid]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.lines().any(|l| l.starts_with('u')));
}
#[test]
fn field_output_login_name_field() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-F", "pL", "-p", &my_pid]).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.lines().any(|l| l.starts_with('L')));
}
#[test]
fn pid_and_user_filter_or() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["-p", &my_pid, "-u", &whoami()])
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.lines().count() > 1);
}
#[test]
fn exclude_pid_still_shows_others() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["-p", &format!("^{my_pid}")])
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.lines().count() > 1, "should have other processes");
}
#[test]
fn fd_filter_single_number() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-d", "0", "-p", &my_pid]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn fd_filter_exclude_range() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["-d", "^0-2", "-p", &my_pid])
.output()
.unwrap();
assert!(out.status.success());
}
#[test]
fn inet_tcp_port_combo() {
let out = lsofrs().args(["-i", "TCP:443"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn inet_6tcp_combo() {
let out = lsofrs().args(["-i", "6TCP"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn inet_4udp_combo() {
let out = lsofrs().args(["-i", "4UDP"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn inet_host_port_combo() {
let out = lsofrs().args(["-i", "TCP@127.0.0.1:80"]).output().unwrap();
assert!(out.status.success());
}
#[test]
fn summary_with_pid_filter() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["--summary", "-p", &my_pid])
.output()
.unwrap();
assert!(out.status.success());
}
#[test]
fn summary_with_command_filter() {
let out = lsofrs()
.args(["--summary", "-c", "lsofrs"])
.output()
.unwrap();
assert!(out.status.success());
}
#[test]
fn terse_no_header() {
let my_pid = std::process::id().to_string();
let out = lsofrs().args(["-t", "-p", &my_pid]).output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(!stdout.contains("COMMAND"));
assert!(!stdout.contains("PID"));
}
#[test]
fn terse_nonexistent_pid_empty() {
let out = lsofrs().args(["-t", "-p", "9999999"]).output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.trim().is_empty());
}
#[test]
fn all_boolean_flags_no_crash() {
let out = lsofrs()
.args(["-n", "-P", "-w", "-R", "--pgid-show"])
.output()
.unwrap();
assert!(out.status.success());
}
#[test]
fn json_with_pgid_ppid() {
let my_pid = std::process::id().to_string();
let out = lsofrs()
.args(["-J", "-R", "--pgid-show", "-p", &my_pid])
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap();
assert!(!parsed.is_empty());
assert!(parsed[0].get("ppid").is_some());
assert!(parsed[0].get("pgid").is_some());
}
#[test]
fn help_contains_all_sections() {
let out = lsofrs().arg("-h").output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("SELECTION"));
assert!(stdout.contains("NETWORK"));
assert!(stdout.contains("FILES & DIRECTORIES"));
assert!(stdout.contains("DISPLAY"));
assert!(stdout.contains("SYSTEM"));
assert!(stdout.contains("EXAMPLES"));
assert!(stdout.contains("INFO"));
}
#[test]
fn help_contains_tree_option() {
let out = lsofrs().arg("-h").output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("--tree"));
}
#[test]
fn help_contains_lsofrs_banner() {
let out = lsofrs().arg("-h").output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("FILE DESCRIPTOR SCANNER"));
assert!(stdout.contains("Every open file tells a story"));
}
#[test]
fn invalid_flag_exits_nonzero() {
let out = lsofrs().arg("--nonexistent-flag-xyz").output().unwrap();
assert!(!out.status.success());
}
#[test]
fn invalid_short_flag_exits_nonzero() {
let out = lsofrs().arg("-Z").output().unwrap();
assert!(!out.status.success());
}
fn whoami() -> String {
String::from_utf8(
std::process::Command::new("whoami")
.output()
.unwrap()
.stdout,
)
.unwrap()
.trim()
.to_string()
}