rigtop 0.4.0

btop-inspired terminal system monitor with storage health (SMART, btrfs RAID, mdadm), per-process GPU usage and power draw
//! End-to-end tests of `rigtop --agent` with `RemoteSource`, running the
//! agent binary directly instead of through ssh.

use std::io::{BufRead, BufReader};
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};

use rigtop_core::DataSource;
use rigtop_core::remote::{RemoteSource, RemoteTarget};

fn agent_source() -> RemoteSource {
    RemoteSource::new(RemoteTarget {
        name: "local-agent".to_owned(),
        program: env!("CARGO_BIN_EXE_rigtop").to_owned(),
        args: vec!["--agent".to_owned()],
    })
}

#[test]
fn remote_source_receives_snapshots_and_limits_processes() {
    let mut source = agent_source();
    source.set_interval(Duration::from_millis(300));
    let first = source.poll().expect("first snapshot");
    assert!(first.process_count > 1);
    assert!(!first.host.hostname.is_empty());

    source.set_process_limit(Some(5));
    // The limit applies from the next snapshot the agent takes.
    let deadline = Instant::now() + Duration::from_secs(10);
    loop {
        let snapshot = source.poll().expect("snapshot");
        if snapshot.processes.len() <= 5 {
            assert!(snapshot.process_count > 5, "the total is still reported");
            break;
        }
        assert!(Instant::now() < deadline, "process limit not applied");
    }
}

#[cfg(unix)]
#[test]
fn remote_source_kills_processes_on_the_agent_host() {
    use rigtop_core::ProcessSignal;

    let mut child = Command::new("sleep").arg("30").spawn().unwrap();
    let mut source = agent_source();
    source.poll().expect("connected");
    source
        .signal_process(child.id(), ProcessSignal::Kill)
        .expect("signal through the agent");
    assert!(!child.wait().unwrap().success());
    // Errors from the agent come back too.
    let err = source
        .signal_process(child.id(), ProcessSignal::Kill)
        .unwrap_err();
    assert!(err.to_string().contains("remote"), "{err}");
}

#[test]
fn agent_exits_when_stdin_closes() {
    let mut agent = Command::new(env!("CARGO_BIN_EXE_rigtop"))
        .arg("--agent")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();
    let mut stdout = BufReader::new(agent.stdout.take().unwrap());
    let mut hello = String::new();
    stdout.read_line(&mut hello).unwrap();
    assert!(hello.contains(r#""type":"hello""#), "{hello}");

    drop(agent.stdin.take());
    let deadline = Instant::now() + Duration::from_secs(10);
    loop {
        if let Some(status) = agent.try_wait().unwrap() {
            assert!(status.success(), "{status}");
            break;
        }
        assert!(
            Instant::now() < deadline,
            "agent kept running after stdin closed"
        );
        std::thread::sleep(Duration::from_millis(50));
    }
}