// Take a look at the license at the top of the repository in the LICENSE file.

#![cfg(feature = "system")]
#![allow(clippy::assertions_on_constants)]

use sysinfo::{ProcessesToUpdate, System};

#[test]
fn test_refresh_system() {
    let mut sys = System::new();
    sys.refresh_memory();
    sys.refresh_cpu_usage();
    // We don't want to test on unsupported systems.
    if sysinfo::IS_SUPPORTED_SYSTEM {
        assert!(sys.total_memory() != 0);
        assert!(sys.free_memory() != 0);
    }
    assert!(sys.total_memory() >= sys.free_memory());
    assert!(sys.total_swap() >= sys.free_swap());
}

#[test]
fn test_refresh_process() {
    let mut sys = System::new();
    assert!(sys.processes().is_empty(), "no process should be listed!");
    // We don't want to test on unsupported systems.

    #[cfg(not(feature = "apple-sandbox"))]
    if sysinfo::IS_SUPPORTED_SYSTEM {
        assert_eq!(
            sys.refresh_processes(
                ProcessesToUpdate::Some(&[
                    sysinfo::get_current_pid().expect("failed to get current pid")
                ]),
                false
            ),
            1,
            "process not listed",
        );
        // Ensure that the process was really added to the list!
        assert!(
            sys.process(sysinfo::get_current_pid().expect("failed to get current pid"))
                .is_some()
        );
    }
}

#[test]
fn test_get_process() {
    let mut sys = System::new();
    sys.refresh_processes(ProcessesToUpdate::All, false);
    let current_pid = match sysinfo::get_current_pid() {
        Ok(pid) => pid,
        _ => {
            if !sysinfo::IS_SUPPORTED_SYSTEM {
                return;
            }
            panic!("get_current_pid should work!");
        }
    };
    if let Some(p) = sys.process(current_pid) {
        assert!(p.memory() > 0);
    } else {
        #[cfg(not(feature = "apple-sandbox"))]
        assert!(!sysinfo::IS_SUPPORTED_SYSTEM);
    }
}

#[test]
fn check_if_send_and_sync() {
    trait Foo {
        fn foo(&self) {}
    }
    impl<T> Foo for T where T: Send {}

    trait Bar {
        fn bar(&self) {}
    }

    impl<T> Bar for T where T: Sync {}

    let mut sys = System::new();
    sys.refresh_processes(ProcessesToUpdate::All, false);
    let current_pid = match sysinfo::get_current_pid() {
        Ok(pid) => pid,
        _ => {
            if !sysinfo::IS_SUPPORTED_SYSTEM {
                return;
            }
            panic!("get_current_pid should work!");
        }
    };
    if let Some(p) = sys.process(current_pid) {
        p.foo(); // If this doesn't compile, it'll simply mean that the Process type
        // doesn't implement the Send trait.
        p.bar(); // If this doesn't compile, it'll simply mean that the Process type
    // doesn't implement the Sync trait.
    } else {
        #[cfg(not(feature = "apple-sandbox"))]
        assert!(!sysinfo::IS_SUPPORTED_SYSTEM);
    }
}

#[test]
fn check_hostname_has_no_nuls() {
    if let Some(hostname) = System::host_name() {
        assert!(!hostname.contains('\u{0}'))
    }
}

#[test]
fn check_uptime() {
    let uptime = System::uptime();
    if sysinfo::IS_SUPPORTED_SYSTEM {
        std::thread::sleep(std::time::Duration::from_millis(1000));
        let new_uptime = System::uptime();
        assert!(uptime < new_uptime);
    }
}

#[test]
fn check_boot_time() {
    if sysinfo::IS_SUPPORTED_SYSTEM {
        assert_ne!(System::boot_time(), 0);
    }
}

// This test is used to ensure that the CPU usage computation isn't completely going off
// when refreshing it too frequently (ie, multiple times in a row in a very small interval).
#[test]
fn test_consecutive_cpu_usage_update() {
    use std::sync::Arc;
    use std::sync::atomic::{AtomicBool, Ordering};
    use std::time::Duration;
    use sysinfo::{Pid, ProcessRefreshKind, System};

    if !sysinfo::IS_SUPPORTED_SYSTEM {
        return;
    }
    if std::env::var("NETBSD_CI").is_ok() {
        // FIXME
        return;
    }

    let mut sys = System::new_all();
    assert!(!sys.cpus().is_empty());
    sys.refresh_processes_specifics(
        ProcessesToUpdate::All,
        true,
        ProcessRefreshKind::nothing().with_cpu(),
    );

    let stop = Arc::new(AtomicBool::new(false));
    // Spawning a few threads to ensure that it will actually have an impact on the CPU usage.
    for it in 0..sys.cpus().len() / 2 + 1 {
        let stop_c = Arc::clone(&stop);
        std::thread::spawn(move || {
            while !stop_c.load(Ordering::Relaxed) {
                if it != 0 {
                    // The first thread runs at 100% to be sure it'll be noticeable.
                    std::thread::sleep(Duration::from_millis(1));
                }
            }
        });
    }

    let mut pids = sys.processes().keys().copied().take(2).collect::<Vec<_>>();
    let pid = std::process::id();
    pids.push(Pid::from_u32(pid));
    assert_eq!(pids.len(), 3);

    for it in 0..3 {
        std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL + Duration::from_millis(1));
        for pid in &pids {
            sys.refresh_processes_specifics(
                ProcessesToUpdate::Some(&[*pid]),
                true,
                ProcessRefreshKind::nothing().with_cpu(),
            );
        }
        // To ensure that Linux doesn't give too high numbers.
        assert!(
            sys.process(pids[2]).unwrap().cpu_usage() < sys.cpus().len() as f32 * 100.,
            "using ALL CPU: failed at iteration {it}",
        );
        // To ensure it's not 0 either.
        assert!(
            sys.process(pids[2]).unwrap().cpu_usage() > 0.,
            "using NO CPU: failed at iteration {it}",
        );
    }
    stop.store(false, Ordering::Relaxed);
}

#[test]
fn test_refresh_memory() {
    if !sysinfo::IS_SUPPORTED_SYSTEM {
        return;
    }
    // On linux, since it's the same file, memory information are always retrieved.
    let is_linux = cfg!(any(target_os = "linux", target_os = "android"));
    let mut s = System::new();
    assert_eq!(s.total_memory(), 0);
    assert_eq!(s.free_memory(), 0);

    s.refresh_memory_specifics(sysinfo::MemoryRefreshKind::nothing().with_ram());
    assert_ne!(s.total_memory(), 0);
    assert_ne!(s.free_memory(), 0);

    if is_linux {
        assert_ne!(s.total_swap(), 0);
        assert_ne!(s.free_swap(), 0);
    } else {
        assert_eq!(s.total_swap(), 0);
        assert_eq!(s.free_swap(), 0);
    }

    let mut s = System::new();
    assert_eq!(s.total_swap(), 0);
    assert_eq!(s.free_swap(), 0);

    if std::env::var("APPLE_CI").is_ok() {
        // Apparently there is no swap for macOS in CIs so can't run further than this point.
        return;
    }

    s.refresh_memory_specifics(sysinfo::MemoryRefreshKind::nothing().with_swap());
    // SWAP can be 0 on macOS so this test is disabled
    #[cfg(not(target_os = "macos"))]
    {
        assert_ne!(s.total_swap(), 0);
        assert_ne!(s.free_swap(), 0);
    }

    if is_linux {
        assert_ne!(s.total_memory(), 0);
        assert_ne!(s.free_memory(), 0);
    } else {
        assert_eq!(s.total_memory(), 0);
        assert_eq!(s.free_memory(), 0);
    }

    let mut s = System::new();
    s.refresh_memory();
    // SWAP can be 0 on macOS so this test is disabled
    #[cfg(not(target_os = "macos"))]
    {
        assert_ne!(s.total_swap(), 0);
        assert_ne!(s.free_swap(), 0);
    }
    assert_ne!(s.total_memory(), 0);
    assert_ne!(s.free_memory(), 0);
}
