tests/util: Use ExitStatus instead of code, success in CmdResult. Rewrite tests for CmdResult

This commit is contained in:
Joining7943 2023-01-04 21:30:14 +01:00
parent 37e06edadc
commit 19db042022

View file

@ -25,7 +25,7 @@ use std::os::windows::fs::{symlink_dir, symlink_file};
#[cfg(windows)] #[cfg(windows)]
use std::path::MAIN_SEPARATOR; use std::path::MAIN_SEPARATOR;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output, Stdio}; use std::process::{Child, Command, ExitStatus, Output, Stdio};
use std::rc::Rc; use std::rc::Rc;
use std::sync::mpsc::{self, RecvTimeoutError}; use std::sync::mpsc::{self, RecvTimeoutError};
use std::thread::{sleep, JoinHandle}; use std::thread::{sleep, JoinHandle};
@ -73,10 +73,7 @@ pub struct CmdResult {
//tmpd is used for convenience functions for asserts against fixtures //tmpd is used for convenience functions for asserts against fixtures
tmpd: Option<Rc<TempDir>>, tmpd: Option<Rc<TempDir>>,
/// exit status for command (if there is one) /// exit status for command (if there is one)
code: Option<i32>, exit_status: Option<ExitStatus>,
/// zero-exit from running the Command?
/// see [`success`]
success: bool,
/// captured standard output after running the Command /// captured standard output after running the Command
stdout: Vec<u8>, stdout: Vec<u8>,
/// captured standard error after running the Command /// captured standard error after running the Command
@ -88,8 +85,7 @@ impl CmdResult {
bin_path: String, bin_path: String,
util_name: Option<String>, util_name: Option<String>,
tmpd: Option<Rc<TempDir>>, tmpd: Option<Rc<TempDir>>,
code: Option<i32>, exit_status: Option<ExitStatus>,
success: bool,
stdout: T, stdout: T,
stderr: U, stderr: U,
) -> Self ) -> Self
@ -101,8 +97,7 @@ impl CmdResult {
bin_path, bin_path,
util_name, util_name,
tmpd, tmpd,
code, exit_status,
success,
stdout: stdout.into(), stdout: stdout.into(),
stderr: stderr.into(), stderr: stderr.into(),
} }
@ -117,8 +112,7 @@ impl CmdResult {
self.bin_path.clone(), self.bin_path.clone(),
self.util_name.clone(), self.util_name.clone(),
self.tmpd.clone(), self.tmpd.clone(),
self.code, self.exit_status,
self.success,
function(&self.stdout), function(&self.stdout),
self.stderr.as_slice(), self.stderr.as_slice(),
) )
@ -133,8 +127,7 @@ impl CmdResult {
self.bin_path.clone(), self.bin_path.clone(),
self.util_name.clone(), self.util_name.clone(),
self.tmpd.clone(), self.tmpd.clone(),
self.code, self.exit_status,
self.success,
function(self.stdout_str()), function(self.stdout_str()),
self.stderr.as_slice(), self.stderr.as_slice(),
) )
@ -149,8 +142,7 @@ impl CmdResult {
self.bin_path.clone(), self.bin_path.clone(),
self.util_name.clone(), self.util_name.clone(),
self.tmpd.clone(), self.tmpd.clone(),
self.code, self.exit_status,
self.success,
self.stdout.as_slice(), self.stdout.as_slice(),
function(&self.stderr), function(&self.stderr),
) )
@ -165,8 +157,7 @@ impl CmdResult {
self.bin_path.clone(), self.bin_path.clone(),
self.util_name.clone(), self.util_name.clone(),
self.tmpd.clone(), self.tmpd.clone(),
self.code, self.exit_status,
self.success,
self.stdout.as_slice(), self.stdout.as_slice(),
function(self.stderr_str()), function(self.stderr_str()),
) )
@ -219,8 +210,10 @@ impl CmdResult {
/// Returns the program's exit code /// Returns the program's exit code
/// Panics if not run or has not finished yet for example when run with `run_no_wait()` /// Panics if not run or has not finished yet for example when run with `run_no_wait()`
pub fn code(&self) -> i32 { pub fn code(&self) -> i32 {
self.code self.exit_status
.expect("Program must be run first or has not finished, yet") .expect("Program must be run first or has not finished, yet")
.code()
.unwrap()
} }
#[track_caller] #[track_caller]
@ -240,14 +233,14 @@ impl CmdResult {
/// Returns whether the program succeeded /// Returns whether the program succeeded
pub fn succeeded(&self) -> bool { pub fn succeeded(&self) -> bool {
self.success self.exit_status.map_or(true, |e| e.success())
} }
/// asserts that the command resulted in a success (zero) status code /// asserts that the command resulted in a success (zero) status code
#[track_caller] #[track_caller]
pub fn success(&self) -> &Self { pub fn success(&self) -> &Self {
assert!( assert!(
self.success, self.succeeded(),
"Command was expected to succeed.\nstdout = {}\n stderr = {}", "Command was expected to succeed.\nstdout = {}\n stderr = {}",
self.stdout_str(), self.stdout_str(),
self.stderr_str() self.stderr_str()
@ -259,7 +252,7 @@ impl CmdResult {
#[track_caller] #[track_caller]
pub fn failure(&self) -> &Self { pub fn failure(&self) -> &Self {
assert!( assert!(
!self.success, !self.succeeded(),
"Command was expected to fail.\nstdout = {}\n stderr = {}", "Command was expected to fail.\nstdout = {}\n stderr = {}",
self.stdout_str(), self.stdout_str(),
self.stderr_str() self.stderr_str()
@ -476,7 +469,7 @@ impl CmdResult {
#[track_caller] #[track_caller]
pub fn fails_silently(&self) -> &Self { pub fn fails_silently(&self) -> &Self {
assert!(!self.success); assert!(!self.succeeded());
assert!(self.stderr.is_empty()); assert!(self.stderr.is_empty());
self self
} }
@ -1447,12 +1440,10 @@ impl<'a> UChildAssertion<'a> {
} }
fn with_output(&mut self, mode: AssertionMode) -> CmdResult { fn with_output(&mut self, mode: AssertionMode) -> CmdResult {
let (code, success) = match self.uchild.is_alive() { let exit_status = if self.uchild.is_alive() {
true => (None, true), None
false => { } else {
let status = self.uchild.raw.wait().unwrap(); Some(self.uchild.raw.wait().unwrap())
(status.code(), status.success())
}
}; };
let (stdout, stderr) = match mode { let (stdout, stderr) = match mode {
AssertionMode::All => ( AssertionMode::All => (
@ -1469,8 +1460,7 @@ impl<'a> UChildAssertion<'a> {
bin_path: self.uchild.bin_path.clone(), bin_path: self.uchild.bin_path.clone(),
util_name: self.uchild.util_name.clone(), util_name: self.uchild.util_name.clone(),
tmpd: self.uchild.tmpd.clone(), tmpd: self.uchild.tmpd.clone(),
code, exit_status,
success,
stdout, stdout,
stderr, stderr,
} }
@ -1699,8 +1689,7 @@ impl UChild {
bin_path, bin_path,
util_name, util_name,
tmpd, tmpd,
code: output.status.code(), exit_status: Some(output.status),
success: output.status.success(),
stdout: output.stdout, stdout: output.stdout,
stderr: output.stderr, stderr: output.stderr,
}) })
@ -2233,8 +2222,7 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
ts.bin_path.as_os_str().to_str().unwrap().to_string(), ts.bin_path.as_os_str().to_str().unwrap().to_string(),
Some(ts.util_name.clone()), Some(ts.util_name.clone()),
Some(result.tmpd()), Some(result.tmpd()),
Some(result.code()), result.exit_status,
result.succeeded(),
stdout.as_bytes(), stdout.as_bytes(),
stderr.as_bytes(), stderr.as_bytes(),
)) ))
@ -2309,152 +2297,156 @@ pub fn run_ucmd_as_root(
mod tests { mod tests {
// spell-checker:ignore (tests) asdfsadfa // spell-checker:ignore (tests) asdfsadfa
use super::*; use super::*;
use tempfile::tempdir;
#[cfg(windows)]
fn run_cmd(cmd: &str) -> CmdResult {
UCommand::new_from_tmp::<&str, String>("cmd", &None, Rc::new(tempdir().unwrap()), true)
.arg("/C")
.arg(cmd)
.run()
}
#[cfg(not(windows))]
fn run_cmd(cmd: &str) -> CmdResult {
return UCommand::new_from_tmp::<&str, String>(
"sh",
&None,
Rc::new(tempdir().unwrap()),
true,
)
.arg("-c")
.arg(cmd)
.run();
}
#[test] #[test]
fn test_code_is() { fn test_command_result_when_no_output_with_exit_32() {
let res = CmdResult { let result = run_cmd("exit 32");
bin_path: String::new(),
util_name: None, if cfg!(windows) {
tmpd: None, std::assert!(result.bin_path.ends_with("cmd"));
code: Some(32), } else {
success: false, std::assert!(result.bin_path.ends_with("sh"));
stdout: "".into(), }
stderr: "".into(),
}; std::assert!(result.util_name.is_none());
res.code_is(32); std::assert!(result.tmpd.is_some());
assert!(result.exit_status.is_some());
std::assert_eq!(result.code(), 32);
result.code_is(32);
assert!(!result.succeeded());
result.failure();
result.fails_silently();
assert!(result.stderr.is_empty());
assert!(result.stdout.is_empty());
result.no_output();
result.no_stderr();
result.no_stdout();
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_code_is_fail() { fn test_command_result_when_exit_32_then_success_panic() {
let res = CmdResult { run_cmd("exit 32").success();
bin_path: String::new(),
util_name: None,
tmpd: None,
code: Some(32),
success: false,
stdout: "".into(),
stderr: "".into(),
};
res.code_is(1);
} }
#[test] #[test]
fn test_failure() { fn test_command_result_when_no_output_with_exit_0() {
let res = CmdResult { let result = run_cmd("exit 0");
bin_path: String::new(),
util_name: None, assert!(result.exit_status.is_some());
tmpd: None, std::assert_eq!(result.code(), 0);
code: None, result.code_is(0);
success: false, assert!(result.succeeded());
stdout: "".into(), result.success();
stderr: "".into(), assert!(result.stderr.is_empty());
}; assert!(result.stdout.is_empty());
res.failure(); result.no_output();
result.no_stderr();
result.no_stdout();
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_failure_fail() { fn test_command_result_when_exit_0_then_failure_panics() {
let res = CmdResult { run_cmd("exit 0").failure();
bin_path: String::new(),
util_name: None,
tmpd: None,
code: None,
success: true,
stdout: "".into(),
stderr: "".into(),
};
res.failure();
}
#[test]
fn test_success() {
let res = CmdResult {
bin_path: String::new(),
util_name: None,
tmpd: None,
code: None,
success: true,
stdout: "".into(),
stderr: "".into(),
};
res.success();
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_success_fail() { fn test_command_result_when_exit_0_then_silent_failure_panics() {
let res = CmdResult { run_cmd("exit 0").fails_silently();
bin_path: String::new(),
util_name: None,
tmpd: None,
code: None,
success: false,
stdout: "".into(),
stderr: "".into(),
};
res.success();
} }
#[test] #[test]
fn test_no_stderr_output() { fn test_command_result_when_stdout_with_exit_0() {
let res = CmdResult { #[cfg(windows)]
bin_path: String::new(), let (result, vector, string) = (
util_name: None, run_cmd("echo hello& exit 0"),
tmpd: None, vec![b'h', b'e', b'l', b'l', b'o', b'\r', b'\n'],
code: None, "hello\r\n",
success: true, );
stdout: "".into(), #[cfg(not(windows))]
stderr: "".into(), let (result, vector, string) = (
}; run_cmd("echo hello; exit 0"),
res.no_stderr(); vec![b'h', b'e', b'l', b'l', b'o', b'\n'],
res.no_stdout(); "hello\n",
);
assert!(result.exit_status.is_some());
std::assert_eq!(result.code(), 0);
result.code_is(0);
assert!(result.succeeded());
result.success();
assert!(result.stderr.is_empty());
std::assert_eq!(result.stdout, vector);
result.no_stderr();
result.stdout_is(string);
result.stdout_is_bytes(&vector);
result.stdout_only(string);
result.stdout_only_bytes(&vector);
} }
#[test] #[test]
#[should_panic] fn test_command_result_when_stderr_with_exit_0() {
fn test_no_stderr_fail() { #[cfg(windows)]
let res = CmdResult { let (result, vector, string) = (
bin_path: String::new(), run_cmd("echo hello>&2& exit 0"),
util_name: None, vec![b'h', b'e', b'l', b'l', b'o', b'\r', b'\n'],
tmpd: None, "hello\r\n",
code: None, );
success: true, #[cfg(not(windows))]
stdout: "".into(), let (result, vector, string) = (
stderr: "asdfsadfa".into(), run_cmd("echo hello >&2; exit 0"),
}; vec![b'h', b'e', b'l', b'l', b'o', b'\n'],
"hello\n",
);
res.no_stderr(); assert!(result.exit_status.is_some());
} std::assert_eq!(result.code(), 0);
result.code_is(0);
#[test] assert!(result.succeeded());
#[should_panic] result.success();
fn test_no_stdout_fail() { assert!(result.stdout.is_empty());
let res = CmdResult { result.no_stdout();
bin_path: String::new(), std::assert_eq!(result.stderr, vector);
util_name: None, result.stderr_is(string);
tmpd: None, result.stderr_is_bytes(&vector);
code: None, result.stderr_only(string);
success: true, result.stderr_only_bytes(&vector);
stdout: "asdfsadfa".into(),
stderr: "".into(),
};
res.no_stdout();
} }
#[test] #[test]
fn test_std_does_not_contain() { fn test_std_does_not_contain() {
let res = CmdResult { #[cfg(windows)]
bin_path: String::new(), let res = run_cmd(
util_name: None, "(echo This is a likely error message& echo This is a likely error message>&2) & exit 0",
tmpd: None, );
code: None, #[cfg(not(windows))]
success: true, let res = run_cmd(
stdout: "This is a likely error message\n".into(), "echo This is a likely error message; echo This is a likely error message >&2; exit 0",
stderr: "This is a likely error message\n".into(), );
};
res.stdout_does_not_contain("unlikely"); res.stdout_does_not_contain("unlikely");
res.stderr_does_not_contain("unlikely"); res.stderr_does_not_contain("unlikely");
} }
@ -2462,15 +2454,10 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn test_stdout_does_not_contain_fail() { fn test_stdout_does_not_contain_fail() {
let res = CmdResult { #[cfg(windows)]
bin_path: String::new(), let res = run_cmd("echo This is a likely error message& exit 0");
util_name: None, #[cfg(not(windows))]
tmpd: None, let res = run_cmd("echo This is a likely error message; exit 0");
code: None,
success: true,
stdout: "This is a likely error message\n".into(),
stderr: "".into(),
};
res.stdout_does_not_contain("likely"); res.stdout_does_not_contain("likely");
} }
@ -2478,30 +2465,25 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn test_stderr_does_not_contain_fail() { fn test_stderr_does_not_contain_fail() {
let res = CmdResult { #[cfg(windows)]
bin_path: String::new(), let res = run_cmd("echo This is a likely error message>&2 & exit 0");
util_name: None, #[cfg(not(windows))]
tmpd: None, let res = run_cmd("echo This is a likely error message >&2; exit 0");
code: None,
success: true,
stdout: "".into(),
stderr: "This is a likely error message\n".into(),
};
res.stderr_does_not_contain("likely"); res.stderr_does_not_contain("likely");
} }
#[test] #[test]
fn test_stdout_matches() { fn test_stdout_matches() {
let res = CmdResult { #[cfg(windows)]
bin_path: String::new(), let res = run_cmd(
util_name: None, "(echo This is a likely error message& echo This is a likely error message>&2 ) & exit 0",
tmpd: None, );
code: None, #[cfg(not(windows))]
success: true, let res = run_cmd(
stdout: "This is a likely error message\n".into(), "echo This is a likely error message; echo This is a likely error message >&2; exit 0",
stderr: "This is a likely error message\n".into(), );
};
let positive = regex::Regex::new(".*likely.*").unwrap(); let positive = regex::Regex::new(".*likely.*").unwrap();
let negative = regex::Regex::new(".*unlikely.*").unwrap(); let negative = regex::Regex::new(".*unlikely.*").unwrap();
res.stdout_matches(&positive); res.stdout_matches(&positive);
@ -2511,66 +2493,52 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn test_stdout_matches_fail() { fn test_stdout_matches_fail() {
let res = CmdResult { #[cfg(windows)]
bin_path: String::new(), let res = run_cmd(
util_name: None, "(echo This is a likely error message& echo This is a likely error message>&2) & exit 0",
tmpd: None, );
code: None, #[cfg(not(windows))]
success: true, let res = run_cmd(
stdout: "This is a likely error message\n".into(), "echo This is a likely error message; echo This is a likely error message >&2; exit 0",
stderr: "This is a likely error message\n".into(), );
};
let negative = regex::Regex::new(".*unlikely.*").unwrap();
let negative = regex::Regex::new(".*unlikely.*").unwrap();
res.stdout_matches(&negative); res.stdout_matches(&negative);
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_stdout_not_matches_fail() { fn test_stdout_not_matches_fail() {
let res = CmdResult { #[cfg(windows)]
bin_path: String::new(), let res = run_cmd(
util_name: None, "(echo This is a likely error message& echo This is a likely error message>&2) & exit 0",
tmpd: None, );
code: None, #[cfg(not(windows))]
success: true, let res = run_cmd(
stdout: "This is a likely error message\n".into(), "echo This is a likely error message; echo This is a likely error message >&2; exit 0",
stderr: "This is a likely error message\n".into(), );
};
let positive = regex::Regex::new(".*likely.*").unwrap();
let positive = regex::Regex::new(".*likely.*").unwrap();
res.stdout_does_not_match(&positive); res.stdout_does_not_match(&positive);
} }
#[cfg(feature = "echo")]
#[test] #[test]
fn test_normalized_newlines_stdout_is() { fn test_normalized_newlines_stdout_is() {
let res = CmdResult { let ts = TestScenario::new("echo");
bin_path: String::new(), let res = ts.ucmd().args(&["-ne", "A\r\nB\nC"]).run();
util_name: None,
tmpd: None,
code: None,
success: true,
stdout: "A\r\nB\nC".into(),
stderr: "".into(),
};
res.normalized_newlines_stdout_is("A\r\nB\nC"); res.normalized_newlines_stdout_is("A\r\nB\nC");
res.normalized_newlines_stdout_is("A\nB\nC"); res.normalized_newlines_stdout_is("A\nB\nC");
res.normalized_newlines_stdout_is("A\nB\r\nC"); res.normalized_newlines_stdout_is("A\nB\r\nC");
} }
#[cfg(feature = "echo")]
#[test] #[test]
#[should_panic] #[should_panic]
fn test_normalized_newlines_stdout_is_fail() { fn test_normalized_newlines_stdout_is_fail() {
let res = CmdResult { let ts = TestScenario::new("echo");
bin_path: String::new(), let res = ts.ucmd().args(&["-ne", "A\r\nB\nC"]).run();
util_name: None,
tmpd: None,
code: None,
success: true,
stdout: "A\r\nB\nC".into(),
stderr: "".into(),
};
res.normalized_newlines_stdout_is("A\r\nB\nC\n"); res.normalized_newlines_stdout_is("A\r\nB\nC\n");
} }