diff --git a/zebra-test/tests/command.rs b/zebra-test/tests/command.rs new file mode 100644 index 000000000..3c7e9fd1a --- /dev/null +++ b/zebra-test/tests/command.rs @@ -0,0 +1,169 @@ +#![allow(clippy::try_err)] + +use std::{process::Command, time::Duration}; + +use color_eyre::eyre::Result; +use tempdir::TempDir; + +use zebra_test::{command::TestDirExt, prelude::Stdio}; + +/// Returns true if `cmd` with `args` runs successfully. +/// +/// On failure, prints an error message to stderr. +/// (This message is captured by the test runner, use `cargo test -- --nocapture` to see it.) +/// +/// The command's stdout and stderr are ignored. +fn is_command_available(cmd: &str, args: &[&str]) -> bool { + let status = Command::new(cmd) + .args(args) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + + match status { + Err(e) => { + eprintln!( + "Skipping test because '{} {:?}' returned error {:?}", + cmd, args, e + ); + false + } + Ok(status) if !status.success() => { + eprintln!( + "Skipping test because '{} {:?}' returned status {:?}", + cmd, args, status + ); + false + } + _ => true, + } +} + +/// Test if a process that keeps on producing lines of output is killed after the timeout. +#[test] +fn kill_on_timeout_output_continuous_lines() -> Result<()> { + zebra_test::init(); + + // Ideally, we'd want to use the 'yes' command here, but BSD yes treats + // every string as an argument to repeat - so we can't test if it is + // present on the system. + const TEST_CMD: &str = "hexdump"; + // Skip the test if the test system does not have the command + if !is_command_available(TEST_CMD, &["/dev/null"]) { + return Ok(()); + } + + // Without '-v', hexdump hides duplicate lines. But we want duplicate lines + // in this test. + let mut child = TempDir::new("zebra_test")? + .spawn_child_with_command(TEST_CMD, &["-v", "/dev/zero"])? + .with_timeout(Duration::from_secs(2)); + + // We need to use expect_stdout, because wait_with_output ignores timeouts. + // We use a non-matching regex, to trigger the timeout. + assert!(child.expect_stdout("this regex should not match").is_err()); + + Ok(()) +} + +/// Test if the tests pass for a process that produces a single line of output, +/// then exits before the timeout. +// +// TODO: create a similar test that pauses after output +#[test] +fn finish_before_timeout_output_single_line() -> Result<()> { + zebra_test::init(); + + const TEST_CMD: &str = "echo"; + // Skip the test if the test system does not have the command + if !is_command_available(TEST_CMD, &[]) { + return Ok(()); + } + + let mut child = TempDir::new("zebra_test")? + .spawn_child_with_command(TEST_CMD, &["zebra_test_output"])? + .with_timeout(Duration::from_secs(2)); + + // We need to use expect_stdout, because wait_with_output ignores timeouts. + // We use a non-matching regex, to trigger the timeout. + assert!(child.expect_stdout("this regex should not match").is_err()); + + Ok(()) +} + +/// Test if a process that keeps on producing output, but doesn't produce any newlines, +/// is killed after the timeout. +/// +/// This test fails due to bugs in TestDirExt, see #1140 for details. +#[test] +#[ignore] +fn kill_on_timeout_continuous_output_no_newlines() -> Result<()> { + zebra_test::init(); + + const TEST_CMD: &str = "cat"; + // Skip the test if the test system does not have the command + if !is_command_available(TEST_CMD, &["/dev/null"]) { + return Ok(()); + } + + let mut child = TempDir::new("zebra_test")? + .spawn_child_with_command(TEST_CMD, &["/dev/zero"])? + .with_timeout(Duration::from_secs(2)); + + // We need to use expect_stdout, because wait_with_output ignores timeouts. + // We use a non-matching regex, to trigger the timeout. + assert!(child.expect_stdout("this regex should not match").is_err()); + + Ok(()) +} + +/// Test if tests pass for a process that produces a small amount of output, +/// with no newlines, then exits before the timeout. +// +// TODO: create a similar test that pauses after output +#[test] +fn finish_before_timeout_short_output_no_newlines() -> Result<()> { + zebra_test::init(); + + const TEST_CMD: &str = "printf"; + // Skip the test if the test system does not have the command + // The empty argument is required, because printf expects at least one argument. + if !is_command_available(TEST_CMD, &[""]) { + return Ok(()); + } + + let mut child = TempDir::new("zebra_test")? + .spawn_child_with_command(TEST_CMD, &["zebra_test_output"])? + .with_timeout(Duration::from_secs(2)); + + // We need to use expect_stdout, because wait_with_output ignores timeouts. + // We use a non-matching regex, to trigger the timeout. + assert!(child.expect_stdout("this regex should not match").is_err()); + + Ok(()) +} + +/// Test if the timeout works for a process that produces no output. +/// +/// This test fails due to bugs in TestDirExt, see #1140 for details. +#[test] +#[ignore] +fn kill_on_timeout_no_output() -> Result<()> { + zebra_test::init(); + + const TEST_CMD: &str = "sleep"; + // Skip the test if the test system does not have the command + if !is_command_available(TEST_CMD, &["0"]) { + return Ok(()); + } + + let mut child = TempDir::new("zebra_test")? + .spawn_child_with_command(TEST_CMD, &["120"])? + .with_timeout(Duration::from_secs(2)); + + // We need to use expect_stdout, because wait_with_output ignores timeouts. + // We use a non-matching regex, to trigger the timeout. + assert!(child.expect_stdout("this regex should not match").is_err()); + + Ok(()) +}