procutils-pkill 0.2.0

Signal processes based on name and other attributes
Documentation
//! Live-process tests: spawn a real helper inside an unshared PID
//! namespace and verify that `pkill` actually delivers signals.
//!
//! Skipped automatically on systems without unprivileged user + PID
//! namespaces (some sandboxed CI runners).

use procutils_testutil::LiveSession;

const PKILL: &str = env!("CARGO_BIN_EXE_pkill");

#[test]
fn pkill_delivers_sigusr1_to_matched_helper() {
    if !procutils_testutil::pid_namespace_supported() {
        eprintln!("skipping: PID namespaces unavailable");
        return;
    }

    let session = LiveSession::new()
        .helper("signal_recipient", &["--rename=worker"])
        .target(PKILL, &["-USR1", "worker"])
        .run()
        .expect("session ran");

    assert_eq!(
        session.target.exit_code,
        Some(0),
        "pkill should exit 0; stderr was: {}",
        session.shepherd_stderr,
    );
    assert_eq!(
        session.helpers[0].exit_code,
        Some(libc::SIGUSR1),
        "helper should have exited with SIGUSR1 ({}); outcome was {:?}",
        libc::SIGUSR1,
        session.helpers[0],
    );
}

#[test]
fn pkill_delivers_real_time_signal() {
    if !procutils_testutil::pid_namespace_supported() {
        eprintln!("skipping: PID namespaces unavailable");
        return;
    }

    // RTMIN+5 has no constant in libc; compute it the same way pkill does.
    let rtmin = procutils_common::signal::rt_min();
    let expected = rtmin + 5;

    let session = LiveSession::new()
        .helper("signal_recipient", &["--rename=worker"])
        .target(PKILL, &["-RTMIN+5", "worker"])
        .run()
        .expect("session ran");

    assert_eq!(session.target.exit_code, Some(0));
    assert_eq!(
        session.helpers[0].exit_code,
        Some(expected),
        "helper should have exited with RTMIN+5 ({expected}); outcome was {:?}",
        session.helpers[0],
    );
}

#[test]
fn pkill_q_delivers_sigqueue_payload() {
    if !procutils_testutil::pid_namespace_supported() {
        eprintln!("skipping: PID namespaces unavailable");
        return;
    }

    // pkill -q VALUE uses sigqueue(3), placing VALUE in si_value.sival_int.
    // The helper records each received signal as `signal=N payload=M`.
    let session = LiveSession::new()
        .helper("signal_recipient", &["--rename=worker"])
        .target(PKILL, &["-q", "42", "-USR1", "worker"])
        .run()
        .expect("session ran");

    assert_eq!(
        session.target.exit_code,
        Some(0),
        "pkill should exit 0; stderr was: {}",
        session.shepherd_stderr,
    );

    let events = &session.helpers[0].events;
    let expected = format!("signal={} payload=42", libc::SIGUSR1);
    assert!(
        events.iter().any(|e| e == &expected),
        "expected {expected:?} in helper events; got {events:?}",
    );
}