use std::{process::Command, time::Duration};
use color_eyre::eyre::{eyre, Result};
use regex::RegexSet;
use tempfile::tempdir;
use zebra_test::{
args,
command::{TestDirExt, NO_MATCHES_REGEX_ITER},
prelude::Stdio,
};
#[allow(clippy::print_stderr)]
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 '{cmd} {args:?}' returned error {e:?}");
false
}
Ok(status) if !status.success() => {
eprintln!("Skipping test because '{cmd} {args:?}' returned status {status:?}");
false
}
_ => true,
}
}
#[test]
fn kill_on_timeout_output_continuous_lines() -> Result<()> {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "hexdump";
if !is_command_available(TEST_CMD, &["/dev/null"]) {
return Ok(());
}
let mut child = tempdir()?
.spawn_child_with_command(TEST_CMD, args!["-v", "-n", "1024", "/dev/zero"])?
.with_timeout(Duration::from_secs(2));
assert!(child
.expect_stdout_line_matches("this regex should not match")
.is_err());
Ok(())
}
#[test]
fn finish_before_timeout_output_single_line() -> Result<()> {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return Ok(());
}
let mut child = tempdir()?
.spawn_child_with_command(TEST_CMD, args!["zebra_test_output"])?
.with_timeout(Duration::from_secs(2));
assert!(child
.expect_stdout_line_matches("this regex should not match")
.is_err());
Ok(())
}
#[allow(dead_code)]
fn kill_on_timeout_continuous_output_no_newlines() -> Result<()> {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "head";
if !is_command_available(TEST_CMD, &["/dev/null"]) {
return Ok(());
}
let mut child = tempdir()?
.spawn_child_with_command(TEST_CMD, args!["-c", "1024", "/dev/zero"])?
.with_timeout(Duration::from_secs(2));
assert!(child
.expect_stdout_line_matches("this regex should not match")
.is_err());
Ok(())
}
#[test]
fn finish_before_timeout_short_output_no_newlines() -> Result<()> {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "printf";
if !is_command_available(TEST_CMD, &[""]) {
return Ok(());
}
let mut child = tempdir()?
.spawn_child_with_command(TEST_CMD, args!["zebra_test_output"])?
.with_timeout(Duration::from_secs(2));
assert!(child
.expect_stdout_line_matches("this regex should not match")
.is_err());
Ok(())
}
#[allow(dead_code)]
fn kill_on_timeout_no_output() -> Result<()> {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "sleep";
if !is_command_available(TEST_CMD, &["0"]) {
return Ok(());
}
let mut child = tempdir()?
.spawn_child_with_command(TEST_CMD, args!["120"])?
.with_timeout(Duration::from_secs(2));
assert!(child
.expect_stdout_line_matches("this regex should not match")
.is_err());
Ok(())
}
#[test]
fn failure_regex_matches_stdout_failure_message() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let mut child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args!["failure_message"])
.unwrap()
.with_timeout(Duration::from_secs(2))
.with_failure_regex_set("fail", RegexSet::empty());
let expected_error = child
.expect_stdout_line_matches("this regex should not match")
.unwrap_err();
let expected_error = format!("{expected_error:?}");
assert!(
expected_error.contains("Logged a failure message"),
"error did not contain expected failure message: {expected_error}",
);
}
#[test]
fn failure_regex_matches_stderr_failure_message() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "bash";
if !is_command_available(TEST_CMD, &["-c", "read -t 1 -p failure_message"]) {
return;
}
let mut child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args![ "-c": "read -t 1 -p failure_message" ])
.unwrap()
.with_timeout(Duration::from_secs(5))
.with_failure_regex_set("fail", RegexSet::empty());
let expected_error = child
.expect_stderr_line_matches("this regex should not match")
.unwrap_err();
let expected_error = format!("{expected_error:?}");
assert!(
expected_error.contains("Logged a failure message"),
"error did not contain expected failure message: {expected_error}",
);
}
#[test]
#[should_panic(expected = "Logged a failure message")]
fn failure_regex_matches_stdout_failure_message_drop() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let _child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args!["failure_message"])
.unwrap()
.with_timeout(Duration::from_secs(5))
.with_failure_regex_set("fail", RegexSet::empty());
std::thread::sleep(Duration::from_secs(1));
}
#[test]
fn failure_regex_reads_multi_line_output_on_expect_line() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let mut child = tempdir()
.unwrap()
.spawn_child_with_command(
TEST_CMD,
args![
"failure_message\n\
multi-line failure message"
],
)
.unwrap()
.with_timeout(Duration::from_secs(5))
.with_failure_regex_set("failure_message", RegexSet::empty());
let expected_error = child
.expect_stdout_line_matches("this regex should not match")
.unwrap_err();
let expected_error = format!("{expected_error:?}");
assert!(
expected_error.contains(
"\
Unread Stdout:
multi-line failure message\
"
),
"error did not contain expected failure message: {expected_error}",
);
}
#[test]
#[should_panic(expected = "Unread Stdout:
multi-line failure message")]
fn failure_regex_reads_multi_line_output_on_drop() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let _child = tempdir()
.unwrap()
.spawn_child_with_command(
TEST_CMD,
args![
"failure_message\n\
multi-line failure message"
],
)
.unwrap()
.with_timeout(Duration::from_secs(5))
.with_failure_regex_set("failure_message", RegexSet::empty());
std::thread::sleep(Duration::from_secs(1));
}
#[test]
#[should_panic(expected = "Logged a failure message")]
fn failure_regex_matches_stdout_failure_message_kill() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let mut child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args!["failure_message"])
.unwrap()
.with_timeout(Duration::from_secs(5))
.with_failure_regex_set("fail", RegexSet::empty());
std::thread::sleep(Duration::from_secs(1));
child.kill(true).unwrap();
}
#[test]
#[should_panic(expected = "Logged a failure message")]
fn failure_regex_matches_stdout_failure_message_kill_on_error() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args!["failure_message"])
.unwrap()
.with_timeout(Duration::from_secs(5))
.with_failure_regex_set("fail", RegexSet::empty());
std::thread::sleep(Duration::from_secs(1));
let test_error: Result<()> = Err(eyre!("test error"));
child.kill_on_error(test_error).unwrap();
}
#[test]
#[should_panic(expected = "Logged a failure message")]
fn failure_regex_matches_stdout_failure_message_no_kill_on_error() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args!["failure_message"])
.unwrap()
.with_timeout(Duration::from_secs(5))
.with_failure_regex_set("fail", RegexSet::empty());
std::thread::sleep(Duration::from_secs(1));
let test_ok: Result<()> = Ok(());
child.kill_on_error(test_ok).unwrap();
}
#[test]
fn failure_regex_timeout_continuous_output() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "hexdump";
if !is_command_available(TEST_CMD, &["/dev/null"]) {
return;
}
let mut child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args!["-v", "/dev/zero"])
.unwrap()
.with_timeout(Duration::from_secs(2))
.with_failure_regex_set("0", RegexSet::empty());
let expected_error = child
.expect_stdout_line_matches("this regex should not match")
.unwrap_err();
let expected_error = format!("{expected_error:?}");
assert!(
expected_error.contains("Logged a failure message"),
"error did not contain expected failure message: {expected_error}",
);
}
#[test]
#[should_panic(expected = "Logged a failure message")]
fn failure_regex_matches_stdout_failure_message_wait_for_output() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args!["failure_message"])
.unwrap()
.with_timeout(Duration::from_secs(5))
.with_failure_regex_set("fail", RegexSet::empty());
std::thread::sleep(Duration::from_secs(1));
let _ = child.wait_with_output().unwrap_err();
}
#[test]
fn failure_regex_iter_matches_stdout_failure_message() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let mut child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args!["failure_message"])
.unwrap()
.with_timeout(Duration::from_secs(2))
.with_failure_regex_iter(
["fail"].iter().cloned(),
NO_MATCHES_REGEX_ITER.iter().cloned(),
);
let expected_error = child
.expect_stdout_line_matches("this regex should not match")
.unwrap_err();
let expected_error = format!("{expected_error:?}");
assert!(
expected_error.contains("Logged a failure message"),
"error did not contain expected failure message: {expected_error}",
);
}
#[test]
fn ignore_regex_ignores_stdout_failure_message() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let mut child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args!["failure_message ignore_message"])
.unwrap()
.with_timeout(Duration::from_secs(2))
.with_failure_regex_set("fail", "ignore");
child.expect_stdout_line_matches("ignore_message").unwrap();
}
#[test]
fn ignore_regex_iter_ignores_stdout_failure_message() {
let _init_guard = zebra_test::init();
const TEST_CMD: &str = "echo";
if !is_command_available(TEST_CMD, &[]) {
return;
}
let mut child = tempdir()
.unwrap()
.spawn_child_with_command(TEST_CMD, args!["failure_message ignore_message"])
.unwrap()
.with_timeout(Duration::from_secs(2))
.with_failure_regex_iter(["fail"].iter().cloned(), ["ignore"].iter().cloned());
child.expect_stdout_line_matches("ignore_message").unwrap();
}