Files
karanabe 005cf50a1a kill: fix GNU compatibility tests for RTMIN and RTMAX (#11224)
* kill: list Linux realtime signals correctly

Add list-specific signal helpers for RTMIN/RTMAX and unnamed
signal numbers, and use them in kill and env range iteration.
Keep send-oriented signal parsing unchanged for signal delivery.

* document low-byte signal decoding in list mode
2026-03-08 09:25:53 +01:00

496 lines
12 KiB
Rust

// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore IAMNOTASIGNAL RTMAX RTMIN SIGRTMAX
use regex::Regex;
use std::os::unix::process::ExitStatusExt;
use std::process::{Child, Command};
use uutests::new_ucmd;
// A child process the tests will try to kill.
struct Target {
child: Child,
killed: bool,
}
impl Target {
// Creates a target that will naturally die after some time if not killed
// fast enough.
// This timeout avoids hanging failing tests.
fn new() -> Self {
Self {
child: Command::new("sleep")
.arg("30")
.spawn()
.expect("cannot spawn target"),
killed: false,
}
}
// Waits for the target to complete and returns the signal it received if any.
fn wait_for_signal(&mut self) -> Option<i32> {
let sig = self.child.wait().expect("cannot wait on target").signal();
self.killed = true;
sig
}
fn pid(&self) -> u32 {
self.child.id()
}
}
impl Drop for Target {
// Terminates this target to avoid littering test boxes with zombi processes
// when a test fails after creating a target but before killing it.
fn drop(&mut self) {
if !self.killed {
self.child.kill().expect("cannot kill target");
}
}
}
#[test]
fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);
}
#[test]
fn test_kill_list_all_signals() {
// Check for a few signals. Do not try to be comprehensive.
new_ucmd!()
.arg("-l")
.succeeds()
.stdout_contains("KILL")
.stdout_contains("TERM")
.stdout_contains("HUP")
.stdout_contains("EXIT");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_kill_list_contains_realtime_signals() {
new_ucmd!()
.arg("-l")
.succeeds()
.stdout_contains("RTMIN")
.stdout_contains("RTMAX");
}
#[test]
fn test_kill_list_final_new_line() {
let re = Regex::new("\\n$").unwrap();
assert!(re.is_match(new_ucmd!().arg("-l").succeeds().stdout_str()));
}
#[test]
fn test_kill_list_all_signals_as_table() {
// Check for a few signals. Do not try to be comprehensive.
new_ucmd!()
.arg("-t")
.succeeds()
.stdout_contains("KILL")
.stdout_contains("TERM")
.stdout_contains("HUP")
.stdout_contains("EXIT");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_kill_table_contains_realtime_signals() {
new_ucmd!()
.arg("-t")
.succeeds()
.stdout_contains("RTMIN")
.stdout_contains("RTMAX");
}
#[test]
fn test_kill_table_starts_at_0() {
new_ucmd!()
.arg("-t")
.succeeds()
.stdout_matches(&Regex::new("^\\s?0\\sEXIT").unwrap());
}
#[test]
fn test_kill_table_lists_all_vertically() {
// Check for a few signals. Do not try to be comprehensive.
let command = new_ucmd!().arg("-t").succeeds();
let signals = command
.stdout_str()
.split('\n')
.filter_map(|line| line.trim().split(' ').nth(1))
.collect::<Vec<&str>>();
assert!(signals.contains(&"KILL"));
assert!(signals.contains(&"TERM"));
assert!(signals.contains(&"HUP"));
assert!(signals.contains(&"EXIT"));
}
#[test]
fn test_kill_list_one_signal_from_number() {
new_ucmd!()
.arg("-l")
.arg("9")
.succeeds()
.stdout_only("KILL\n");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_kill_list_rtmax_from_name() {
new_ucmd!()
.arg("-l")
.arg("RTMAX")
.succeeds()
.stdout_only(format!("{}\n", libc::SIGRTMAX()));
}
#[test]
fn test_kill_list_one_signal_from_invalid_number() {
new_ucmd!()
.arg("-l")
.arg("99")
.fails()
.stderr_contains("'99': invalid signal");
}
#[test]
fn test_kill_list_one_signal_from_name() {
// Use SIGKILL because it is 9 on all unixes.
new_ucmd!()
.arg("-l")
.arg("KILL")
.succeeds()
.stdout_matches(&Regex::new("\\b9\\b").unwrap());
}
#[test]
fn test_kill_list_one_signal_ignore_case() {
// Use SIGKILL because it is 9 on all unixes.
new_ucmd!()
.arg("-l")
.arg("KiLl")
.succeeds()
.stdout_matches(&Regex::new("\\b9\\b").unwrap());
}
#[test]
fn test_kill_list_unknown_must_match_input_case() {
new_ucmd!()
.arg("-l")
.arg("IaMnOtAsIgNaL")
.fails()
.stderr_contains("IaMnOtAsIgNaL");
}
#[test]
fn test_kill_list_all_vertically() {
// Check for a few signals. Do not try to be comprehensive.
let command = new_ucmd!().arg("-l").succeeds();
let signals = command.stdout_str().split('\n').collect::<Vec<&str>>();
assert!(signals.contains(&"KILL"));
assert!(signals.contains(&"TERM"));
assert!(signals.contains(&"HUP"));
assert!(signals.contains(&"EXIT"));
}
#[test]
fn test_kill_list_two_signal_from_name() {
new_ucmd!()
.arg("-l")
.arg("INT")
.arg("KILL")
.succeeds()
.stdout_matches(&Regex::new("\\d\n\\d").unwrap());
}
#[test]
fn test_kill_list_three_signal_first_unknown() {
new_ucmd!()
.arg("-l")
.arg("IAMNOTASIGNAL")
.arg("INT")
.arg("KILL")
.fails()
.stderr_contains("'IAMNOTASIGNAL': invalid signal")
.stdout_matches(&Regex::new("\\d\n\\d").unwrap());
}
#[test]
fn test_kill_set_bad_signal_name() {
new_ucmd!()
.arg("-s")
.arg("IAMNOTASIGNAL")
.fails()
.stderr_contains("'IAMNOTASIGNAL': invalid signal");
}
#[test]
fn test_kill_with_default_signal() {
let mut target = Target::new();
new_ucmd!().arg(format!("{}", target.pid())).succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGTERM));
}
#[test]
fn test_kill_with_signal_number_old_form() {
let mut target = Target::new();
new_ucmd!()
.arg("-9")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(9));
}
#[test]
fn test_kill_with_signal_name_old_form() {
for arg in ["-Kill", "-KILL"] {
let mut target = Target::new();
new_ucmd!()
.arg(arg)
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
}
}
#[test]
fn test_kill_with_lower_case_signal_name_old_form() {
let target = Target::new();
new_ucmd!()
.arg("-kill")
.arg(format!("{}", target.pid()))
.fails()
.stderr_contains("unexpected argument");
}
#[test]
fn test_kill_with_signal_prefixed_name_old_form() {
let mut target = Target::new();
new_ucmd!()
.arg("-SIGKILL")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
}
#[test]
fn test_kill_with_signal_number_new_form() {
let mut target = Target::new();
new_ucmd!()
.arg("-s")
.arg("9")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(9));
}
#[test]
fn test_kill_with_signal_name_new_form() {
let mut target = Target::new();
new_ucmd!()
.arg("-s")
.arg("KILL")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
}
#[test]
fn test_kill_with_signal_name_new_form_ignore_case() {
let mut target = Target::new();
new_ucmd!()
.arg("-s")
.arg("KiLl")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
}
#[test]
fn test_kill_with_signal_prefixed_name_new_form() {
let mut target = Target::new();
new_ucmd!()
.arg("-s")
.arg("SIGKILL")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
}
#[test]
fn test_kill_with_signal_prefixed_name_new_form_ignore_case() {
let mut target = Target::new();
new_ucmd!()
.arg("-s")
.arg("SiGKiLl")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
}
#[test]
fn test_kill_with_signal_name_new_form_unknown_must_match_input_case() {
let target = Target::new();
new_ucmd!()
.arg("-s")
.arg("IaMnOtAsIgNaL")
.arg(format!("{}", target.pid()))
.fails()
.stderr_contains("'IaMnOtAsIgNaL': invalid signal");
}
#[test]
fn test_kill_no_pid_provided() {
new_ucmd!()
.fails()
.stderr_contains("no process ID specified");
}
#[test]
fn test_kill_with_signal_exit_new_form() {
let target = Target::new();
new_ucmd!()
.arg("-s")
.arg("EXIT")
.arg(format!("{}", target.pid()))
.succeeds();
}
#[test]
fn test_kill_with_signal_number_hidden_compatibility_option() {
let mut target = Target::new();
new_ucmd!()
.arg("-n")
.arg("9")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(9));
}
#[test]
fn test_kill_with_signal_and_list() {
let target = Target::new();
new_ucmd!()
.arg("-s")
.arg("EXIT")
.arg(format!("{}", target.pid()))
.arg("-l")
.fails();
}
#[test]
fn test_kill_with_list_lower_bits() {
new_ucmd!()
.arg("-l")
.arg("128")
.succeeds()
.stdout_contains("EXIT");
new_ucmd!()
.arg("-l")
.arg("143")
.succeeds()
.stdout_contains("TERM");
new_ucmd!()
.arg("-l")
.arg("256")
.succeeds()
.stdout_contains("EXIT");
new_ucmd!()
.arg("-l")
.arg("2304")
.succeeds()
.stdout_contains("EXIT");
}
#[test]
fn test_kill_with_list_lower_bits_unrecognized() {
new_ucmd!().arg("-l").arg("111").fails();
new_ucmd!().arg("-l").arg("384").fails();
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_kill_with_list_unnamed_signal_numbers() {
new_ucmd!()
.arg("-l")
.arg("32")
.succeeds()
.stdout_only("32\n");
new_ucmd!()
.arg("-l")
.arg("33")
.succeeds()
.stdout_only("33\n");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_kill_with_list_all_signal_numbers_up_to_last_named_signal() {
let last_signal_name = new_ucmd!()
.arg("-l")
.succeeds()
.stdout_str()
.lines()
.last()
.unwrap()
.to_string();
let last_signal_number: usize = new_ucmd!()
.arg("-l")
.arg("--")
.arg(&last_signal_name)
.succeeds()
.stdout_str()
.trim()
.parse()
.unwrap();
let args = std::iter::once(String::from("--"))
.chain((0..=last_signal_number).map(|signal| signal.to_string()))
.collect::<Vec<_>>();
new_ucmd!().arg("-l").args(&args).succeeds();
}
#[test]
fn test_kill_with_signal_and_table() {
let target = Target::new();
new_ucmd!()
.arg("-s")
.arg("EXIT")
.arg(format!("{}", target.pid()))
.arg("-t")
.fails();
}
/// Test that `kill -1` (signal without PID) reports "no process ID" error
/// instead of being misinterpreted as pid=-1 which would kill all processes.
/// This matches GNU kill behavior.
#[test]
fn test_kill_signal_only_no_pid() {
// Test with -1 (SIGHUP)
new_ucmd!()
.arg("-1")
.fails()
.stderr_contains("no process ID specified");
// Test with -9 (SIGKILL)
new_ucmd!()
.arg("-9")
.fails()
.stderr_contains("no process ID specified");
// Test with -TERM
new_ucmd!()
.arg("-TERM")
.fails()
.stderr_contains("no process ID specified");
}