1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

more ergonomic testing

This commit is contained in:
Nathan Ross 2016-02-17 00:03:52 -05:00
parent aafd20d565
commit 5e764ecf6e
5 changed files with 97 additions and 34 deletions

View file

@ -27,6 +27,7 @@ static FIXTURES_DIR: &'static str = "tests/fixtures";
static ALREADY_RUN: &'static str = " you have already run this UCommand, if you want to run \ static ALREADY_RUN: &'static str = " you have already run this UCommand, if you want to run \
another command in the same test, use TestSet::new instead of \ another command in the same test, use TestSet::new instead of \
testing();"; testing();";
static MULTIPLE_STDIN_MEANINGLESS: &'static str = "Ucommand is designed around a typical use case of: provide args and input stream -> spawn process -> block until completion -> return output streams. For verifying that a particular section of the input stream is what causes a particular behavior, use the Command type directly.";
#[macro_export] #[macro_export]
macro_rules! assert_empty_stderr( macro_rules! assert_empty_stderr(
@ -53,6 +54,44 @@ pub struct CmdResult {
pub stderr: String, pub stderr: String,
} }
impl CmdResult {
pub fn success(&self) -> Box<&CmdResult> {
assert!(self.success);
Box::new(self)
}
pub fn failure(&self) -> Box<&CmdResult> {
assert!(!self.success);
Box::new(self)
}
pub fn no_stderr(&self) -> Box<&CmdResult> {
assert!(self.stderr.len() == 0);
Box::new(self)
}
pub fn no_stdout(&self) -> Box<&CmdResult> {
assert!(self.stdout.len() == 0);
Box::new(self)
}
pub fn stdout_is<T: AsRef<str>>(&self, msg: T) -> Box<&CmdResult> {
assert!(self.stdout.trim_right() == String::from(msg.as_ref()).trim_right());
Box::new(self)
}
pub fn stderr_is<T: AsRef<str>>(&self, msg: T) -> Box<&CmdResult> {
assert!(self.stderr.trim_right() == String::from(msg.as_ref()).trim_right());
Box::new(self)
}
pub fn stdout_only<T: AsRef<str>>(&self, msg: T) -> Box<&CmdResult> {
self.stdout_is(msg).no_stderr()
}
pub fn stderr_only<T: AsRef<str>>(&self, msg: T) -> Box<&CmdResult> {
self.stderr_is(msg).no_stdout()
}
pub fn fails_silently(&self) -> Box<&CmdResult> {
assert!(!self.success);
assert!(self.stderr.len() == 0);
Box::new(self)
}
}
pub fn log_info<T: AsRef<str>, U: AsRef<str>>(msg: T, par: U) { pub fn log_info<T: AsRef<str>, U: AsRef<str>>(msg: T, par: U) {
println!("{}: {}", msg.as_ref(), par.as_ref()); println!("{}: {}", msg.as_ref(), par.as_ref());
} }
@ -272,6 +311,7 @@ pub struct UCommand {
comm_string: String, comm_string: String,
tmpd: Option<Rc<TempDir>>, tmpd: Option<Rc<TempDir>>,
has_run: bool, has_run: bool,
stdin: Option<Vec<u8>>
} }
impl UCommand { impl UCommand {
pub fn new<T: AsRef<OsStr>, U: AsRef<OsStr>>(arg: T, curdir: U, env_clear: bool) -> UCommand { pub fn new<T: AsRef<OsStr>, U: AsRef<OsStr>>(arg: T, curdir: U, env_clear: bool) -> UCommand {
@ -299,6 +339,7 @@ impl UCommand {
cmd cmd
}, },
comm_string: String::from(arg.as_ref().to_str().unwrap()), comm_string: String::from(arg.as_ref().to_str().unwrap()),
stdin: None
} }
} }
pub fn new_from_tmp<T: AsRef<OsStr>>(arg: T, tmpd: Rc<TempDir>, env_clear: bool) -> UCommand { pub fn new_from_tmp<T: AsRef<OsStr>>(arg: T, tmpd: Rc<TempDir>, env_clear: bool) -> UCommand {
@ -319,7 +360,7 @@ impl UCommand {
pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> Box<&mut UCommand> { pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> Box<&mut UCommand> {
if self.has_run { if self.has_run {
panic!(ALREADY_RUN); panic!(MULTIPLE_STDIN_MEANINGLESS);
} }
for s in args { for s in args {
self.comm_string.push_str(" "); self.comm_string.push_str(" ");
@ -339,37 +380,59 @@ impl UCommand {
} }
pub fn run(&mut self) -> CmdResult { pub fn run(&mut self) -> CmdResult {
if self.has_run {
panic!(ALREADY_RUN);
}
self.has_run = true; self.has_run = true;
log_info("run", &self.comm_string); log_info("run", &self.comm_string);
let prog = self.raw.output().unwrap(); let prog = match self.stdin {
Some(ref input) => {
let mut result = self.raw
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
result.stdin
.take()
.unwrap_or_else(
|| panic!(
"Could not take child process stdin"))
.write_all(&input)
.unwrap_or_else(|e| panic!("{}", e));
result.wait_with_output().unwrap()
}
None => {
self.raw.output().unwrap()
}
};
CmdResult { CmdResult {
success: prog.status.success(), success: prog.status.success(),
stdout: from_utf8(&prog.stdout).unwrap().to_string(), stdout: from_utf8(&prog.stdout).unwrap().to_string(),
stderr: from_utf8(&prog.stderr).unwrap().to_string(), stderr: from_utf8(&prog.stderr).unwrap().to_string(),
} }
} }
pub fn run_piped_stdin<T: AsRef<[u8]>>(&mut self, input: T) -> CmdResult { pub fn pipe_in<T: Into<Vec<u8>>>(&mut self, input: T) -> Box<&mut UCommand> {
self.has_run = true; if self.stdin.is_some() {
log_info("run_piped_stdin", &self.comm_string); panic!(MULTIPLE_STDIN_MEANINGLESS);
let mut result = self.raw
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
result.stdin
.take()
.unwrap_or_else(|| panic!("Could not take child process stdin"))
.write_all(input.as_ref())
.unwrap_or_else(|e| panic!("{}", e));
let prog = result.wait_with_output().unwrap();
CmdResult {
success: prog.status.success(),
stdout: from_utf8(&prog.stdout).unwrap().to_string(),
stderr: from_utf8(&prog.stderr).unwrap().to_string(),
} }
self.stdin = Some(input.into());
Box::new(self)
}
pub fn run_piped_stdin<T: Into<Vec<u8>>>(&mut self, input: T) -> CmdResult {
self.pipe_in(input).run()
}
pub fn succeeds(&mut self) -> CmdResult {
let cmd_result = self.run();
cmd_result.success();
cmd_result
}
pub fn fails(&mut self) -> CmdResult {
let cmd_result = self.run();
cmd_result.failure();
cmd_result
} }
} }

View file

@ -126,7 +126,7 @@ fn test_symlink_interactive() {
let result1 = ts.util_cmd() let result1 = ts.util_cmd()
.args(&["-i", "-s", file, link]) .args(&["-i", "-s", file, link])
.run_piped_stdin(b"n"); .run_piped_stdin("n");
assert_empty_stderr!(result1); assert_empty_stderr!(result1);
assert!(result1.success); assert!(result1.success);
@ -136,7 +136,7 @@ fn test_symlink_interactive() {
let result2 = ts.util_cmd() let result2 = ts.util_cmd()
.args(&["-i", "-s", file, link]) .args(&["-i", "-s", file, link])
.run_piped_stdin(b"Yesh"); .run_piped_stdin("Yesh");
assert_empty_stderr!(result2); assert_empty_stderr!(result2);
assert!(result2.success); assert!(result2.success);

View file

@ -131,7 +131,7 @@ fn test_mv_interactive() {
at.touch(file_b); at.touch(file_b);
let result1 = ts.util_cmd().arg("-i").arg(file_a).arg(file_b).run_piped_stdin(b"n"); let result1 = ts.util_cmd().arg("-i").arg(file_a).arg(file_b).run_piped_stdin("n");
assert_empty_stderr!(result1); assert_empty_stderr!(result1);
assert!(result1.success); assert!(result1.success);
@ -140,7 +140,7 @@ fn test_mv_interactive() {
assert!(at.file_exists(file_b)); assert!(at.file_exists(file_b));
let result2 = ts.util_cmd().arg("-i").arg(file_a).arg(file_b).run_piped_stdin(b"Yesh"); let result2 = ts.util_cmd().arg("-i").arg(file_a).arg(file_b).run_piped_stdin("Yesh");
assert_empty_stderr!(result2); assert_empty_stderr!(result2);
assert!(result2.success); assert!(result2.success);

View file

@ -51,7 +51,7 @@ fn test_rm_interactive() {
.arg("-i") .arg("-i")
.arg(file_a) .arg(file_a)
.arg(file_b) .arg(file_b)
.run_piped_stdin(b"n"); .run_piped_stdin("n");
assert!(result1.success); assert!(result1.success);
@ -62,7 +62,7 @@ fn test_rm_interactive() {
.arg("-i") .arg("-i")
.arg(file_a) .arg(file_a)
.arg(file_b) .arg(file_b)
.run_piped_stdin(b"Yesh"); .run_piped_stdin("Yesh");
assert!(result2.success); assert!(result2.success);

View file

@ -10,14 +10,14 @@ static UTIL_NAME: &'static str = "tr";
#[test] #[test]
fn test_toupper() { fn test_toupper() {
let (_, mut ucmd) = testing(UTIL_NAME); let (_, mut ucmd) = testing(UTIL_NAME);
let result = ucmd.args(&["a-z", "A-Z"]).run_piped_stdin(b"!abcd!"); let result = ucmd.args(&["a-z", "A-Z"]).run_piped_stdin("!abcd!");
assert_eq!(result.stdout, "!ABCD!"); assert_eq!(result.stdout, "!ABCD!");
} }
#[test] #[test]
fn test_small_set2() { fn test_small_set2() {
let (_, mut ucmd) = testing(UTIL_NAME); let (_, mut ucmd) = testing(UTIL_NAME);
let result = ucmd.args(&["0-9", "X"]).run_piped_stdin(b"@0123456789"); let result = ucmd.args(&["0-9", "X"]).run_piped_stdin("@0123456789");
assert_eq!(result.stdout, "@XXXXXXXXXX"); assert_eq!(result.stdout, "@XXXXXXXXXX");
} }
@ -32,13 +32,13 @@ fn test_unicode() {
#[test] #[test]
fn test_delete() { fn test_delete() {
let (_, mut ucmd) = testing(UTIL_NAME); let (_, mut ucmd) = testing(UTIL_NAME);
let result = ucmd.args(&["-d", "a-z"]).run_piped_stdin(b"aBcD"); let result = ucmd.args(&["-d", "a-z"]).run_piped_stdin("aBcD");
assert_eq!(result.stdout, "BD"); assert_eq!(result.stdout, "BD");
} }
#[test] #[test]
fn test_delete_complement() { fn test_delete_complement() {
let (_, mut ucmd) = testing(UTIL_NAME); let (_, mut ucmd) = testing(UTIL_NAME);
let result = ucmd.args(&["-d", "-c", "a-z"]).run_piped_stdin(b"aBcD"); let result = ucmd.args(&["-d", "-c", "a-z"]).run_piped_stdin("aBcD");
assert_eq!(result.stdout, "ac"); assert_eq!(result.stdout, "ac");
} }