Test imdl torrent verify output

- Test all individual `FileError` variants
- Test terminal colors
- Test multiple and single file torrents

type: testing
This commit is contained in:
Casey Rodarmor 2020-03-15 22:41:47 -07:00
parent 2ea5e0b384
commit 0033e8381f
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
12 changed files with 355 additions and 34 deletions

41
Cargo.lock generated
View File

@ -316,6 +316,7 @@ dependencies = [
"console",
"globset",
"indicatif",
"indoc",
"lazy_static",
"libc",
"md5",
@ -349,6 +350,29 @@ dependencies = [
"regex",
]
[[package]]
name = "indoc"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9553c1e16c114b8b77ebeb329e5f2876eed62a8d51178c8bc6bff0d65f98f8"
dependencies = [
"indoc-impl",
"proc-macro-hack",
]
[[package]]
name = "indoc-impl"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b714fc08d0961716390977cdff1536234415ac37b509e34e5a983def8340fb75"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
"unindent",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@ -504,6 +528,17 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.9"
@ -908,6 +943,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "unindent"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993"
[[package]]
name = "update-readme"
version = "0.0.0"

View File

@ -54,6 +54,7 @@ version = "0.3.0"
features = ["default", "wrap_help"]
[dev-dependencies]
indoc = "0.3.4"
temptree = "0.0.0"
[workspace]

View File

@ -78,8 +78,6 @@ impl Env {
)
.ok();
error.print_body(self).ok();
if let Some(lint) = error.lint() {
writeln!(
&mut self.err,

View File

@ -120,7 +120,7 @@ pub(crate) enum Error {
))]
Unstable { feature: &'static str },
#[snafu(display("Torrent verification failed."))]
Verify { status: Status },
Verify,
}
impl Error {
@ -137,14 +137,6 @@ impl Error {
message: message.into(),
}
}
pub(crate) fn print_body(&self, env: &mut Env) -> Result<()> {
if let Self::Verify { status } = self {
status.print_body(env)?;
}
Ok(())
}
}
impl From<clap::Error> for Error {

View File

@ -73,8 +73,8 @@ impl Display for FileError {
Self::Io(io_error) => write!(f, "{}", io_error),
Self::Missing => write!(f, "File missing"),
Self::Directory => write!(f, "Expected file but found directory"),
Self::Surfeit(difference) => write!(f, "Extra bytes: {}", difference),
Self::Dearth(difference) => write!(f, "Missing bytes: {}", difference),
Self::Surfeit(difference) => write!(f, "{} too long", difference),
Self::Dearth(difference) => write!(f, "{} too short", difference),
Self::Md5 { actual, expected } => write!(
f,
"MD5 checksum mismatch: {} (expected {})",

View File

@ -49,6 +49,10 @@ impl OutputStream {
self.style
}
pub(crate) fn is_styled_term(&self) -> bool {
self.is_styled() && self.is_term()
}
pub(crate) fn style(&self) -> Style {
Style::from_active(self.style)
}

View File

@ -49,7 +49,7 @@ impl Status {
}
}
pub(crate) fn print_body(&self, env: &mut Env) -> Result<()> {
pub(crate) fn print(&self, env: &mut Env) -> Result<()> {
match self {
Self::Single { error, .. } => {
if let Some(error) = error {
@ -70,13 +70,6 @@ impl Status {
error.println(env.err_mut()).context(error::Stderr)?;
}
}
errln!(
env,
"{}/{} files corrupted.",
files.iter().filter(|file| file.is_bad()).count(),
files.len(),
)?;
}
}

View File

@ -15,7 +15,7 @@ pub(crate) trait Step {
dim.suffix()
)?;
err!(env, "{}{} ", message.prefix(), self.symbol())?;
err!(env, "{} {}", self.symbol(), message.prefix())?;
self.write_message(env.err_mut()).context(error::Stderr)?;

View File

@ -208,7 +208,7 @@ impl Create {
CreateStep::Searching.print(env)?;
let spinner = if env.err().is_styled() {
let spinner = if env.err().is_styled_term() {
let style = ProgressStyle::default_spinner()
.template("{spinner:.green} {msg:.bold}…")
.tick_chars(consts::TICK_CHARS);
@ -300,7 +300,7 @@ impl Create {
CreateStep::Hashing.print(env)?;
let progress_bar = if env.err().is_styled() {
let progress_bar = if env.err().is_styled_term() {
let style = ProgressStyle::default_bar()
.template(
"{spinner:.green} ⟪{elapsed_precise}⟫ ⟦{bar:40.cyan}⟧ \
@ -380,7 +380,7 @@ impl Create {
let status = metainfo.verify(&input, None)?;
if !status.good() {
return Err(Error::Verify { status });
return Err(Error::Verify);
}
}

View File

@ -45,7 +45,7 @@ impl Verify {
metainfo_path.parent().unwrap().join(&metainfo.info.name)
};
let progress_bar = if env.err().is_styled() {
let progress_bar = if env.err().is_styled_term() {
let style = ProgressStyle::default_bar()
.template(
"{spinner:.green} ⟪{elapsed_precise}⟫ ⟦{bar:40.cyan}⟧ \
@ -63,6 +63,8 @@ impl Verify {
let status = metainfo.verify(&base, progress_bar)?;
status.print(env)?;
if status.good() {
errln!(
env,
@ -70,7 +72,7 @@ impl Verify {
)?;
Ok(())
} else {
Err(Error::Verify { status })
Err(Error::Verify)
}
}
}
@ -79,6 +81,8 @@ impl Verify {
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn require_metainfo_argument() {
let mut env = test_env! {
@ -173,13 +177,22 @@ mod tests {
tree: {},
};
assert_matches!(verify_env.run(), Err(Error::Verify { .. }));
assert_matches!(verify_env.status(), Err(EXIT_FAILURE));
let want = format!(
"[1/2] \u{1F4BE} Loading metainfo from `{}`…\n[2/2] \u{1F9EE} Verifying pieces from `{}`…\n",
torrent.display(),
let want = [
&format!(
"[1/2] \u{1F4BE} Loading metainfo from `{}`…",
torrent.display()
),
&format!(
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
create_env.resolve("foo").display()
);
),
"Pieces corrupted.",
"error: Torrent verification failed.",
"",
]
.join("\n");
assert_eq!(verify_env.err(), want);
assert_eq!(verify_env.out(), "");
@ -243,4 +256,258 @@ mod tests {
Ok(())
}
#[test]
fn output_multiple() -> Result<()> {
let mut create_env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
"--announce",
"https://bar",
"--md5",
],
tree: {
foo: {
a: "abc",
d: "efg",
h: "ijk",
l: "mno",
p: "qrs",
t: "uvw",
},
},
};
create_env.run()?;
let torrent = create_env.resolve("foo.torrent");
create_env.write("foo/a", "xyz");
create_env.write("foo/d", "efgg");
create_env.write("foo/h", "ik");
create_env.remove_file("foo/l");
create_env.remove_file("foo/p");
create_env.create_dir("foo/p");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let metadata = create_env.metadata("foo/t");
let mut permissions = metadata.permissions();
permissions.set_mode(0);
create_env.set_permissions("foo/t", permissions);
}
let mut verify_env = test_env! {
args: [
"torrent",
"verify",
"--input",
&torrent,
],
tree: {},
};
assert_matches!(verify_env.status(), Err(EXIT_FAILURE));
let want = [
&format!(
"[1/2] \u{1F4BE} Loading metainfo from `{}`…",
torrent.display()
),
&format!(
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
create_env.resolve("foo").display()
),
"a: MD5 checksum mismatch: d16fb36f911f878998c136191af705e (expected \
90150983cd24fb0d6963f7d28e17f72)",
"d: 1 byte too long",
"h: 1 byte too short",
"l: File missing",
"p: Expected file but found directory",
#[cfg(unix)]
"t: Permission denied (os error 13)",
"Pieces corrupted.",
"error: Torrent verification failed.",
"",
]
.join("\n");
assert_eq!(verify_env.err(), want);
assert_eq!(verify_env.out(), "");
Ok(())
}
#[test]
fn output_color() -> Result<()> {
let mut create_env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
"--announce",
"https://bar",
"--md5",
],
tree: {
foo: {
a: "abc",
d: "efg",
h: "ijk",
l: "mno",
p: "qrs",
t: "uvw",
},
},
};
create_env.run()?;
let torrent = create_env.resolve("foo.torrent");
create_env.write("foo/a", "xyz");
create_env.write("foo/d", "efgg");
create_env.write("foo/h", "ik");
create_env.remove_file("foo/l");
create_env.remove_file("foo/p");
create_env.create_dir("foo/p");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let metadata = create_env.metadata("foo/t");
let mut permissions = metadata.permissions();
permissions.set_mode(0);
create_env.set_permissions("foo/t", permissions);
}
let mut verify_env = test_env! {
args: [
"torrent",
"verify",
"--input",
&torrent,
],
err_style: true,
tree: {},
};
assert_matches!(verify_env.status(), Err(EXIT_FAILURE));
let style = Style::active();
fn error(path: &str, message: &str) -> String {
let style = Style::active();
format!(
"{}{}:{} {}",
style.message().prefix(),
path,
style.message().suffix(),
style.error().paint(message)
)
}
let want = [
&format!(
"{} \u{1F4BE} {}",
style.dim().paint("[1/2]"),
style
.message()
.paint(format!("Loading metainfo from `{}`…", torrent.display()))
),
&format!(
"{} \u{1F9EE} {}",
style.dim().paint("[2/2]"),
style.message().paint(format!(
"Verifying pieces from `{}`…",
create_env.resolve("foo").display()
))
),
&error(
"a",
"MD5 checksum mismatch: d16fb36f911f878998c136191af705e (expected \
90150983cd24fb0d6963f7d28e17f72)",
),
&error("d", "1 byte too long"),
&error("h", "1 byte too short"),
&error("l", "File missing"),
&error("p", "Expected file but found directory"),
#[cfg(unix)]
&error("t", "Permission denied (os error 13)"),
"Pieces corrupted.",
&format!(
"{}{}",
style.error().paint("error"),
style.message().paint(": Torrent verification failed.")
),
"",
]
.join("\n");
assert_eq!(verify_env.err(), want);
assert_eq!(verify_env.out(), "");
Ok(())
}
#[test]
fn output_single() -> Result<()> {
let mut create_env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
"--announce",
"https://bar",
],
tree: {
foo: "abc",
},
};
create_env.run()?;
let torrent = create_env.resolve("foo.torrent");
create_env.write("foo", "abcxyz");
let mut verify_env = test_env! {
args: [
"torrent",
"verify",
"--input",
&torrent,
],
tree: {},
};
assert_matches!(verify_env.status(), Err(EXIT_FAILURE));
let want = [
&format!(
"[1/2] \u{1F4BE} Loading metainfo from `{}`…",
torrent.display()
),
&format!(
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
create_env.resolve("foo").display()
),
"3 bytes too long",
"Pieces corrupted.",
"error: Torrent verification failed.",
"",
]
.join("\n");
assert_eq!(verify_env.err(), want);
assert_eq!(verify_env.out(), "");
Ok(())
}
}

View File

@ -4,6 +4,7 @@ macro_rules! test_env {
{
args: [$($arg:expr),* $(,)?],
$(cwd: $cwd:expr,)?
$(err_style: $err_style:expr,)?
tree: {
$($tree:tt)*
} $(,)?
@ -13,6 +14,7 @@ macro_rules! test_env {
TestEnvBuilder::new()
$(.current_dir(tempdir.path().join($cwd)))?
$(.err_style($err_style))?
.tempdir(tempdir)
.arg("imdl")
$(.arg($arg))*
@ -55,6 +57,22 @@ impl TestEnv {
fs::write(self.env.resolve(path), bytes.as_ref()).unwrap();
}
pub(crate) fn remove_file(&self, path: impl AsRef<Path>) {
fs::remove_file(self.env.resolve(path)).unwrap();
}
pub(crate) fn create_dir(&self, path: impl AsRef<Path>) {
fs::create_dir(self.env.resolve(path)).unwrap();
}
pub(crate) fn metadata(&self, path: impl AsRef<Path>) -> fs::Metadata {
fs::metadata(self.env.resolve(path)).unwrap()
}
pub(crate) fn set_permissions(&self, path: impl AsRef<Path>, permissions: fs::Permissions) {
fs::set_permissions(self.env.resolve(path), permissions).unwrap();
}
pub(crate) fn load_metainfo(&self, filename: impl AsRef<Path>) -> Metainfo {
Metainfo::load(self.env.resolve(filename.as_ref())).unwrap()
}

View File

@ -6,6 +6,7 @@ pub(crate) struct TestEnvBuilder {
out_is_term: bool,
tempdir: Option<TempDir>,
use_color: bool,
err_style: bool,
}
impl TestEnvBuilder {
@ -16,6 +17,7 @@ impl TestEnvBuilder {
out_is_term: false,
tempdir: None,
use_color: false,
err_style: false,
}
}
@ -24,6 +26,11 @@ impl TestEnvBuilder {
self
}
pub(crate) fn err_style(mut self, err_style: bool) -> Self {
self.err_style = err_style;
self
}
pub(crate) fn arg(mut self, arg: impl Into<OsString>) -> Self {
self.args.push(arg.into());
self
@ -64,7 +71,7 @@ impl TestEnvBuilder {
self.out_is_term,
);
let err_stream = OutputStream::new(Box::new(err.clone()), false, false);
let err_stream = OutputStream::new(Box::new(err.clone()), self.err_style, false);
let env = Env::new(current_dir, self.args, out_stream, err_stream);