1148 lines
36 KiB
Rust
1148 lines
36 KiB
Rust
// Take a look at the license at the top of the repository in the LICENSE file.
|
||
|
||
#![cfg(feature = "system")]
|
||
|
||
use bstr::ByteSlice;
|
||
use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System, UpdateKind};
|
||
|
||
macro_rules! start_proc {
|
||
($time:literal, $name:literal) => {
|
||
if cfg!(target_os = "windows") {
|
||
std::process::Command::new("waitfor")
|
||
.arg("/t")
|
||
.arg($time)
|
||
.arg($name)
|
||
.stdout(std::process::Stdio::null())
|
||
.spawn()
|
||
.unwrap()
|
||
} else {
|
||
std::process::Command::new("sleep")
|
||
.arg($time)
|
||
.stdout(std::process::Stdio::null())
|
||
.spawn()
|
||
.unwrap()
|
||
}
|
||
};
|
||
}
|
||
|
||
#[test]
|
||
fn test_cwd() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let mut p = start_proc!("3", "CwdSignal");
|
||
|
||
let pid = Pid::from_u32(p.id() as _);
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
let mut s = System::new();
|
||
s.refresh_processes_specifics(
|
||
ProcessesToUpdate::All,
|
||
false,
|
||
ProcessRefreshKind::nothing().with_cwd(UpdateKind::Always),
|
||
);
|
||
p.kill().expect("Unable to kill process.");
|
||
|
||
let processes = s.processes();
|
||
let p = processes.get(&pid);
|
||
|
||
if let Some(p) = p {
|
||
assert_eq!(p.pid(), pid);
|
||
assert_eq!(p.cwd().unwrap(), &std::env::current_dir().unwrap());
|
||
} else {
|
||
panic!("Process not found!");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_cmd() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let mut p = start_proc!("3", "CmdSignal");
|
||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||
let mut s = System::new();
|
||
assert!(s.processes().is_empty());
|
||
s.refresh_processes_specifics(
|
||
ProcessesToUpdate::All,
|
||
false,
|
||
ProcessRefreshKind::nothing().with_cmd(UpdateKind::Always),
|
||
);
|
||
p.kill().expect("Unable to kill process");
|
||
assert!(!s.processes().is_empty());
|
||
if let Some(process) = s.process(Pid::from_u32(p.id() as _)) {
|
||
if cfg!(target_os = "windows") {
|
||
// Sometimes, we get the full path instead for some reasons... So just in case,
|
||
// we check for the command independently that from the arguments.
|
||
assert!(process.cmd()[0].as_encoded_bytes().contains_str("waitfor"));
|
||
assert_eq!(&process.cmd()[1..], &["/t", "3", "CmdSignal"]);
|
||
} else {
|
||
assert_eq!(process.cmd(), &["sleep", "3"]);
|
||
}
|
||
} else {
|
||
panic!("Process not found!");
|
||
}
|
||
}
|
||
|
||
fn build_test_binary(file_name: &str) {
|
||
std::process::Command::new("rustc")
|
||
.arg("test_bin/main.rs")
|
||
.arg("-o")
|
||
.arg(file_name)
|
||
.stdout(std::process::Stdio::null())
|
||
.spawn()
|
||
.unwrap()
|
||
.wait()
|
||
.unwrap();
|
||
}
|
||
|
||
#[test]
|
||
#[allow(clippy::zombie_processes)]
|
||
fn test_environ() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let file_name = "target/test_binary";
|
||
build_test_binary(file_name);
|
||
let mut p = std::process::Command::new(format!("./{file_name}"))
|
||
.env("FOO", "BAR")
|
||
.env("OTHER", "VALUE")
|
||
.spawn()
|
||
.unwrap();
|
||
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
let pid = Pid::from_u32(p.id() as _);
|
||
let mut s = System::new();
|
||
|
||
s.refresh_processes_specifics(
|
||
ProcessesToUpdate::Some(&[pid]),
|
||
false,
|
||
ProcessRefreshKind::everything(),
|
||
);
|
||
p.kill().expect("Unable to kill process.");
|
||
|
||
let processes = s.processes();
|
||
let proc_ = processes.get(&pid);
|
||
|
||
if let Some(proc_) = proc_ {
|
||
assert_eq!(proc_.pid(), pid);
|
||
assert!(proc_.environ().iter().any(|e| e == "FOO=BAR"));
|
||
assert!(proc_.environ().iter().any(|e| e == "OTHER=VALUE"));
|
||
} else {
|
||
panic!("Process not found!");
|
||
}
|
||
|
||
// Test to ensure that a process with a lot of environment variables doesn't get truncated.
|
||
// More information in <https://github.com/GuillaumeGomez/sysinfo/issues/886>.
|
||
const SIZE: usize = 30_000;
|
||
let mut big_env = String::with_capacity(SIZE);
|
||
for _ in 0..SIZE {
|
||
big_env.push('a');
|
||
}
|
||
let mut p = std::process::Command::new("./target/test_binary")
|
||
.env("FOO", &big_env)
|
||
.spawn()
|
||
.unwrap();
|
||
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
let pid = Pid::from_u32(p.id() as _);
|
||
let mut s = System::new();
|
||
|
||
s.refresh_processes_specifics(
|
||
ProcessesToUpdate::All,
|
||
false,
|
||
ProcessRefreshKind::nothing().with_environ(UpdateKind::Always),
|
||
);
|
||
|
||
let processes = s.processes();
|
||
let proc_ = processes.get(&pid);
|
||
|
||
if let Some(proc_) = proc_ {
|
||
p.kill().expect("Unable to kill process.");
|
||
assert_eq!(proc_.pid(), pid);
|
||
let env = format!("FOO={big_env}");
|
||
assert!(proc_.environ().iter().any(|e| *e == *env));
|
||
} else {
|
||
panic!("Process not found!");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_process_refresh() {
|
||
let mut s = System::new();
|
||
assert_eq!(s.processes().len(), 0);
|
||
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
s.refresh_processes(
|
||
ProcessesToUpdate::Some(&[sysinfo::get_current_pid().expect("failed to get current pid")]),
|
||
false,
|
||
);
|
||
assert!(s
|
||
.process(sysinfo::get_current_pid().expect("failed to get current pid"))
|
||
.is_some());
|
||
|
||
assert!(s
|
||
.processes()
|
||
.iter()
|
||
.all(|(_, p)| p.environ().is_empty() && p.cwd().is_none() && p.cmd().is_empty()));
|
||
assert!(s
|
||
.processes()
|
||
.iter()
|
||
.any(|(_, p)| !p.name().is_empty() && p.memory() != 0));
|
||
}
|
||
|
||
#[test]
|
||
fn test_process_disk_usage() {
|
||
use std::fs;
|
||
use std::fs::File;
|
||
use std::io::prelude::*;
|
||
use sysinfo::get_current_pid;
|
||
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
if std::env::var("FREEBSD_CI").is_ok() {
|
||
// For an unknown reason, when running this test on Cirrus CI, it fails. It works perfectly
|
||
// locally though... Dark magic...
|
||
return;
|
||
}
|
||
|
||
fn inner() -> System {
|
||
{
|
||
let mut file = File::create("test.txt").expect("failed to create file");
|
||
file.write_all(b"This is a test file\nwith test data.\n")
|
||
.expect("failed to write to file");
|
||
}
|
||
fs::remove_file("test.txt").expect("failed to remove file");
|
||
// Waiting a bit just in case...
|
||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||
let mut system = System::new();
|
||
assert!(system.processes().is_empty());
|
||
system.refresh_processes(ProcessesToUpdate::All, false);
|
||
assert!(!system.processes().is_empty());
|
||
system
|
||
}
|
||
|
||
let mut system = inner();
|
||
let mut p = system
|
||
.process(get_current_pid().expect("Failed retrieving current pid."))
|
||
.expect("failed to get process");
|
||
|
||
if cfg!(any(target_os = "macos", target_os = "ios")) && p.disk_usage().total_written_bytes == 0
|
||
{
|
||
// For whatever reason, sometimes, mac doesn't work on the first time when running
|
||
// `cargo test`. Two solutions, either run with "cargo test -- --test-threads 1", or
|
||
// check twice...
|
||
system = inner();
|
||
p = system
|
||
.process(get_current_pid().expect("Failed retrieving current pid."))
|
||
.expect("failed to get process");
|
||
}
|
||
|
||
assert!(
|
||
p.disk_usage().total_written_bytes > 0,
|
||
"found {} total written bytes...",
|
||
p.disk_usage().total_written_bytes
|
||
);
|
||
assert!(
|
||
p.disk_usage().written_bytes > 0,
|
||
"found {} written bytes...",
|
||
p.disk_usage().written_bytes
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn cpu_usage_is_not_nan() {
|
||
let mut system = System::new();
|
||
system.refresh_processes(ProcessesToUpdate::All, false);
|
||
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
|
||
// We need `collect` otherwise we can't have mutable access to `system`.
|
||
#[allow(clippy::needless_collect)]
|
||
let first_pids = system
|
||
.processes()
|
||
.iter()
|
||
.take(10)
|
||
.map(|(&pid, _)| pid)
|
||
.collect::<Vec<_>>();
|
||
let mut checked = 0;
|
||
|
||
first_pids.into_iter().for_each(|pid| {
|
||
system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true);
|
||
if let Some(p) = system.process(pid) {
|
||
assert!(!p.cpu_usage().is_nan());
|
||
checked += 1;
|
||
}
|
||
});
|
||
assert!(checked > 0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_process_times() {
|
||
use std::time::{SystemTime, UNIX_EPOCH};
|
||
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let boot_time = System::boot_time();
|
||
assert!(boot_time > 0);
|
||
let mut p = start_proc!("3", "ProcessTimes");
|
||
|
||
let pid = Pid::from_u32(p.id() as _);
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
let mut s = System::new();
|
||
s.refresh_processes(ProcessesToUpdate::All, false);
|
||
p.kill().expect("Unable to kill process.");
|
||
|
||
if let Some(p) = s.process(pid) {
|
||
assert_eq!(p.pid(), pid);
|
||
assert!(p.run_time() >= 1);
|
||
assert!(p.run_time() <= 2);
|
||
assert!(p.start_time() > p.run_time());
|
||
// On linux, for whatever reason, the uptime seems to be older than the boot time, leading
|
||
// to this weird `+ 5` to ensure the test is passing as it should...
|
||
assert!(
|
||
p.start_time() + 5
|
||
> SystemTime::now()
|
||
.duration_since(UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs(),
|
||
);
|
||
assert!(p.start_time() >= boot_time);
|
||
} else {
|
||
panic!("Process not found!");
|
||
}
|
||
}
|
||
|
||
// Checks that `session_id` is working.
|
||
#[test]
|
||
fn test_process_session_id() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let mut s = System::new();
|
||
s.refresh_processes(ProcessesToUpdate::All, false);
|
||
assert!(s.processes().values().any(|p| p.session_id().is_some()));
|
||
}
|
||
|
||
// Checks that `refresh_processes` is removing dead processes.
|
||
#[test]
|
||
fn test_refresh_processes() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let mut p = start_proc!("300", "RefreshProcesses");
|
||
|
||
let pid = Pid::from_u32(p.id() as _);
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
|
||
// Checks that the process is listed as it should.
|
||
let mut s = System::new();
|
||
s.refresh_processes(ProcessesToUpdate::All, false);
|
||
assert!(s.process(pid).is_some());
|
||
// We will use this `System` instance for another check.
|
||
let mut old_system = System::new();
|
||
old_system.refresh_processes(ProcessesToUpdate::All, false);
|
||
assert!(old_system.process(pid).is_some());
|
||
|
||
// Check that the process name is not empty.
|
||
assert!(!s.process(pid).unwrap().name().is_empty());
|
||
|
||
p.kill().expect("Unable to kill process.");
|
||
// We need this, otherwise the process will still be around as a zombie on linux.
|
||
let _ = p.wait();
|
||
// Let's give some time to the system to clean up...
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
|
||
let mut new_system = sysinfo::System::new_with_specifics(RefreshKind::nothing());
|
||
new_system.refresh_processes_specifics(
|
||
ProcessesToUpdate::Some(&[pid]),
|
||
true,
|
||
ProcessRefreshKind::nothing(),
|
||
);
|
||
|
||
// `new_system` should not have this removed process.
|
||
assert!(new_system.process(pid).is_none());
|
||
|
||
s.refresh_processes(ProcessesToUpdate::All, true);
|
||
// Checks that the process isn't listed anymore.
|
||
assert!(s.process(pid).is_none());
|
||
|
||
// And we ensure that refreshing it this way will work too (ie, not listed anymore).
|
||
old_system.refresh_processes_specifics(
|
||
ProcessesToUpdate::Some(&[pid]),
|
||
true,
|
||
ProcessRefreshKind::nothing(),
|
||
);
|
||
|
||
assert!(old_system.process(pid).is_none());
|
||
}
|
||
|
||
// This test ensures that if we refresh only one process, then only this process is removed.
|
||
#[test]
|
||
fn test_refresh_process_doesnt_remove() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let mut p1 = start_proc!("300", "RefreshProcessRemove1");
|
||
let mut p2 = start_proc!("300", "RefreshProcessRemove2");
|
||
|
||
let pid1 = Pid::from_u32(p1.id() as _);
|
||
let pid2 = Pid::from_u32(p2.id() as _);
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
|
||
// Checks that the process is listed as it should.
|
||
let mut s = System::new_with_specifics(
|
||
RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing()),
|
||
);
|
||
s.refresh_processes(ProcessesToUpdate::All, false);
|
||
|
||
assert!(s.process(pid1).is_some());
|
||
assert!(s.process(pid2).is_some());
|
||
|
||
p1.kill().expect("Unable to kill process.");
|
||
p2.kill().expect("Unable to kill process.");
|
||
// We need this, otherwise the process will still be around as a zombie on linux.
|
||
let _ = p1.wait();
|
||
let _ = p2.wait();
|
||
|
||
// Let's give some time to the system to clean up...
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
|
||
assert_eq!(
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[pid1]), false),
|
||
0
|
||
);
|
||
|
||
// We check that none of the two processes were removed.
|
||
assert!(s.process(pid1).is_some());
|
||
assert!(s.process(pid2).is_some());
|
||
|
||
assert_eq!(
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[pid1]), true),
|
||
0
|
||
);
|
||
|
||
// We check that only `pid1` was removed.
|
||
assert!(s.process(pid1).is_none());
|
||
assert!(s.process(pid2).is_some());
|
||
}
|
||
|
||
// Checks that `refresh_processes` is adding and removing task.
|
||
#[test]
|
||
#[cfg(all(
|
||
any(target_os = "linux", target_os = "android"),
|
||
not(feature = "unknown-ci")
|
||
))]
|
||
fn test_refresh_tasks() {
|
||
// Skip if unsupported.
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
|
||
// 1) Spawn a thread that waits on a channel, so we control when it exits.
|
||
let task_name = "controlled_test_thread";
|
||
let (tx, rx) = std::sync::mpsc::channel::<()>();
|
||
|
||
std::thread::Builder::new()
|
||
.name(task_name.to_string())
|
||
.spawn(move || {
|
||
// Wait until the main thread signals we can exit.
|
||
let _ = rx.recv();
|
||
})
|
||
.unwrap();
|
||
|
||
let pid = Pid::from_u32(std::process::id() as _);
|
||
let mut sys = System::new();
|
||
|
||
// Wait until the new thread shows up in the process/tasks list.
|
||
// We do a short loop and check each time by refreshing processes.
|
||
const MAX_POLLS: usize = 20;
|
||
const POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(100);
|
||
|
||
for _ in 0..MAX_POLLS {
|
||
sys.refresh_processes(ProcessesToUpdate::All, /*refresh_users=*/ false);
|
||
|
||
// Check if our thread is present in two ways:
|
||
// (a) via parent's tasks
|
||
// (b) by exact name
|
||
let parent_proc = sys.process(pid);
|
||
let tasks_contain_thread = parent_proc
|
||
.and_then(|p| p.tasks())
|
||
.map(|tids| {
|
||
tids.iter().any(|tid| {
|
||
sys.process(*tid)
|
||
.map(|t| t.name() == task_name)
|
||
.unwrap_or(false)
|
||
})
|
||
})
|
||
.unwrap_or(false);
|
||
|
||
let by_exact_name_exists = sys
|
||
.processes_by_exact_name(task_name.as_ref())
|
||
.next()
|
||
.is_some();
|
||
|
||
if tasks_contain_thread && by_exact_name_exists {
|
||
// We confirmed the thread is now visible
|
||
break;
|
||
}
|
||
std::thread::sleep(POLL_INTERVAL);
|
||
}
|
||
|
||
// At this point we know the task is visible in the system's process/tasks list.
|
||
// Let's validate a few more things:
|
||
// * ProcessRefreshKind::nothing() should have task information.
|
||
// * ProcessRefreshKind::nothing().with_tasks() should have task information.
|
||
// * ProcessRefreshKind::nothing().without_tasks() shouldn't have task information.
|
||
// * ProcessRefreshKind::everything() should have task information.
|
||
// * ProcessRefreshKind::everything() should have task information.
|
||
// * ProcessRefreshKind::everything().without_tasks() should not have task information.
|
||
|
||
let expectations = [
|
||
(ProcessRefreshKind::nothing(), true),
|
||
(ProcessRefreshKind::nothing().with_tasks(), true),
|
||
(ProcessRefreshKind::nothing().without_tasks(), false),
|
||
(ProcessRefreshKind::everything(), true),
|
||
(ProcessRefreshKind::everything().with_tasks(), true),
|
||
(ProcessRefreshKind::everything().without_tasks(), false),
|
||
];
|
||
for (kind, expect_tasks) in expectations.iter() {
|
||
let mut sys_new = System::new();
|
||
sys_new.refresh_processes_specifics(ProcessesToUpdate::All, true, *kind);
|
||
let proc = sys_new.process(pid).unwrap();
|
||
assert_eq!(proc.tasks().is_some(), *expect_tasks);
|
||
}
|
||
|
||
// 3) Signal the thread to exit.
|
||
drop(tx);
|
||
|
||
// 4) Wait until the thread is gone from the system’s process/tasks list.
|
||
for _ in 0..MAX_POLLS {
|
||
sys.refresh_processes(ProcessesToUpdate::All, /*refresh_users=*/ true);
|
||
|
||
let parent_proc = sys.process(pid as sysinfo::Pid);
|
||
let tasks_contain_thread = parent_proc
|
||
.and_then(|p| p.tasks())
|
||
.map(|tids| {
|
||
tids.iter().any(|tid| {
|
||
sys.process(*tid)
|
||
.map(|t| t.name() == task_name)
|
||
.unwrap_or(false)
|
||
})
|
||
})
|
||
.unwrap_or(false);
|
||
|
||
let by_exact_name_exists = sys
|
||
.processes_by_exact_name(task_name.as_ref())
|
||
.next()
|
||
.is_some();
|
||
|
||
// If it's gone from both checks, we're good.
|
||
if !tasks_contain_thread && !by_exact_name_exists {
|
||
break;
|
||
}
|
||
std::thread::sleep(POLL_INTERVAL);
|
||
}
|
||
}
|
||
|
||
// Checks that `refresh_process` is removing dead processes when asked.
|
||
#[test]
|
||
fn test_refresh_process() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let mut p = start_proc!("300", "RefreshProcess");
|
||
|
||
let pid = Pid::from_u32(p.id() as _);
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
|
||
// Checks that the process is listed as it should.
|
||
let mut s = System::new();
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false);
|
||
assert!(s.process(pid).is_some());
|
||
|
||
// Check that the process name is not empty.
|
||
assert!(!s.process(pid).unwrap().name().is_empty());
|
||
|
||
p.kill().expect("Unable to kill process.");
|
||
// We need this, otherwise the process will still be around as a zombie on linux.
|
||
let _ = p.wait();
|
||
// Let's give some time to the system to clean up...
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
|
||
assert_eq!(
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false),
|
||
0
|
||
);
|
||
// Checks that the process is still listed.
|
||
assert!(s.process(pid).is_some());
|
||
|
||
assert_eq!(
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[pid]), true),
|
||
0
|
||
);
|
||
// Checks that the process is not listed anymore.
|
||
assert!(s.process(pid).is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn test_wait_child() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let p = start_proc!("300", "WaitChild");
|
||
|
||
let before = std::time::Instant::now();
|
||
let pid = Pid::from_u32(p.id() as _);
|
||
|
||
let mut s = System::new();
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false);
|
||
let process = s.process(pid).unwrap();
|
||
|
||
// Kill the child process.
|
||
process.kill();
|
||
// Wait for child process should work.
|
||
process.wait();
|
||
|
||
// Child process should not be present.
|
||
assert_eq!(
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[pid]), true),
|
||
0
|
||
);
|
||
assert!(before.elapsed() < std::time::Duration::from_millis(1000));
|
||
}
|
||
|
||
#[test]
|
||
fn test_wait_non_child() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
|
||
let before = std::time::Instant::now();
|
||
|
||
// spawn non child process.
|
||
let p = if !cfg!(target_os = "linux") {
|
||
return;
|
||
} else {
|
||
std::process::Command::new("setsid")
|
||
.arg("-w")
|
||
.arg("sleep")
|
||
.arg("2")
|
||
.stdout(std::process::Stdio::null())
|
||
.spawn()
|
||
.unwrap()
|
||
};
|
||
let pid = Pid::from_u32(p.id());
|
||
|
||
let mut s = System::new();
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false);
|
||
let process = s.process(pid).expect("Process not found!");
|
||
|
||
// Wait for a non child process.
|
||
process.wait();
|
||
|
||
// Child process should not be present.
|
||
assert_eq!(
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[pid]), true),
|
||
0
|
||
);
|
||
|
||
// should wait for 2s.
|
||
assert!(
|
||
before.elapsed() > std::time::Duration::from_millis(1900),
|
||
"Elapsed time {:?} is not greater than 1900ms",
|
||
before.elapsed()
|
||
);
|
||
assert!(
|
||
before.elapsed() < std::time::Duration::from_millis(3000),
|
||
"Elapsed time {:?} is not less than 3000ms",
|
||
before.elapsed()
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_process_iterator_lifetimes() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
|
||
let s = System::new_with_specifics(
|
||
RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing()),
|
||
);
|
||
|
||
let process: Option<&sysinfo::Process>;
|
||
{
|
||
let name = String::from("");
|
||
// errors before PR #904: name does not live long enough
|
||
process = s.processes_by_name(name.as_ref()).next();
|
||
}
|
||
process.unwrap();
|
||
|
||
let process: Option<&sysinfo::Process>;
|
||
{
|
||
// worked fine before and after: &'static str lives longer than System, error couldn't appear
|
||
process = s.processes_by_name("".as_ref()).next();
|
||
}
|
||
process.unwrap();
|
||
}
|
||
|
||
// Regression test for <https://github.com/GuillaumeGomez/sysinfo/issues/918>.
|
||
#[test]
|
||
fn test_process_cpu_usage() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
|
||
let mut sys = System::new_all();
|
||
std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
|
||
sys.refresh_all();
|
||
|
||
let max_usage = sys.cpus().len() as f32 * 100.;
|
||
|
||
for process in sys.processes().values() {
|
||
assert!(process.cpu_usage() <= max_usage);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_process_creds() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
|
||
let mut sys = System::new_all();
|
||
sys.refresh_all();
|
||
|
||
// Just ensure there is at least one process on the system whose credentials can be retrieved.
|
||
assert!(sys.processes().values().any(|process| {
|
||
if process.user_id().is_none() {
|
||
return false;
|
||
}
|
||
|
||
#[cfg(not(windows))]
|
||
{
|
||
if process.group_id().is_none()
|
||
|| process.effective_user_id().is_none()
|
||
|| process.effective_group_id().is_none()
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
true
|
||
}));
|
||
|
||
// On Windows, make sure no process has real group ID and no effective IDs.
|
||
#[cfg(windows)]
|
||
assert!(sys.processes().values().all(|process| {
|
||
if process.group_id().is_some()
|
||
|| process.effective_user_id().is_some()
|
||
|| process.effective_group_id().is_some()
|
||
{
|
||
return false;
|
||
}
|
||
|
||
true
|
||
}));
|
||
}
|
||
|
||
// This test ensures that only the requested information is retrieved.
|
||
#[test]
|
||
fn test_process_specific_refresh() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
|
||
fn check_empty(s: &System, pid: Pid) {
|
||
let p = s.process(pid).unwrap();
|
||
|
||
// Name should never be empty.
|
||
assert!(!p.name().is_empty());
|
||
if cfg!(target_os = "windows") {
|
||
assert_eq!(p.user_id(), None);
|
||
}
|
||
assert_eq!(p.environ().len(), 0);
|
||
assert_eq!(p.cmd().len(), 0);
|
||
assert_eq!(p.exe(), None);
|
||
assert_eq!(p.cwd(), None);
|
||
assert_eq!(p.root(), None);
|
||
assert_eq!(p.memory(), 0);
|
||
assert_eq!(p.virtual_memory(), 0);
|
||
// These two won't be checked, too much lazyness in testing them...
|
||
assert_eq!(p.disk_usage(), sysinfo::DiskUsage::default());
|
||
assert_eq!(p.cpu_usage(), 0.);
|
||
}
|
||
|
||
let mut s = System::new();
|
||
let pid = Pid::from_u32(std::process::id());
|
||
|
||
macro_rules! update_specific_and_check {
|
||
(memory) => {
|
||
s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing());
|
||
{
|
||
let p = s.process(pid).unwrap();
|
||
assert_eq!(p.memory(), 0, "failed 0 check for memory");
|
||
assert_eq!(p.virtual_memory(), 0, "failed 0 check for virtual memory");
|
||
}
|
||
s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing().with_memory());
|
||
{
|
||
let p = s.process(pid).unwrap();
|
||
assert_ne!(p.memory(), 0, "failed non-0 check for memory");
|
||
assert_ne!(p.virtual_memory(), 0, "failed non-0 check for virtual memory");
|
||
}
|
||
// And now we check that re-refreshing nothing won't remove the
|
||
// information.
|
||
s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing());
|
||
{
|
||
let p = s.process(pid).unwrap();
|
||
assert_ne!(p.memory(), 0, "failed non-0 check (number 2) for memory");
|
||
assert_ne!(p.virtual_memory(), 0, "failed non-0 check(number 2) for virtual memory");
|
||
}
|
||
};
|
||
($name:ident, $method:ident, $($extra:tt)+) => {
|
||
s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing());
|
||
{
|
||
let p = s.process(pid).unwrap();
|
||
assert_eq!(
|
||
p.$name()$($extra)+,
|
||
concat!("failed 0 check check for ", stringify!($name)),
|
||
);
|
||
}
|
||
s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing().$method(UpdateKind::Always));
|
||
{
|
||
let p = s.process(pid).unwrap();
|
||
assert_ne!(
|
||
p.$name()$($extra)+,
|
||
concat!("failed non-0 check check for ", stringify!($name)),);
|
||
}
|
||
// And now we check that re-refreshing nothing won't remove the
|
||
// information.
|
||
s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing());
|
||
{
|
||
let p = s.process(pid).unwrap();
|
||
assert_ne!(
|
||
p.$name()$($extra)+,
|
||
concat!("failed non-0 check (number 2) check for ", stringify!($name)),);
|
||
}
|
||
}
|
||
}
|
||
|
||
s.refresh_processes_specifics(
|
||
ProcessesToUpdate::Some(&[pid]),
|
||
false,
|
||
ProcessRefreshKind::nothing(),
|
||
);
|
||
check_empty(&s, pid);
|
||
|
||
s.refresh_processes_specifics(
|
||
ProcessesToUpdate::Some(&[pid]),
|
||
false,
|
||
ProcessRefreshKind::nothing(),
|
||
);
|
||
check_empty(&s, pid);
|
||
|
||
update_specific_and_check!(memory);
|
||
update_specific_and_check!(environ, with_environ, .len(), 0);
|
||
update_specific_and_check!(cmd, with_cmd, .len(), 0);
|
||
if !cfg!(any(
|
||
target_os = "macos",
|
||
target_os = "ios",
|
||
feature = "apple-sandbox",
|
||
)) {
|
||
update_specific_and_check!(root, with_root, , None);
|
||
}
|
||
update_specific_and_check!(exe, with_exe, , None);
|
||
update_specific_and_check!(cwd, with_cwd, , None);
|
||
}
|
||
|
||
#[test]
|
||
fn test_refresh_pids() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let self_pid = sysinfo::get_current_pid().expect("failed to get current pid");
|
||
let mut s = System::new();
|
||
|
||
let mut p = start_proc!("3", "RefreshPids");
|
||
|
||
let child_pid = Pid::from_u32(p.id() as _);
|
||
let pids = &[child_pid, self_pid];
|
||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||
s.refresh_processes(ProcessesToUpdate::Some(pids), false);
|
||
p.kill().expect("Unable to kill process.");
|
||
|
||
assert_eq!(s.processes().len(), 2);
|
||
for pid in s.processes().keys() {
|
||
assert!(pids.contains(pid));
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_process_run_time() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let mut s = System::new();
|
||
let current_pid = sysinfo::get_current_pid().expect("failed to get current pid");
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[current_pid]), false);
|
||
let run_time = s.process(current_pid).expect("no process found").run_time();
|
||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[current_pid]), true);
|
||
let new_run_time = s.process(current_pid).expect("no process found").run_time();
|
||
assert!(
|
||
new_run_time > run_time,
|
||
"{} not superior to {}",
|
||
new_run_time,
|
||
run_time
|
||
);
|
||
}
|
||
|
||
// Test that if the parent of a process is removed, then the child PID will be
|
||
// updated as well.
|
||
#[test]
|
||
fn test_parent_change() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") || cfg!(windows) {
|
||
// Windows never updates its parent PID so no need to check anything.
|
||
return;
|
||
}
|
||
|
||
let file_name = "target/test_binary2";
|
||
build_test_binary(file_name);
|
||
let mut p = std::process::Command::new(format!("./{file_name}"))
|
||
.arg("1")
|
||
.spawn()
|
||
.unwrap();
|
||
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
|
||
let pid = Pid::from_u32(p.id() as _);
|
||
let mut s = System::new();
|
||
s.refresh_processes(ProcessesToUpdate::All, false);
|
||
|
||
assert_eq!(
|
||
s.process(pid).expect("process was not created").parent(),
|
||
sysinfo::get_current_pid().ok(),
|
||
);
|
||
|
||
let child_pid = s
|
||
.processes()
|
||
.iter()
|
||
.find(|(_, proc_)| proc_.parent() == Some(pid))
|
||
.map(|(pid, _)| *pid)
|
||
.expect("failed to get child process");
|
||
|
||
// Waiting for the parent process to stop.
|
||
p.wait().expect("wait failed");
|
||
|
||
s.refresh_processes(ProcessesToUpdate::All, true);
|
||
// Parent should not be around anymore.
|
||
assert!(s.process(pid).is_none());
|
||
|
||
let child = s.process(child_pid).expect("child is dead");
|
||
// Child should have a different parent now.
|
||
assert_ne!(child.parent(), Some(pid));
|
||
|
||
// We kill the child to clean up.
|
||
child.kill();
|
||
}
|
||
|
||
// We want to ensure that if `System::refresh_process*` methods are called
|
||
// one after the other, it won't badly impact the CPU usage computation.
|
||
#[test]
|
||
fn test_multiple_single_process_refresh() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") || cfg!(windows) {
|
||
// Windows never updates its parent PID so no need to check anything.
|
||
return;
|
||
}
|
||
|
||
let file_name = "target/test_binary3";
|
||
build_test_binary(file_name);
|
||
let mut p_a = std::process::Command::new(format!("./{file_name}"))
|
||
.arg("1")
|
||
.spawn()
|
||
.unwrap();
|
||
let mut p_b = std::process::Command::new(format!("./{file_name}"))
|
||
.arg("1")
|
||
.spawn()
|
||
.unwrap();
|
||
|
||
let pid_a = Pid::from_u32(p_a.id() as _);
|
||
let pid_b = Pid::from_u32(p_b.id() as _);
|
||
|
||
let mut s = System::new();
|
||
let process_refresh_kind = ProcessRefreshKind::nothing().with_cpu();
|
||
s.refresh_processes_specifics(
|
||
ProcessesToUpdate::Some(&[pid_a]),
|
||
false,
|
||
process_refresh_kind,
|
||
);
|
||
s.refresh_processes_specifics(
|
||
ProcessesToUpdate::Some(&[pid_b]),
|
||
false,
|
||
process_refresh_kind,
|
||
);
|
||
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
s.refresh_processes_specifics(
|
||
ProcessesToUpdate::Some(&[pid_a]),
|
||
true,
|
||
process_refresh_kind,
|
||
);
|
||
s.refresh_processes_specifics(
|
||
ProcessesToUpdate::Some(&[pid_b]),
|
||
true,
|
||
process_refresh_kind,
|
||
);
|
||
|
||
let cpu_a = s.process(pid_a).unwrap().cpu_usage();
|
||
let cpu_b = s.process(pid_b).unwrap().cpu_usage();
|
||
|
||
p_a.kill().expect("failed to kill process a");
|
||
p_b.kill().expect("failed to kill process b");
|
||
|
||
let _ = p_a.wait();
|
||
let _ = p_b.wait();
|
||
|
||
assert!(cpu_b - 5. < cpu_a && cpu_b + 5. > cpu_a);
|
||
}
|
||
|
||
#[test]
|
||
fn accumulated_cpu_time() {
|
||
fn generate_cpu_usage() {
|
||
use std::sync::atomic::{AtomicBool, Ordering};
|
||
|
||
let atomic = std::sync::Arc::new(AtomicBool::new(false));
|
||
let thread_atomic = atomic.clone();
|
||
std::thread::spawn(move || {
|
||
while !thread_atomic.load(Ordering::Relaxed) {
|
||
System::new_all();
|
||
}
|
||
});
|
||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||
atomic.store(true, Ordering::Relaxed);
|
||
}
|
||
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM
|
||
|| cfg!(feature = "apple-sandbox")
|
||
|| cfg!(target_os = "freebsd")
|
||
{
|
||
return;
|
||
}
|
||
|
||
let mut s = System::new();
|
||
let current_pid = sysinfo::get_current_pid().expect("failed to get current pid");
|
||
let refresh_kind = ProcessRefreshKind::nothing().with_cpu();
|
||
generate_cpu_usage();
|
||
s.refresh_processes_specifics(ProcessesToUpdate::Some(&[current_pid]), false, refresh_kind);
|
||
let acc_time = s
|
||
.process(current_pid)
|
||
.expect("no process found")
|
||
.accumulated_cpu_time();
|
||
assert_ne!(acc_time, 0);
|
||
|
||
generate_cpu_usage();
|
||
s.refresh_processes_specifics(ProcessesToUpdate::Some(&[current_pid]), true, refresh_kind);
|
||
let new_acc_time = s
|
||
.process(current_pid)
|
||
.expect("no process found")
|
||
.accumulated_cpu_time();
|
||
assert!(
|
||
new_acc_time > acc_time,
|
||
"{} not superior to {}",
|
||
new_acc_time,
|
||
acc_time
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_exists() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
|
||
let file_name = "target/test_binary4";
|
||
build_test_binary(file_name);
|
||
let mut p = std::process::Command::new(format!("./{file_name}"))
|
||
.arg("1")
|
||
.spawn()
|
||
.unwrap();
|
||
let pid = Pid::from_u32(p.id() as _);
|
||
|
||
let mut s = System::new();
|
||
let process_refresh_kind = ProcessRefreshKind::nothing().with_memory();
|
||
s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, process_refresh_kind);
|
||
assert!(s.process(pid).unwrap().exists());
|
||
|
||
p.kill().expect("Unable to kill process.");
|
||
// We need this, otherwise the process will still be around as a zombie on linux.
|
||
let _ = p.wait();
|
||
// Let's give some time to the system to clean up...
|
||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||
|
||
s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, process_refresh_kind);
|
||
assert!(!s.process(pid).unwrap().exists());
|
||
}
|
||
|
||
#[cfg(target_os = "linux")]
|
||
#[test]
|
||
fn test_tasks() {
|
||
use std::collections::HashSet;
|
||
|
||
fn get_tasks(system: &System) -> HashSet<Pid> {
|
||
let pid_to_process = system.processes();
|
||
let mut task_pids: HashSet<Pid> = HashSet::new();
|
||
for process in pid_to_process.values() {
|
||
if let Some(tasks) = process.tasks() {
|
||
task_pids.extend(tasks);
|
||
}
|
||
}
|
||
task_pids
|
||
}
|
||
|
||
let mut system = System::new_with_specifics(RefreshKind::nothing());
|
||
system.refresh_processes_specifics(ProcessesToUpdate::All, true, ProcessRefreshKind::nothing());
|
||
|
||
// Spawn a thread to increase the task count
|
||
let scheduler = std::thread::spawn(move || {
|
||
system.refresh_processes_specifics(
|
||
ProcessesToUpdate::All,
|
||
true,
|
||
ProcessRefreshKind::nothing(),
|
||
);
|
||
|
||
let mut system_new = System::new_with_specifics(RefreshKind::nothing());
|
||
system_new.refresh_processes_specifics(
|
||
ProcessesToUpdate::All,
|
||
true,
|
||
ProcessRefreshKind::nothing(),
|
||
);
|
||
|
||
assert_eq!(get_tasks(&system), get_tasks(&system_new));
|
||
});
|
||
scheduler.join().expect("Scheduler panicked");
|
||
}
|
||
|
||
#[test]
|
||
fn open_files() {
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
|
||
return;
|
||
}
|
||
let pid = sysinfo::get_current_pid().expect("failed to get current pid");
|
||
let _file =
|
||
std::fs::File::create(std::env::temp_dir().join("sysinfo-open-files.test")).unwrap();
|
||
let mut s = System::new();
|
||
s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false);
|
||
let cur_process = s.process(pid).unwrap();
|
||
assert!(cur_process
|
||
.open_files()
|
||
.is_some_and(|open_files| open_files > 0));
|
||
assert!(cur_process
|
||
.open_files_limit()
|
||
.is_some_and(|open_files| open_files > 0));
|
||
}
|