running-process 4.10.15

Subprocess and PTY runtime for the running-process project
Documentation
#![cfg(target_os = "linux")]

use std::time::Duration;

use running_process::{
    CommandSpec, NativeProcess, ObservationGrade, ObservationPolicy, ProcessConfig, ProcessWatch,
    ProcessWatchRead, StderrMode, StdinMode,
};

#[test]
fn exact_trace_observes_rapid_execs_and_normalizes_minus_one() {
    let exec_watch = ProcessWatch::on_exec(
        Some("true".to_owned()),
        None,
        None,
        None,
        Duration::ZERO,
        "rapid-true",
    )
    .unwrap();
    let any_exec_watch = ProcessWatch::on_exec(
        None,
        None,
        None,
        None,
        Duration::ZERO,
        "descendant-execs-only",
    )
    .unwrap();
    let exit_watch = ProcessWatch::on_exit(
        Some(-1),
        None,
        None,
        None,
        Some(1),
        Duration::ZERO,
        "minus-one",
    )
    .unwrap();
    let config = ProcessConfig {
        command: CommandSpec::Argv(vec![
            "/bin/sh".to_owned(),
            "-c".to_owned(),
            "i=0; while [ $i -lt 20 ]; do /bin/true; i=$((i+1)); done; /bin/sh -c 'exit 255'; exit 0"
                .to_owned(),
        ]),
        cwd: None,
        env: None,
        capture: false,
        stderr_mode: StderrMode::Stdout,
        creationflags: None,
        create_process_group: false,
        stdin_mode: StdinMode::Null,
        nice: None,
        address_space_limit_bytes: None,
    };
    let (process, subscriber) = NativeProcess::with_process_watches(
        config,
        vec![exec_watch, any_exec_watch, exit_watch],
        ObservationPolicy::RequireExact,
    )
    .unwrap();
    let mut cursor = subscriber.cursor();
    process.start().unwrap();
    assert_eq!(process.wait(Some(Duration::from_secs(10))).unwrap(), 0);

    let matches = subscriber.snapshot();
    let execs = matches
        .iter()
        .filter(|item| item.watch.label == "rapid-true")
        .count();
    assert_eq!(execs, 20, "exact tracing lost a rapid /bin/true exec");
    assert_eq!(
        matches
            .iter()
            .filter(|item| item.watch.label == "descendant-execs-only")
            .count(),
        21,
        "the launched-tree watch must not publish the root's initial exec",
    );
    assert!(matches.iter().any(|item| item.watch.label == "minus-one"));
    assert!(matches
        .iter()
        .filter(|item| item.watch.label == "rapid-true")
        .all(|item| item.event.observation_grade == ObservationGrade::ExactTrace));

    let mut cursor_matches = 0;
    loop {
        match cursor.read_next(Some(Duration::from_secs(1))) {
            ProcessWatchRead::Match(_) => cursor_matches += 1,
            ProcessWatchRead::Loss(loss) => panic!("unexpected trace loss: {}", loss.reason),
            ProcessWatchRead::Eof => break,
            ProcessWatchRead::Gap(gap) => panic!("unexpected cursor gap: {gap:?}"),
            ProcessWatchRead::Timeout => panic!("watch cursor did not reach EOF"),
        }
    }
    assert_eq!(cursor_matches, matches.len());
}

#[test]
fn exact_trace_preserves_negative_signal_return_codes() {
    let watch = ProcessWatch::on_spawn(None, Some(1), Duration::ZERO, "enable-exact").unwrap();
    let config = ProcessConfig {
        command: CommandSpec::Argv(vec![
            "/bin/sh".to_owned(),
            "-c".to_owned(),
            "kill -KILL $$".to_owned(),
        ]),
        cwd: None,
        env: None,
        capture: false,
        stderr_mode: StderrMode::Stdout,
        creationflags: None,
        create_process_group: false,
        stdin_mode: StdinMode::Null,
        nice: None,
        address_space_limit_bytes: None,
    };
    let (process, _subscriber) =
        NativeProcess::with_process_watches(config, vec![watch], ObservationPolicy::RequireExact)
            .unwrap();
    process.start().unwrap();
    assert_eq!(process.wait(Some(Duration::from_secs(10))).unwrap(), -9);
}