Redirect stdin and stdout and capture for tests

type: testing
This commit is contained in:
Casey Rodarmor 2020-01-14 00:52:27 -08:00
parent fd06943726
commit b334fa49b2
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
12 changed files with 205 additions and 32 deletions

42
Cargo.lock generated
View File

@ -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"

12
src/assert_matches.rs Normal file
View File

@ -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)?)
),
}
}
}

30
src/capture.rs Normal file
View File

@ -0,0 +1,30 @@
use crate::common::*;
#[derive(Clone)]
pub(crate) struct Capture {
cursor: Rc<RefCell<Cursor<Vec<u8>>>>,
}
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<usize, std::io::Error> {
self.cursor.borrow_mut().write(buffer)
}
fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
self.cursor.borrow_mut().flush()
}
}

View File

@ -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};

View File

@ -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");

View File

@ -4,6 +4,7 @@ pub(crate) struct Env {
args: Vec<String>,
dir: Box<dyn AsRef<Path>>,
pub(crate) err: Box<dyn Write>,
pub(crate) _out: Box<dyn Write>,
}
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<D, E, S, I>(dir: D, err: E, args: I) -> Self
pub(crate) fn new<D, O, E, S, I>(dir: D, out: O, err: E, args: I) -> Self
where
D: AsRef<Path> + 'static,
O: Write + 'static,
E: Write + 'static,
S: Into<String>,
I: IntoIterator<Item = S>,
@ -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() {
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(), "");
}
}

View File

@ -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()))]

View File

@ -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;

View File

@ -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,

45
src/test_env.rs Normal file
View File

@ -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<Item = impl Into<String>>) -> 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
}
}

View File

@ -1,11 +1,5 @@
use crate::common::*;
use std::{io::Cursor, iter};
pub(crate) fn env(iter: impl IntoIterator<Item = impl Into<String>>) -> 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<Item = impl Into<String>>) -> TestEnv {
TestEnv::new(iter)
}

View File

@ -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]