diff --git a/tests/common/macros.rs b/tests/common/macros.rs index e8b9c9d5d..81878bf1b 100644 --- a/tests/common/macros.rs +++ b/tests/common/macros.rs @@ -1,40 +1,3 @@ -/// Assertion helper macro for [`CmdResult`] types -/// -/// [`CmdResult`]: crate::tests::common::util::CmdResult -#[macro_export] -macro_rules! assert_empty_stderr( - ($cond:expr) => ( - if $cond.stderr.len() > 0 { - panic!("stderr: {}", $cond.stderr_str()) - } - ); -); - -/// Assertion helper macro for [`CmdResult`] types -/// -/// [`CmdResult`]: crate::tests::common::util::CmdResult -#[macro_export] -macro_rules! assert_empty_stdout( - ($cond:expr) => ( - if $cond.stdout.len() > 0 { - panic!("stdout: {}", $cond.stdout_str()) - } - ); -); - -/// Assertion helper macro for [`CmdResult`] types -/// -/// [`CmdResult`]: crate::tests::common::util::CmdResult -#[macro_export] -macro_rules! assert_no_error( - ($cond:expr) => ( - assert!($cond.success); - if $cond.stderr.len() > 0 { - panic!("stderr: {}", $cond.stderr_str()) - } - ); -); - /// Platform-independent helper for constructing a PathBuf from individual elements #[macro_export] macro_rules! path_concat { diff --git a/tests/common/util.rs b/tests/common/util.rs index d5663e9ed..aca8ef216 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -64,7 +64,7 @@ fn read_scenario_fixture>(tmpd: &Option>, file_rel_p /// A command result is the outputs of a command (streams and status code) /// within a struct which has convenience assertion functions about those outputs -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CmdResult { //tmpd is used for convenience functions for asserts against fixtures tmpd: Option>, @@ -130,6 +130,11 @@ impl CmdResult { self.code.expect("Program must be run first") } + pub fn code_is(&self, expected_code: i32) -> &CmdResult { + assert_eq!(self.code(), expected_code); + self + } + /// Returns the program's TempDir /// Panics if not present pub fn tmpd(&self) -> Rc { @@ -146,13 +151,25 @@ impl CmdResult { /// asserts that the command resulted in a success (zero) status code pub fn success(&self) -> &CmdResult { - assert!(self.success); + if !self.success { + panic!( + "Command was expected to succeed.\nstdout = {}\n stderr = {}", + self.stdout_str(), + self.stderr_str() + ); + } self } /// asserts that the command resulted in a failure (non-zero) status code pub fn failure(&self) -> &CmdResult { - assert!(!self.success); + if self.success { + panic!( + "Command was expected to fail.\nstdout = {}\n stderr = {}", + self.stdout_str(), + self.stderr_str() + ); + } self } @@ -168,7 +185,12 @@ impl CmdResult { /// 1. you can not know exactly what stdout will be or /// 2. you know that stdout will also be empty pub fn no_stderr(&self) -> &CmdResult { - assert!(self.stderr.is_empty()); + if !self.stderr.is_empty() { + panic!( + "Expected stderr to be empty, but it's:\n{}", + self.stderr_str() + ); + } self } @@ -179,7 +201,12 @@ impl CmdResult { /// 1. you can not know exactly what stderr will be or /// 2. you know that stderr will also be empty pub fn no_stdout(&self) -> &CmdResult { - assert!(self.stdout.is_empty()); + if !self.stdout.is_empty() { + panic!( + "Expected stdout to be empty, but it's:\n{}", + self.stderr_str() + ); + } self } @@ -277,10 +304,34 @@ impl CmdResult { self } - pub fn stderr_contains>(&self, cmp: &T) -> &CmdResult { + pub fn stderr_contains>(&self, cmp: T) -> &CmdResult { assert!(self.stderr_str().contains(cmp.as_ref())); self } + + pub fn stdout_does_not_contain>(&self, cmp: T) -> &CmdResult { + assert!(!self.stdout_str().contains(cmp.as_ref())); + self + } + + pub fn stderr_does_not_contain>(&self, cmp: T) -> &CmdResult { + assert!(!self.stderr_str().contains(cmp.as_ref())); + self + } + + pub fn stdout_matches(&self, regex: ®ex::Regex) -> &CmdResult { + if !regex.is_match(self.stdout_str()) { + panic!("Stdout does not match regex:\n{}", self.stdout_str()) + } + self + } + + pub fn stdout_does_not_match(&self, regex: ®ex::Regex) -> &CmdResult { + if regex.is_match(self.stdout_str()) { + panic!("Stdout matches regex:\n{}", self.stdout_str()) + } + self + } } pub fn log_info, U: AsRef>(msg: T, par: U) { @@ -819,3 +870,211 @@ pub fn vec_of_size(n: usize) -> Vec { assert_eq!(result.len(), n); result } + +/// Sanity checks for test utils +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_code_is() { + let res = CmdResult { + tmpd: None, + code: Some(32), + success: false, + stdout: "".into(), + stderr: "".into(), + }; + res.code_is(32); + } + + #[test] + #[should_panic] + fn test_code_is_fail() { + let res = CmdResult { + tmpd: None, + code: Some(32), + success: false, + stdout: "".into(), + stderr: "".into(), + }; + res.code_is(1); + } + + #[test] + fn test_failure() { + let res = CmdResult { + tmpd: None, + code: None, + success: false, + stdout: "".into(), + stderr: "".into(), + }; + res.failure(); + } + + #[test] + #[should_panic] + fn test_failure_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "".into(), + stderr: "".into(), + }; + res.failure(); + } + + #[test] + fn test_success() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "".into(), + stderr: "".into(), + }; + res.success(); + } + + #[test] + #[should_panic] + fn test_success_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: false, + stdout: "".into(), + stderr: "".into(), + }; + res.success(); + } + + #[test] + fn test_no_std_errout() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "".into(), + stderr: "".into(), + }; + res.no_stderr(); + res.no_stdout(); + } + + #[test] + #[should_panic] + fn test_no_stderr_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "".into(), + stderr: "asdfsadfa".into(), + }; + + res.no_stderr(); + } + + #[test] + #[should_panic] + fn test_no_stdout_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "asdfsadfa".into(), + stderr: "".into(), + }; + + res.no_stdout(); + } + + #[test] + fn test_std_does_not_contain() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "This is a likely error message\n".into(), + stderr: "This is a likely error message\n".into(), + }; + res.stdout_does_not_contain("unlikely"); + res.stderr_does_not_contain("unlikely"); + } + + #[test] + #[should_panic] + fn test_stdout_does_not_contain_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "This is a likely error message\n".into(), + stderr: "".into(), + }; + + res.stdout_does_not_contain("likely"); + } + + #[test] + #[should_panic] + fn test_stderr_does_not_contain_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "".into(), + stderr: "This is a likely error message\n".into(), + }; + + res.stderr_does_not_contain("likely"); + } + + #[test] + fn test_stdout_matches() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "This is a likely error message\n".into(), + stderr: "This is a likely error message\n".into(), + }; + let positive = regex::Regex::new(".*likely.*").unwrap(); + let negative = regex::Regex::new(".*unlikely.*").unwrap(); + res.stdout_matches(&positive); + res.stdout_does_not_match(&negative); + } + + #[test] + #[should_panic] + fn test_stdout_matches_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "This is a likely error message\n".into(), + stderr: "This is a likely error message\n".into(), + }; + let negative = regex::Regex::new(".*unlikely.*").unwrap(); + + res.stdout_matches(&negative); + } + + #[test] + #[should_panic] + fn test_stdout_not_matches_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "This is a likely error message\n".into(), + stderr: "This is a likely error message\n".into(), + }; + let positive = regex::Regex::new(".*likely.*").unwrap(); + + res.stdout_does_not_match(&positive); + } +}