procutils-top 0.2.0

Display Linux processes in real time
Documentation
//! Snapshot tests for `top -bn1` against a programmatically-built `/proc`.
//!
//! Only batch mode is exercised here; the interactive ratatui UI isn't
//! readily snapshot-testable through a subprocess.

use procutils_testutil::{Builder, ProcRoot, ProcessFixture};

const BIN: &str = env!("CARGO_BIN_EXE_top");

/// Same shape as the ps fixture: init / kthreadd / sshd / bash / vim,
/// with explicit cpu times so the cumulative %CPU columns stay stable.
fn small_system() -> ProcRoot {
    let init = ProcessFixture {
        ppid: 0,
        pgrp: 1,
        session: 1,
        uid: 0,
        gid: 0,
        cmdline: "/sbin/init\0splash".to_string(),
        ..ProcessFixture::new(1, "systemd")
    };
    let kthreadd = ProcessFixture {
        ppid: 0,
        pgrp: 0,
        session: 0,
        uid: 0,
        gid: 0,
        cmdline: String::new(),
        ..ProcessFixture::new(2, "kthreadd")
    };
    let sshd = ProcessFixture {
        ppid: 1,
        pgrp: 100,
        session: 100,
        uid: 0,
        gid: 0,
        cmdline: "/usr/sbin/sshd\0-D".to_string(),
        ..ProcessFixture::new(100, "sshd")
    };
    let bash = ProcessFixture {
        ppid: 100,
        pgrp: 200,
        session: 200,
        tty_nr: 34816,
        tpgid: 300,
        uid: 1000,
        gid: 1000,
        cmdline: "-bash".to_string(),
        utime: 100,
        stime: 50,
        ..ProcessFixture::new(200, "bash")
    };
    let vim = ProcessFixture {
        ppid: 200,
        pgrp: 300,
        session: 200,
        tty_nr: 34816,
        tpgid: 300,
        uid: 1000,
        gid: 1000,
        cmdline: "vim\0README.md".to_string(),
        utime: 200,
        stime: 30,
        rss: 2000,
        vsize: 50_000_000,
        ..ProcessFixture::new(300, "vim")
    };

    Builder::new()
        .process(init)
        .process(kthreadd)
        .process(sshd)
        .process(bash)
        .process(vim)
        .finish()
        .expect("build fixture")
}

#[track_caller]
fn run(root: &ProcRoot, args: &[&str]) -> String {
    if !procutils_testutil::unshare_supported() {
        return "<unshare unavailable>".to_string();
    }
    let mounts = procutils_testutil::proc_only(root.path());
    procutils_testutil::run_ok(BIN, &mounts, args)
}

/// Redactions for fields that drift between runs:
///   - the wall-clock "top - HH:MM:SS" header
///   - the user count read from the host's utmp (we don't bind-mount it)
macro_rules! assert_top_snapshot {
    ($value:expr) => {
        insta::with_settings!({
            filters => vec![
                (r"top - \d{2}:\d{2}:\d{2}", "top - [TIME]"),
                (r", \d+ users?,", ", [N] users,"),
            ],
        }, {
            insta::assert_snapshot!($value);
        });
    };
}

#[test]
fn batch_default() {
    let root = small_system();
    assert_top_snapshot!(run(&root, &["-b", "-n", "1"]));
}

#[test]
fn batch_filter_pid() {
    let root = small_system();
    assert_top_snapshot!(run(&root, &["-b", "-n", "1", "-p", "200,300"]));
}

#[test]
fn batch_filter_user() {
    let root = small_system();
    assert_top_snapshot!(run(&root, &["-b", "-n", "1", "-u", "0"]));
}