mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Expand CmdResult
's API (#1977)
This commit is contained in:
parent
d3cd1f960c
commit
a57f17ac5b
2 changed files with 171 additions and 39 deletions
|
@ -14,7 +14,7 @@ fn test_utf8() {
|
|||
.args(&["-lwmcL"])
|
||||
.pipe_in_fixture("UTF_8_test.txt")
|
||||
.run()
|
||||
.stdout_is(" 0 0 0 0 0\n");
|
||||
.stdout_is(" 300 4969 22781 22213 79\n");
|
||||
// GNU returns " 300 2086 22219 22781 79"
|
||||
// TODO: we should fix that to match GNU's behavior
|
||||
}
|
||||
|
|
|
@ -53,9 +53,10 @@ pub fn is_wsl() -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: &Option<Rc<TempDir>>, file_rel_path: S) -> String {
|
||||
/// Read a test scenario fixture, returning its bytes
|
||||
fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: &Option<Rc<TempDir>>, file_rel_path: S) -> Vec<u8> {
|
||||
let tmpdir_path = tmpd.as_ref().unwrap().as_ref().path();
|
||||
AtPath::new(tmpdir_path).read(file_rel_path.as_ref().to_str().unwrap())
|
||||
AtPath::new(tmpdir_path).read_bytes(file_rel_path.as_ref().to_str().unwrap())
|
||||
}
|
||||
|
||||
/// A command result is the outputs of a command (streams and status code)
|
||||
|
@ -69,29 +70,95 @@ pub struct CmdResult {
|
|||
/// zero-exit from running the Command?
|
||||
/// see [`success`]
|
||||
pub success: bool,
|
||||
/// captured utf-8 standard output after running the Command
|
||||
/// captured standard output after running the Command
|
||||
pub stdout: String,
|
||||
/// captured utf-8 standard error after running the Command
|
||||
/// captured standard error after running the Command
|
||||
pub stderr: String,
|
||||
}
|
||||
|
||||
impl CmdResult {
|
||||
/// Returns a reference to the program's standard output as a slice of bytes
|
||||
pub fn stdout(&self) -> &[u8] {
|
||||
&self.stdout.as_bytes()
|
||||
}
|
||||
|
||||
/// Returns the program's standard output as a string slice
|
||||
pub fn stdout_str(&self) -> &str {
|
||||
&self.stdout
|
||||
}
|
||||
|
||||
/// Returns the program's standard output as a string
|
||||
/// consumes self
|
||||
pub fn stdout_move_str(self) -> String {
|
||||
self.stdout
|
||||
}
|
||||
|
||||
/// Returns the program's standard output as a vec of bytes
|
||||
/// consumes self
|
||||
pub fn stdout_move_bytes(self) -> Vec<u8> {
|
||||
Vec::from(self.stdout)
|
||||
}
|
||||
|
||||
/// Returns a reference to the program's standard error as a slice of bytes
|
||||
pub fn stderr(&self) -> &[u8] {
|
||||
&self.stderr.as_bytes()
|
||||
}
|
||||
|
||||
/// Returns the program's standard error as a string slice
|
||||
pub fn stderr_str(&self) -> &str {
|
||||
&self.stderr
|
||||
}
|
||||
|
||||
/// Returns the program's standard error as a string
|
||||
/// consumes self
|
||||
pub fn stderr_move_str(self) -> String {
|
||||
self.stderr
|
||||
}
|
||||
|
||||
/// Returns the program's standard error as a vec of bytes
|
||||
/// consumes self
|
||||
pub fn stderr_move_bytes(self) -> Vec<u8> {
|
||||
Vec::from(self.stderr)
|
||||
}
|
||||
|
||||
/// Returns the program's exit code
|
||||
/// Panics if not run
|
||||
pub fn code(&self) -> i32 {
|
||||
self.code.expect("Program must be run first")
|
||||
}
|
||||
|
||||
/// Returns the program's TempDir
|
||||
/// Panics if not present
|
||||
pub fn tmpd(&self) -> Rc<TempDir> {
|
||||
match &self.tmpd {
|
||||
Some(ptr) => ptr.clone(),
|
||||
None => {
|
||||
panic!("Command not associated with a TempDir")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the program succeeded
|
||||
pub fn succeeded(&self) -> bool {
|
||||
self.success
|
||||
}
|
||||
|
||||
/// asserts that the command resulted in a success (zero) status code
|
||||
pub fn success(&self) -> Box<&CmdResult> {
|
||||
pub fn success(&self) -> &CmdResult {
|
||||
assert!(self.success);
|
||||
Box::new(self)
|
||||
self
|
||||
}
|
||||
|
||||
/// asserts that the command resulted in a failure (non-zero) status code
|
||||
pub fn failure(&self) -> Box<&CmdResult> {
|
||||
pub fn failure(&self) -> &CmdResult {
|
||||
assert!(!self.success);
|
||||
Box::new(self)
|
||||
self
|
||||
}
|
||||
|
||||
/// asserts that the command's exit code is the same as the given one
|
||||
pub fn status_code(&self, code: i32) -> Box<&CmdResult> {
|
||||
pub fn status_code(&self, code: i32) -> &CmdResult {
|
||||
assert!(self.code == Some(code));
|
||||
Box::new(self)
|
||||
self
|
||||
}
|
||||
|
||||
/// asserts that the command resulted in empty (zero-length) stderr stream output
|
||||
|
@ -99,9 +166,9 @@ impl CmdResult {
|
|||
/// but you might find yourself using this function if
|
||||
/// 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) -> Box<&CmdResult> {
|
||||
assert_eq!(self.stderr, "");
|
||||
Box::new(self)
|
||||
pub fn no_stderr(&self) -> &CmdResult {
|
||||
assert!(self.stderr.is_empty());
|
||||
self
|
||||
}
|
||||
|
||||
/// asserts that the command resulted in empty (zero-length) stderr stream output
|
||||
|
@ -110,62 +177,102 @@ impl CmdResult {
|
|||
/// but you might find yourself using this function if
|
||||
/// 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) -> Box<&CmdResult> {
|
||||
assert_eq!(self.stdout, "");
|
||||
Box::new(self)
|
||||
pub fn no_stdout(&self) -> &CmdResult {
|
||||
assert!(self.stdout.is_empty());
|
||||
self
|
||||
}
|
||||
|
||||
/// asserts that the command resulted in stdout stream output that equals the
|
||||
/// passed in value, trailing whitespace are kept to force strict comparison (#1235)
|
||||
/// stdout_only is a better choice unless stderr may or will be non-empty
|
||||
pub fn stdout_is<T: AsRef<str>>(&self, msg: T) -> Box<&CmdResult> {
|
||||
pub fn stdout_is<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
|
||||
assert_eq!(self.stdout, String::from(msg.as_ref()));
|
||||
Box::new(self)
|
||||
self
|
||||
}
|
||||
|
||||
/// asserts that the command resulted in stdout stream output,
|
||||
/// whose bytes equal those of the passed in slice
|
||||
pub fn stdout_is_bytes<T: AsRef<[u8]>>(&self, msg: T) -> &CmdResult {
|
||||
assert_eq!(self.stdout.as_bytes(), msg.as_ref());
|
||||
self
|
||||
}
|
||||
|
||||
/// like stdout_is(...), but expects the contents of the file at the provided relative path
|
||||
pub fn stdout_is_fixture<T: AsRef<OsStr>>(&self, file_rel_path: T) -> Box<&CmdResult> {
|
||||
pub fn stdout_is_fixture<T: AsRef<OsStr>>(&self, file_rel_path: T) -> &CmdResult {
|
||||
let contents = read_scenario_fixture(&self.tmpd, file_rel_path);
|
||||
self.stdout_is(contents)
|
||||
self.stdout_is_bytes(contents)
|
||||
}
|
||||
|
||||
/// asserts that the command resulted in stderr stream output that equals the
|
||||
/// passed in value, when both are trimmed of trailing whitespace
|
||||
/// stderr_only is a better choice unless stdout may or will be non-empty
|
||||
pub fn stderr_is<T: AsRef<str>>(&self, msg: T) -> Box<&CmdResult> {
|
||||
pub fn stderr_is<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
|
||||
assert_eq!(
|
||||
self.stderr.trim_end(),
|
||||
String::from(msg.as_ref()).trim_end()
|
||||
);
|
||||
Box::new(self)
|
||||
self
|
||||
}
|
||||
|
||||
/// asserts that the command resulted in stderr stream output,
|
||||
/// whose bytes equal those of the passed in slice
|
||||
pub fn stderr_is_bytes<T: AsRef<[u8]>>(&self, msg: T) -> &CmdResult {
|
||||
assert_eq!(self.stderr.as_bytes(), msg.as_ref());
|
||||
self
|
||||
}
|
||||
|
||||
/// asserts that
|
||||
/// 1. the command resulted in stdout stream output that equals the
|
||||
/// passed in value, when both are trimmed of trailing whitespace
|
||||
/// and 2. the command resulted in empty (zero-length) stderr stream output
|
||||
pub fn stdout_only<T: AsRef<str>>(&self, msg: T) -> Box<&CmdResult> {
|
||||
pub fn stdout_only<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
|
||||
self.no_stderr().stdout_is(msg)
|
||||
}
|
||||
|
||||
/// asserts that
|
||||
/// 1. the command resulted in a stdout stream whose bytes
|
||||
/// equal those of the passed in value
|
||||
/// 2. the command resulted in an empty stderr stream
|
||||
pub fn stdout_only_bytes<T: AsRef<[u8]>>(&self, msg: T) -> &CmdResult {
|
||||
self.no_stderr().stdout_is_bytes(msg)
|
||||
}
|
||||
|
||||
/// like stdout_only(...), but expects the contents of the file at the provided relative path
|
||||
pub fn stdout_only_fixture<T: AsRef<OsStr>>(&self, file_rel_path: T) -> Box<&CmdResult> {
|
||||
pub fn stdout_only_fixture<T: AsRef<OsStr>>(&self, file_rel_path: T) -> &CmdResult {
|
||||
let contents = read_scenario_fixture(&self.tmpd, file_rel_path);
|
||||
self.stdout_only(contents)
|
||||
self.stdout_only_bytes(contents)
|
||||
}
|
||||
|
||||
/// asserts that
|
||||
/// 1. the command resulted in stderr stream output that equals the
|
||||
/// passed in value, when both are trimmed of trailing whitespace
|
||||
/// and 2. the command resulted in empty (zero-length) stdout stream output
|
||||
pub fn stderr_only<T: AsRef<str>>(&self, msg: T) -> Box<&CmdResult> {
|
||||
pub fn stderr_only<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
|
||||
self.no_stdout().stderr_is(msg)
|
||||
}
|
||||
|
||||
pub fn fails_silently(&self) -> Box<&CmdResult> {
|
||||
/// asserts that
|
||||
/// 1. the command resulted in a stderr stream whose bytes equal the ones
|
||||
/// of the passed value
|
||||
/// 2. the command resulted in an empty stdout stream
|
||||
pub fn stderr_only_bytes<T: AsRef<[u8]>>(&self, msg: T) -> &CmdResult {
|
||||
self.no_stderr().stderr_is_bytes(msg)
|
||||
}
|
||||
|
||||
pub fn fails_silently(&self) -> &CmdResult {
|
||||
assert!(!self.success);
|
||||
assert_eq!(self.stderr, "");
|
||||
Box::new(self)
|
||||
assert!(self.stderr.is_empty());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn stdout_contains<T: AsRef<str>>(&self, cmp: T) -> &CmdResult {
|
||||
assert!(self.stdout_str().contains(cmp.as_ref()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn stderr_contains<T: AsRef<str>>(&self, cmp: &T) -> &CmdResult {
|
||||
assert!(self.stderr_str().contains(cmp.as_ref()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,11 +362,25 @@ impl AtPath {
|
|||
contents
|
||||
}
|
||||
|
||||
pub fn read_bytes(&self, name: &str) -> Vec<u8> {
|
||||
let mut f = self.open(name);
|
||||
let mut contents = Vec::new();
|
||||
f.read_to_end(&mut contents)
|
||||
.unwrap_or_else(|e| panic!("Couldn't read {}: {}", name, e));
|
||||
contents
|
||||
}
|
||||
|
||||
pub fn write(&self, name: &str, contents: &str) {
|
||||
log_info("open(write)", self.plus_as_string(name));
|
||||
let _ = std::fs::write(self.plus(name), contents);
|
||||
}
|
||||
|
||||
pub fn write_bytes(&self, name: &str, contents: &[u8]) {
|
||||
log_info("open(write)", self.plus_as_string(name));
|
||||
std::fs::write(self.plus(name), contents)
|
||||
.unwrap_or_else(|e| panic!("Couldn't write {}: {}", name, e));
|
||||
}
|
||||
|
||||
pub fn append(&self, name: &str, contents: &str) {
|
||||
log_info("open(append)", self.plus_as_string(name));
|
||||
let mut f = OpenOptions::new()
|
||||
|
@ -270,6 +391,17 @@ impl AtPath {
|
|||
let _ = f.write(contents.as_bytes());
|
||||
}
|
||||
|
||||
pub fn append_bytes(&self, name: &str, contents: &[u8]) {
|
||||
log_info("open(append)", self.plus_as_string(name));
|
||||
let mut f = OpenOptions::new()
|
||||
.write(true)
|
||||
.append(true)
|
||||
.open(self.plus(name))
|
||||
.unwrap();
|
||||
f.write_all(contents)
|
||||
.unwrap_or_else(|e| panic!("Couldn't append to {}: {}", name, e));
|
||||
}
|
||||
|
||||
pub fn mkdir(&self, dir: &str) {
|
||||
log_info("mkdir", self.plus_as_string(dir));
|
||||
fs::create_dir(&self.plus(dir)).unwrap();
|
||||
|
@ -521,19 +653,19 @@ impl UCommand {
|
|||
|
||||
/// Add a parameter to the invocation. Path arguments are treated relative
|
||||
/// to the test environment directory.
|
||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> Box<&mut UCommand> {
|
||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut UCommand {
|
||||
if self.has_run {
|
||||
panic!(ALREADY_RUN);
|
||||
}
|
||||
self.comm_string.push_str(" ");
|
||||
self.comm_string.push_str(arg.as_ref().to_str().unwrap());
|
||||
self.raw.arg(arg.as_ref());
|
||||
Box::new(self)
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple parameters to the invocation. Path arguments are treated relative
|
||||
/// to the test environment directory.
|
||||
pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> Box<&mut UCommand> {
|
||||
pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut UCommand {
|
||||
if self.has_run {
|
||||
panic!(MULTIPLE_STDIN_MEANINGLESS);
|
||||
}
|
||||
|
@ -543,25 +675,25 @@ impl UCommand {
|
|||
}
|
||||
|
||||
self.raw.args(args.as_ref());
|
||||
Box::new(self)
|
||||
self
|
||||
}
|
||||
|
||||
/// provides stdinput to feed in to the command when spawned
|
||||
pub fn pipe_in<T: Into<Vec<u8>>>(&mut self, input: T) -> Box<&mut UCommand> {
|
||||
pub fn pipe_in<T: Into<Vec<u8>>>(&mut self, input: T) -> &mut UCommand {
|
||||
if self.stdin.is_some() {
|
||||
panic!(MULTIPLE_STDIN_MEANINGLESS);
|
||||
}
|
||||
self.stdin = Some(input.into());
|
||||
Box::new(self)
|
||||
self
|
||||
}
|
||||
|
||||
/// like pipe_in(...), but uses the contents of the file at the provided relative path as the piped in data
|
||||
pub fn pipe_in_fixture<S: AsRef<OsStr>>(&mut self, file_rel_path: S) -> Box<&mut UCommand> {
|
||||
pub fn pipe_in_fixture<S: AsRef<OsStr>>(&mut self, file_rel_path: S) -> &mut UCommand {
|
||||
let contents = read_scenario_fixture(&self.tmpd, file_rel_path);
|
||||
self.pipe_in(contents)
|
||||
}
|
||||
|
||||
pub fn env<K, V>(&mut self, key: K, val: V) -> Box<&mut UCommand>
|
||||
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut UCommand
|
||||
where
|
||||
K: AsRef<OsStr>,
|
||||
V: AsRef<OsStr>,
|
||||
|
@ -570,7 +702,7 @@ impl UCommand {
|
|||
panic!(ALREADY_RUN);
|
||||
}
|
||||
self.raw.env(key, val);
|
||||
Box::new(self)
|
||||
self
|
||||
}
|
||||
|
||||
/// Spawns the command, feeds the stdin if any, and returns the
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue