coreutils/tests/by-util/test_sleep.rs
2025-07-01 03:36:46 +02:00

452 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.
use rstest::rstest;
use uucore::display::Quotable;
// spell-checker:ignore dont SIGBUS SIGSEGV sigsegv sigbus infd
use uutests::new_ucmd;
#[cfg(unix)]
use nix::sys::signal::Signal::{SIGBUS, SIGSEGV};
use std::io::ErrorKind;
use std::time::{Duration, Instant};
#[test]
fn test_invalid_time_interval() {
new_ucmd!()
.arg("xyz")
.fails()
.usage_error("invalid time interval 'xyz'");
new_ucmd!()
.args(&["--", "-1"])
.fails()
.usage_error("invalid time interval '-1'");
}
#[test]
fn test_sleep_no_suffix() {
let millis_100 = Duration::from_millis(100);
let before_test = Instant::now();
new_ucmd!().args(&["0.1"]).succeeds().stdout_only("");
let duration = before_test.elapsed();
assert!(duration >= millis_100);
}
#[test]
fn test_sleep_s_suffix() {
let millis_100 = Duration::from_millis(100);
let before_test = Instant::now();
new_ucmd!().args(&["0.1s"]).succeeds().stdout_only("");
let duration = before_test.elapsed();
assert!(duration >= millis_100);
}
#[test]
fn test_sleep_m_suffix() {
let millis_600 = Duration::from_millis(600);
let before_test = Instant::now();
new_ucmd!().args(&["0.01m"]).succeeds().stdout_only("");
let duration = before_test.elapsed();
assert!(duration >= millis_600);
}
#[test]
fn test_sleep_h_suffix() {
let millis_360 = Duration::from_millis(360);
let before_test = Instant::now();
new_ucmd!().args(&["0.0001h"]).succeeds().stdout_only("");
let duration = before_test.elapsed();
assert!(duration >= millis_360);
}
#[test]
fn test_sleep_negative_duration() {
new_ucmd!().args(&["-1"]).fails();
new_ucmd!().args(&["-1s"]).fails();
new_ucmd!().args(&["-1m"]).fails();
new_ucmd!().args(&["-1h"]).fails();
new_ucmd!().args(&["-1d"]).fails();
}
#[test]
fn test_sleep_zero_duration() {
new_ucmd!().args(&["0"]).succeeds().stdout_only("");
new_ucmd!().args(&["0s"]).succeeds().stdout_only("");
new_ucmd!().args(&["0m"]).succeeds().stdout_only("");
new_ucmd!().args(&["0h"]).succeeds().stdout_only("");
new_ucmd!().args(&["0d"]).succeeds().stdout_only("");
}
#[test]
fn test_sleep_no_argument() {
new_ucmd!().fails().usage_error("missing operand");
}
#[test]
fn test_sleep_sum_duration_same_suffix() {
let millis_200 = Duration::from_millis(100 + 100);
let before_test = Instant::now();
new_ucmd!()
.args(&["0.1s", "0.1s"])
.succeeds()
.stdout_only("");
let duration = before_test.elapsed();
assert!(duration >= millis_200);
}
#[test]
fn test_sleep_sum_duration_different_suffix() {
let millis_700 = Duration::from_millis(100 + 600);
let before_test = Instant::now();
new_ucmd!()
.args(&["0.1s", "0.01m"])
.succeeds()
.stdout_only("");
let duration = before_test.elapsed();
assert!(duration >= millis_700);
}
#[test]
fn test_sleep_sum_duration_many() {
let millis_900 = Duration::from_millis(100 + 100 + 300 + 400);
let before_test = Instant::now();
new_ucmd!()
.args(&["0.1s", "0.1s", "0.3s", "0.4s"])
.succeeds()
.stdout_only("");
let duration = before_test.elapsed();
assert!(duration >= millis_900);
}
#[test]
fn test_sleep_wrong_time() {
new_ucmd!().args(&["0.1s", "abc"]).fails();
}
#[test]
#[cfg(unix)]
fn test_sleep_stops_after_sigsegv() {
let mut child = new_ucmd!()
.arg("100")
.timeout(Duration::from_secs(10))
.run_no_wait();
child
.delay(100)
.kill_with_custom_signal(SIGSEGV)
.make_assertion()
.with_current_output()
.signal_is(SIGSEGV as i32) // make sure it was us who terminated the process
.no_output();
}
#[test]
#[cfg(unix)]
fn test_sleep_stops_after_sigbus() {
let mut child = new_ucmd!()
.arg("100")
.timeout(Duration::from_secs(10))
.run_no_wait();
child
.delay(100)
.kill_with_custom_signal(SIGBUS)
.make_assertion()
.with_current_output()
.signal_is(SIGBUS as i32) // make sure it was us who terminated the process
.no_output();
}
#[test]
fn test_sleep_when_single_input_exceeds_max_duration_then_no_error() {
let mut child = new_ucmd!()
.arg(format!("{}", u128::from(u64::MAX) + 1))
.timeout(Duration::from_secs(10))
.run_no_wait();
#[cfg(unix)]
child
.delay(100)
.kill()
.make_assertion()
.with_current_output()
.signal_is(9) // make sure it was us who terminated the process
.no_output();
#[cfg(windows)]
child
.delay(100)
.kill()
.make_assertion()
.with_current_output()
.failure()
.no_output();
}
#[test]
fn test_sleep_when_multiple_inputs_exceed_max_duration_then_no_error() {
let mut child = new_ucmd!()
.arg(format!("{}", u64::MAX))
.arg("1")
.timeout(Duration::from_secs(10))
.run_no_wait();
#[cfg(unix)]
child
.delay(100)
.kill()
.make_assertion()
.with_current_output()
.signal_is(9) // make sure it was us who terminated the process
.no_output();
#[cfg(windows)]
child
.delay(100)
.kill()
.make_assertion()
.with_current_output()
.failure()
.no_output();
}
#[rstest]
#[case::whitespace_prefix(" 0.1s")]
#[case::multiple_whitespace_prefix(" 0.1s")]
fn test_sleep_when_input_has_leading_whitespace_then_no_error(#[case] input: &str) {
new_ucmd!()
.arg(input)
.timeout(Duration::from_secs(10))
.succeeds()
.no_output();
}
#[rstest]
#[case::whitespace_suffix("0.1s ")]
#[case::mixed_newlines_spaces_tabs("\n\t0.1s \n ")]
fn test_sleep_when_input_has_trailing_whitespace_then_error(#[case] input: &str) {
new_ucmd!()
.arg(input)
.timeout(Duration::from_secs(10))
.fails()
.usage_error(format!("invalid time interval {}", input.quote()));
}
#[rstest]
#[case::only_space(" ")]
#[case::only_tab("\t")]
#[case::only_newline("\n")]
fn test_sleep_when_input_has_only_whitespace_then_error(#[case] input: &str) {
new_ucmd!()
.arg(input)
.timeout(Duration::from_secs(10))
.fails()
.usage_error(format!("invalid time interval {}", input.quote()));
}
#[test]
fn test_sleep_when_multiple_input_some_with_error_then_shows_all_errors() {
let expected = "invalid time interval 'abc'\n\
sleep: invalid time interval '1years'\n\
sleep: invalid time interval ' '";
// Even if one of the arguments is valid, but the rest isn't, we should still fail and exit early.
// So, the timeout of 10 seconds ensures we haven't executed `thread::sleep` with the only valid
// interval of `100000.0` seconds.
new_ucmd!()
.args(&["abc", "100000.0", "1years", " "])
.timeout(Duration::from_secs(10))
.fails()
.usage_error(expected);
}
#[test]
fn test_negative_interval() {
new_ucmd!()
.args(&["--", "-1"])
.fails()
.usage_error("invalid time interval '-1'");
}
#[rstest]
#[case::int("0x0")]
#[case::negative_zero("-0x0")]
#[case::int_suffix("0x0s")]
#[case::int_suffix("0x0h")]
#[case::frac("0x0.1")]
#[case::frac_suffix("0x0.1s")]
#[case::frac_suffix("0x0.001h")]
#[case::scientific("0x1.0p-3")]
#[case::scientific_suffix("0x1.0p-4s")]
fn test_valid_hex_duration(#[case] input: &str) {
new_ucmd!().args(&["--", input]).succeeds().no_output();
}
#[rstest]
#[case::negative("-0x1")]
#[case::negative_suffix("-0x1s")]
#[case::negative_frac_suffix("-0x0.1s")]
#[case::wrong_capitalization("infD")]
#[case::wrong_capitalization("INFD")]
#[case::wrong_capitalization("iNfD")]
#[case::single_quote("'1")]
fn test_invalid_duration(#[case] input: &str) {
new_ucmd!()
.args(&["--", input])
.fails()
.usage_error(format!("invalid time interval {}", input.quote()));
}
#[cfg(unix)]
#[test]
#[should_panic = "Program must be run first or has not finished"]
fn test_cmd_result_signal_when_still_running_then_panic() {
let mut child = new_ucmd!().arg("60").run_no_wait();
child
.make_assertion()
.is_alive()
.with_current_output()
.signal();
}
#[cfg(unix)]
#[test]
fn test_cmd_result_signal_when_kill_then_signal() {
let mut child = new_ucmd!().arg("60").run_no_wait();
child.kill();
child
.make_assertion()
.is_not_alive()
.with_current_output()
.signal_is(9)
.signal_name_is("SIGKILL")
.signal_name_is("KILL")
.signal_name_is("9")
.signal()
.expect("Signal was none");
child
.wait()
.unwrap()
.signal_is(9)
.signal_name_is("SIGKILL")
.signal_name_is("KILL")
.signal_name_is("9")
.signal()
.expect("Signal was none");
}
#[cfg(unix)]
#[rstest]
#[case::signal_only_part_of_name("IGKILL")] // spell-checker: disable-line
#[case::signal_just_sig("SIG")]
#[case::signal_value_too_high("100")]
#[case::signal_value_negative("-1")]
#[should_panic = "Invalid signal name or value"]
fn test_cmd_result_signal_when_invalid_signal_name_then_panic(#[case] signal_name: &str) {
let mut child = new_ucmd!().arg("60").run_no_wait();
child.kill();
child.wait().unwrap().signal_name_is(signal_name);
}
#[test]
#[cfg(unix)]
fn test_cmd_result_signal_name_is_accepts_lowercase() {
let mut child = new_ucmd!().arg("60").run_no_wait();
child.kill();
let result = child.wait().unwrap();
result.signal_name_is("sigkill");
result.signal_name_is("kill");
}
#[test]
fn test_uchild_when_wait_and_timeout_is_reached_then_timeout_error() {
let child = new_ucmd!()
.timeout(Duration::from_secs(1))
.arg("10.0")
.run_no_wait();
match child.wait() {
Err(error) if error.kind() == ErrorKind::Other => {
std::assert_eq!(error.to_string(), "wait: Timeout of '1s' reached");
}
Err(error) => panic!("Assertion failed: Expected error with timeout but was: {error}"),
Ok(_) => panic!("Assertion failed: Expected timeout of `wait`."),
}
}
#[rstest]
#[timeout(Duration::from_secs(5))]
fn test_uchild_when_kill_and_timeout_higher_than_kill_time_then_no_panic() {
let mut child = new_ucmd!()
.timeout(Duration::from_secs(60))
.arg("20.0")
.run_no_wait();
child.kill().make_assertion().is_not_alive();
}
#[test]
fn test_uchild_when_try_kill_and_timeout_is_reached_then_error() {
let mut child = new_ucmd!()
.timeout(Duration::ZERO)
.arg("10.0")
.run_no_wait();
match child.try_kill() {
Err(error) if error.kind() == ErrorKind::Other => {
std::assert_eq!(error.to_string(), "kill: Timeout of '0s' reached");
}
Err(error) => panic!("Assertion failed: Expected error with timeout but was: {error}"),
Ok(()) => panic!("Assertion failed: Expected timeout of `try_kill`."),
}
}
#[test]
#[should_panic = "kill: Timeout of '0s' reached"]
fn test_uchild_when_kill_with_timeout_and_timeout_is_reached_then_panic() {
let mut child = new_ucmd!()
.timeout(Duration::ZERO)
.arg("10.0")
.run_no_wait();
child.kill();
panic!("Assertion failed: Expected timeout of `kill`.");
}
#[test]
#[should_panic(expected = "wait: Timeout of '1.1s' reached")]
fn test_ucommand_when_run_with_timeout_and_timeout_is_reached_then_panic() {
new_ucmd!()
.timeout(Duration::from_millis(1100))
.arg("10.0")
.run();
panic!("Assertion failed: Expected timeout of `run`.")
}
#[rstest]
#[timeout(Duration::from_secs(10))]
fn test_ucommand_when_run_with_timeout_higher_then_execution_time_then_no_panic() {
new_ucmd!()
.timeout(Duration::from_secs(60))
.arg("1.0")
.run();
}