Skip to content

Commit

Permalink
Redirect stdin and stdout and capture for tests
Browse files Browse the repository at this point in the history
type: testing
  • Loading branch information
casey committed Apr 8, 2020
1 parent fd06943 commit b334fa4
Show file tree
Hide file tree
Showing 12 changed files with 205 additions and 32 deletions.
42 changes: 26 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/assert_matches.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 30 additions & 0 deletions src/capture.rs
Original file line number Diff line number Diff line change
@@ -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()
}
}
14 changes: 14 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
10 changes: 10 additions & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
42 changes: 38 additions & 4 deletions src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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>,
Expand All @@ -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(())
}
Expand All @@ -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(), "");
}
}
4 changes: 3 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))]
Expand Down
13 changes: 12 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -18,6 +19,10 @@ use crate::common::*;
#[macro_use]
mod matches;

#[cfg(test)]
#[macro_use]
mod assert_matches;

#[macro_use]
mod errln;

Expand All @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/opt.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Loading

0 comments on commit b334fa4

Please sign in to comment.