From b334fa49b2d84905197533354afb302a61f3499b Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 14 Jan 2020 00:52:27 -0800 Subject: [PATCH] Redirect stdin and stdout and capture for tests type: testing --- Cargo.lock | 42 +++++++++++++++++++++++++--------------- src/assert_matches.rs | 12 ++++++++++++ src/capture.rs | 30 +++++++++++++++++++++++++++++ src/common.rs | 14 ++++++++++++++ src/consts.rs | 10 ++++++++++ src/env.rs | 42 ++++++++++++++++++++++++++++++++++++---- src/error.rs | 4 +++- src/main.rs | 13 ++++++++++++- src/opt.rs | 9 +++++++++ src/test_env.rs | 45 +++++++++++++++++++++++++++++++++++++++++++ src/testing.rs | 10 ++-------- src/torrent/create.rs | 6 ++++-- 12 files changed, 205 insertions(+), 32 deletions(-) create mode 100644 src/assert_matches.rs create mode 100644 src/capture.rs create mode 100644 src/test_env.rs diff --git a/Cargo.lock b/Cargo.lock index 75d699a..f9b961b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,9 +18,10 @@ dependencies = [ [[package]] name = "atty" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -49,7 +50,7 @@ version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -64,12 +65,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "getrandom" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -80,6 +81,14 @@ dependencies = [ "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hermit-abi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "idna" version = "0.2.0" @@ -101,7 +110,7 @@ dependencies = [ "serde_bencode 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_bytes 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "snafu 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -189,7 +198,7 @@ name = "rand" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -210,7 +219,7 @@ name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -323,16 +332,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "snafu" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "snafu-derive 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "snafu-derive 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "snafu-derive" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -478,7 +487,7 @@ dependencies = [ [[package]] name = "wasi" -version = "0.7.0" +version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -511,14 +520,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" +"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" -"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" +"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" @@ -548,8 +558,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" "checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" -"checksum snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41207ca11f96a62cd34e6b7fdf73d322b25ae3848eb9d38302169724bb32cf27" -"checksum snafu-derive 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5e338c8b0577457c9dda8e794b6ad7231c96e25b1b0dd5842d52249020c1c0" +"checksum snafu 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "65929384f863545b67a696ce36499cd045e5dca834aca85c929401d99a920bad" +"checksum snafu-derive 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d2b1d1afd31abf5e72cc8d21e51443e3eb6b24dcda99ad3c048df5524eae80" "checksum static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum structopt 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "884ae79d6aad1e738f4a70dff314203fd498490a63ebc4d03ea83323c40b7b72" @@ -567,7 +577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" -"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" +"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" diff --git a/src/assert_matches.rs b/src/assert_matches.rs new file mode 100644 index 0000000..b9822e9 --- /dev/null +++ b/src/assert_matches.rs @@ -0,0 +1,12 @@ +macro_rules! assert_matches { + ($expression:expr, $( $pattern:pat )|+ $( if $guard:expr )?) => { + match $expression { + $( $pattern )|+ $( if $guard )? => {} + left => panic!( + "assertion failed: (left ~= right)\n left: `{:?}`\n right: `{}`", + left, + stringify!($($pattern)|+ $(if $guard)?) + ), + } + } +} diff --git a/src/capture.rs b/src/capture.rs new file mode 100644 index 0000000..88661fd --- /dev/null +++ b/src/capture.rs @@ -0,0 +1,30 @@ +use crate::common::*; + +#[derive(Clone)] +pub(crate) struct Capture { + cursor: Rc>>>, +} + +impl Capture { + pub(crate) fn new() -> Self { + Self { + cursor: Rc::new(RefCell::new(Cursor::new(Vec::new()))), + } + } + + pub(crate) fn string(&self) -> String { + str::from_utf8(&self.cursor.borrow().get_ref()) + .unwrap() + .to_owned() + } +} + +impl Write for Capture { + fn write(&mut self, buffer: &[u8]) -> std::result::Result { + self.cursor.borrow_mut().write(buffer) + } + + fn flush(&mut self) -> std::result::Result<(), std::io::Error> { + self.cursor.borrow_mut().flush() + } +} diff --git a/src/common.rs b/src/common.rs index 44588e2..3e0482b 100644 --- a/src/common.rs +++ b/src/common.rs @@ -44,3 +44,17 @@ pub(crate) use crate::{ // test modules #[cfg(test)] pub(crate) use crate::testing; + +// test stdlib types +#[cfg(test)] +pub(crate) use std::{ + cell::RefCell, + io::Cursor, + iter, + ops::{Deref, DerefMut}, + rc::Rc, +}; + +// test structs and enums +#[cfg(test)] +pub(crate) use crate::{capture::Capture, test_env::TestEnv}; diff --git a/src/consts.rs b/src/consts.rs index eb9eba8..63d520f 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -11,3 +11,13 @@ pub(crate) const CREATED_BY_DEFAULT: &str = concat!( /// Value for `encoding` torrent metainfo field. pub(crate) const ENCODING_UTF8: &str = "UTF-8"; + +pub(crate) const ABOUT: &str = concat!( + env!("CARGO_PKG_DESCRIPTION"), + " - ", + env!("CARGO_PKG_HOMEPAGE") +); + +pub(crate) const VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); + +pub(crate) const AUTHOR: &str = env!("CARGO_PKG_AUTHORS"); diff --git a/src/env.rs b/src/env.rs index e2a0e26..32dc0b4 100644 --- a/src/env.rs +++ b/src/env.rs @@ -4,6 +4,7 @@ pub(crate) struct Env { args: Vec, dir: Box>, pub(crate) err: Box, + pub(crate) _out: Box, } impl Env { @@ -13,16 +14,17 @@ impl Env { Err(error) => panic!("Failed to get current directory: {}", error), }; - Self::new(dir, io::stderr(), env::args()) + Self::new(dir, io::stdout(), io::stderr(), env::args()) } pub(crate) fn run(&mut self) -> Result<(), Error> { Opt::from_iter_safe(&self.args)?.run(self) } - pub(crate) fn new(dir: D, err: E, args: I) -> Self + pub(crate) fn new(dir: D, out: O, err: E, args: I) -> Self where D: AsRef + 'static, + O: Write + 'static, E: Write + 'static, S: Into, I: IntoIterator, @@ -31,13 +33,23 @@ impl Env { args: args.into_iter().map(Into::into).collect(), dir: Box::new(dir), err: Box::new(err), + _out: Box::new(out), } } pub(crate) fn status(&mut self) -> Result<(), i32> { + use structopt::clap::ErrorKind; if let Err(error) = self.run() { - write!(&mut self.err, "error: {}", error).ok(); - Err(EXIT_FAILURE) + if let Error::Clap { source } = error { + write!(&mut self.err, "{}", source).ok(); + match source.kind { + ErrorKind::VersionDisplayed | ErrorKind::HelpDisplayed => Ok(()), + _ => Err(EXIT_FAILURE), + } + } else { + write!(&mut self.err, "error: {}", error).ok(); + Err(EXIT_FAILURE) + } } else { Ok(()) } @@ -47,3 +59,25 @@ impl Env { self.dir.as_ref().as_ref().join(path).clean() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn error_message_on_stdout() { + let mut env = testing::env( + ["torrent", "create", "--input", "foo", "--announce", "bar"] + .iter() + .cloned(), + ); + fs::write(env.resolve("foo"), "").unwrap(); + env.status().ok(); + let err = env.err(); + if !err.starts_with("error: Failed to parse announce URL:") { + panic!("Unexpected standard error output: {}", err); + } + + assert_eq!(env.out(), ""); + } +} diff --git a/src/error.rs b/src/error.rs index 9ab639e..696f838 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,8 +9,10 @@ pub(crate) enum Error { Clap { source: clap::Error }, #[snafu(display("I/O error at `{}`: {}", path.display(), source))] Filesystem { source: io::Error, path: PathBuf }, - #[snafu(display("Failed to write to standard error stream: {}", source))] + #[snafu(display("Failed to write to standard error: {}", source))] Stderr { source: io::Error }, + #[snafu(display("Failed to write to standard output: {}", source))] + Stdout { source: io::Error }, #[snafu(display("Serialization failed: {}", source))] Serialize { source: serde_bencode::Error }, #[snafu(display("Filename was not valid unicode: {}", filename.to_string_lossy()))] diff --git a/src/main.rs b/src/main.rs index 1b16b39..f68adf9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,8 @@ clippy::option_map_unwrap_or_else, clippy::option_unwrap_used, clippy::result_expect_used, - clippy::result_unwrap_used + clippy::result_unwrap_used, + clippy::wildcard_enum_match_arm )] use crate::common::*; @@ -18,6 +19,10 @@ use crate::common::*; #[macro_use] mod matches; +#[cfg(test)] +#[macro_use] +mod assert_matches; + #[macro_use] mod errln; @@ -27,6 +32,12 @@ mod err; #[cfg(test)] mod testing; +#[cfg(test)] +mod test_env; + +#[cfg(test)] +mod capture; + mod bencode; mod common; mod consts; diff --git a/src/opt.rs b/src/opt.rs index 98a1938..5ba080d 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -1,6 +1,15 @@ use crate::common::*; +use structopt::clap::AppSettings; + #[derive(StructOpt)] +#[structopt( + about(consts::ABOUT), + version(consts::VERSION), + author(consts::AUTHOR), + setting(AppSettings::ColoredHelp), + setting(AppSettings::ColorAuto) +)] pub(crate) struct Opt { #[structopt(long = "unstable", short = "u")] unstable: bool, diff --git a/src/test_env.rs b/src/test_env.rs new file mode 100644 index 0000000..2cd3ac2 --- /dev/null +++ b/src/test_env.rs @@ -0,0 +1,45 @@ +use crate::common::*; + +pub(crate) struct TestEnv { + env: Env, + err: Capture, + out: Capture, +} + +impl TestEnv { + pub(crate) fn new(iter: impl IntoIterator>) -> Self { + let err = Capture::new(); + let out = Capture::new(); + + let env = Env::new( + tempfile::tempdir().unwrap(), + out.clone(), + err.clone(), + iter::once(String::from("imdl")).chain(iter.into_iter().map(|item| item.into())), + ); + + Self { err, env, out } + } + + pub(crate) fn err(&self) -> String { + self.err.string() + } + + pub(crate) fn out(&self) -> String { + self.out.string() + } +} + +impl Deref for TestEnv { + type Target = Env; + + fn deref(&self) -> &Self::Target { + &self.env + } +} + +impl DerefMut for TestEnv { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.env + } +} diff --git a/src/testing.rs b/src/testing.rs index dee91f2..05ebaec 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,11 +1,5 @@ use crate::common::*; -use std::{io::Cursor, iter}; - -pub(crate) fn env(iter: impl IntoIterator>) -> Env { - Env::new( - tempfile::tempdir().unwrap(), - Cursor::new(Vec::new()), - iter::once(String::from("imdl")).chain(iter.into_iter().map(|item| item.into())), - ) +pub(crate) fn env(iter: impl IntoIterator>) -> TestEnv { + TestEnv::new(iter) } diff --git a/src/torrent/create.rs b/src/torrent/create.rs index b7b6df7..56dd809 100644 --- a/src/torrent/create.rs +++ b/src/torrent/create.rs @@ -125,7 +125,9 @@ impl Create { mod tests { use super::*; - fn environment(args: &[&str]) -> Env { + use crate::test_env::TestEnv; + + fn environment(args: &[&str]) -> TestEnv { testing::env(["torrent", "create"].iter().chain(args).cloned()) } @@ -178,7 +180,7 @@ mod tests { fn tracker_flag_must_be_url() { let mut env = environment(&["--input", "foo", "--announce", "bar"]); fs::write(env.resolve("foo"), "").unwrap(); - assert!(matches!(env.run(), Err(Error::AnnounceUrlParse { .. }))); + assert_matches!(env.run(), Err(Error::AnnounceUrlParse { .. })); } #[test]