diff --git a/src/bytes.rs b/src/bytes.rs index ef6b3f9c..aa0f5d11 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -1,5 +1,53 @@ use crate::common::*; +impl Div for Bytes { + type Output = u64; + + fn div(self, rhs: Bytes) -> u64 { + self.0 / rhs.0 + } +} + +impl Div for Bytes { + type Output = Bytes; + + fn div(self, rhs: u64) -> Bytes { + Bytes::from(self.0 / rhs) + } +} + +impl Mul for Bytes { + type Output = Bytes; + + fn mul(self, rhs: u64) -> Self { + Bytes::from(self.0 * rhs) + } +} + +impl DivAssign for Bytes { + fn div_assign(&mut self, rhs: u64) { + self.0 /= rhs; + } +} + +impl MulAssign for Bytes { + fn mul_assign(&mut self, rhs: u64) { + self.0 *= rhs; + } +} + +impl AddAssign for Bytes { + fn add_assign(&mut self, rhs: Bytes) { + self.0 += rhs.0; + } +} + +impl SubAssign for Bytes { + fn sub_assign(&mut self, rhs: u64) { + self.0 -= rhs; + } +} + const KI: u64 = 1 << 10; const MI: u64 = KI << 10; const GI: u64 = MI << 10; @@ -52,54 +100,6 @@ impl> From for Bytes { } } -impl Div for Bytes { - type Output = u64; - - fn div(self, rhs: Bytes) -> u64 { - self.0 / rhs.0 - } -} - -impl Div for Bytes { - type Output = Bytes; - - fn div(self, rhs: u64) -> Bytes { - Bytes::from(self.0 / rhs) - } -} - -impl DivAssign for Bytes { - fn div_assign(&mut self, rhs: u64) { - self.0 /= rhs; - } -} - -impl Mul for Bytes { - type Output = Bytes; - - fn mul(self, rhs: u64) -> Self { - Bytes::from(self.0 * rhs) - } -} - -impl MulAssign for Bytes { - fn mul_assign(&mut self, rhs: u64) { - self.0 *= rhs; - } -} - -impl AddAssign for Bytes { - fn add_assign(&mut self, rhs: Bytes) { - self.0 += rhs.0; - } -} - -impl SubAssign for Bytes { - fn sub_assign(&mut self, rhs: u64) { - self.0 -= rhs; - } -} - impl FromStr for Bytes { type Err = Error; diff --git a/src/common.rs b/src/common.rs index e568b7fb..dbc049aa 100644 --- a/src/common.rs +++ b/src/common.rs @@ -77,9 +77,6 @@ mod test { pub(crate) use tempfile::TempDir; pub(crate) use temptree::temptree; - // test modules - pub(crate) use crate::testing; - // test structs and enums pub(crate) use crate::{capture::Capture, test_env::TestEnv, test_env_builder::TestEnvBuilder}; } diff --git a/src/env.rs b/src/env.rs index 666e500a..cb843aa8 100644 --- a/src/env.rs +++ b/src/env.rs @@ -1,7 +1,7 @@ use crate::common::*; pub(crate) struct Env { - args: Vec, + args: Vec, dir: Box>, pub(crate) err: Box, pub(crate) out: Box, @@ -76,7 +76,7 @@ impl Env { D: AsRef + 'static, O: Write + 'static, E: Write + 'static, - S: Into, + S: Into, I: IntoIterator, { Self { @@ -155,8 +155,8 @@ mod tests { #[test] fn error_message_on_stdout() { - let mut env = testing::env( - [ + let mut env = test_env! { + args: [ "torrent", "create", "--input", @@ -165,11 +165,11 @@ mod tests { "udp:bar.com", "--announce-tier", "foo", - ] - .iter() - .cloned(), - ); - fs::write(env.resolve("foo"), "").unwrap(); + ], + tree: { + foo: "", + } + }; env.status().ok(); let err = env.err(); if !err.starts_with("error: Failed to parse announce URL:") { diff --git a/src/file_status.rs b/src/file_status.rs index c2e2cba2..17d04711 100644 --- a/src/file_status.rs +++ b/src/file_status.rs @@ -63,36 +63,6 @@ impl FileStatus { Ok(()) } - pub(crate) fn icon(&self) -> char { - if self.error.is_some() { - return '!'; - } - - if !self.present { - return '?'; - } - - if !self.file { - return '¿'; - } - - if !self.md5() { - return 'x'; - } - - let length = self.length_actual.unwrap(); - - if length > self.length_expected { - return '+'; - } - - if length < self.length_expected { - return '-'; - } - - '♡' - } - fn md5(&self) -> bool { match (self.md5_actual, self.md5_expected) { (Some(actual), Some(expected)) => actual == expected, @@ -108,7 +78,4 @@ impl FileStatus { pub(crate) fn bad(&self) -> bool { !self.good() } - pub(crate) fn path(&self) -> &Path { - &self.path - } } diff --git a/src/main.rs b/src/main.rs index f5bb9549..0e66b630 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,9 +43,7 @@ mod err; mod outln; #[cfg(test)] -mod testing; - -#[cfg(test)] +#[macro_use] mod test_env; #[cfg(test)] diff --git a/src/metainfo.rs b/src/metainfo.rs index 87e3b3b3..b789ade1 100644 --- a/src/metainfo.rs +++ b/src/metainfo.rs @@ -52,26 +52,27 @@ impl Metainfo { Self::deserialize(path, &bytes) } - #[cfg(test)] - pub(crate) fn dump(&self, path: impl AsRef) -> Result<(), Error> { - let path = path.as_ref(); - let bencode = bendy::serde::ser::to_bytes(&self).context(error::MetainfoSerialize)?; - fs::write(path, &bencode).context(error::Filesystem { path })?; - Ok(()) - } - pub(crate) fn deserialize(path: impl AsRef, bytes: &[u8]) -> Result { let path = path.as_ref(); - bendy::serde::de::from_bytes(&bytes).context(error::MetainfoLoad { path }) + let metainfo = bendy::serde::de::from_bytes(&bytes).context(error::MetainfoLoad { path })?; + Ok(metainfo) } pub(crate) fn serialize(&self) -> Result, Error> { bendy::serde::ser::to_bytes(&self).context(error::MetainfoSerialize) } + #[cfg(test)] + pub(crate) fn dump(&self, path: impl AsRef) -> Result<(), Error> { + let path = path.as_ref(); + let bencode = bendy::serde::ser::to_bytes(&self).context(error::MetainfoSerialize)?; + fs::write(path, &bencode).context(error::Filesystem { path })?; + Ok(()) + } + #[cfg(test)] pub(crate) fn from_bytes(bytes: &[u8]) -> Metainfo { - bendy::serde::de::from_bytes(bytes).unwrap() + Self::deserialize("", bytes).unwrap() } pub(crate) fn files<'a>( diff --git a/src/opt/torrent/create.rs b/src/opt/torrent/create.rs index d314424d..5e8c11c5 100644 --- a/src/opt/torrent/create.rs +++ b/src/opt/torrent/create.rs @@ -328,47 +328,39 @@ mod tests { use pretty_assertions::assert_eq; - fn environment(args: &[&str]) -> TestEnv { - testing::env(["torrent", "create"].iter().chain(args).cloned()) - } - - fn tree_environment(args: &[&str], tempdir: TempDir) -> TestEnv { - TestEnvBuilder::new() - .args(["imdl", "torrent", "create"].iter().chain(args).cloned()) - .tempdir(tempdir) - .build() - } - - macro_rules! env { - { - args: [$($arg:expr),* $(,)?], - tree: { - $($tree:tt)* - } $(,)? - } => { - { - let tempdir = temptree! { $($tree)* }; - tree_environment(&[$($arg),*], tempdir) - } - } - } - #[test] fn require_input_argument() { - let mut env = env! { args: [], tree: {} }; + let mut env = test_env! { args: [], tree: {} }; assert!(matches!(env.run(), Err(Error::Clap { .. }))); } #[test] fn require_input_present() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], + tree: {}, + }; assert!(matches!(env.run(), Err(Error::Filesystem { .. }))); } #[test] fn torrent_file_is_bencode_dict() { - let mut env = env! { - args: ["--input", "foo", "--announce", "https://bar"], + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "https://bar", + ], tree: { foo: "", } @@ -382,8 +374,8 @@ mod tests { #[test] fn privacy_defaults_to_false() { - let mut env = env! { - args: ["--input", "foo", "--announce", "https://bar"], + let mut env = test_env! { + args: ["torrent", "create", "--input", "foo", "--announce", "https://bar"], tree: { foo: "", } @@ -395,8 +387,8 @@ mod tests { #[test] fn privacy_flag_sets_privacy() { - let mut env = env! { - args: ["--input", "foo", "--announce", "https://bar", "--private"], + let mut env = test_env! { + args: ["torrent", "create", "--input", "foo", "--announce", "https://bar", "--private"], tree: { foo: "", } @@ -408,8 +400,8 @@ mod tests { #[test] fn tracker_flag_must_be_url() { - let mut env = env! { - args: ["--input", "foo", "--announce", "bar"], + let mut env = test_env! { + args: ["torrent", "create", "--input", "foo", "--announce", "bar"], tree: { foo: "", } @@ -419,8 +411,8 @@ mod tests { #[test] fn announce_single() { - let mut env = env! { - args: ["--input", "foo", "--announce", "http://bar"], + let mut env = test_env! { + args: ["torrent", "create", "--input", "foo", "--announce", "http://bar"], tree: { foo: "", } @@ -433,8 +425,10 @@ mod tests { #[test] fn announce_udp() { - let mut env = env! { + let mut env = test_env! { args: [ + "torrent", + "create", "--input", "foo", "--announce", @@ -455,8 +449,19 @@ mod tests { #[test] fn announce_wss_tracker() { - let mut env = environment(&["--input", "foo", "--announce", "wss://tracker.btorrent.xyz"]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "wss://tracker.btorrent.xyz", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.announce, "wss://tracker.btorrent.xyz/"); @@ -465,15 +470,21 @@ mod tests { #[test] fn announce_single_tier() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--announce-tier", - "http://bar,http://baz", - ]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--announce-tier", + "http://bar,http://baz", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.announce, "http://bar/"); @@ -485,17 +496,23 @@ mod tests { #[test] fn announce_multiple_tiers() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--announce-tier", - "http://bar,http://baz", - "--announce-tier", - "http://abc,http://xyz", - ]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--announce-tier", + "http://bar,http://baz", + "--announce-tier", + "http://abc,http://xyz", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.announce, "http://bar/"); @@ -510,8 +527,19 @@ mod tests { #[test] fn comment_default() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.comment, None); @@ -519,15 +547,21 @@ mod tests { #[test] fn comment_set() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--comment", - "Hello, world!", - ]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--comment", + "Hello, world!", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.comment.unwrap(), "Hello, world!"); @@ -535,8 +569,19 @@ mod tests { #[test] fn piece_length_default() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.info.piece_length, Bytes::from(16 * 2u32.pow(10))); @@ -544,15 +589,21 @@ mod tests { #[test] fn piece_length_override() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--piece-length", - "64KiB", - ]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--piece-length", + "64KiB", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.info.piece_length, Bytes(64 * 1024)); @@ -560,15 +611,21 @@ mod tests { #[test] fn si_piece_size() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--piece-length", - "0.5MiB", - ]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--piece-length", + "0.5MiB", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.info.piece_length, Bytes(512 * 1024)); @@ -576,15 +633,21 @@ mod tests { #[test] fn name() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--piece-length", - "16KiB", - ]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--piece-length", + "16KiB", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.info.name, "foo"); @@ -592,17 +655,23 @@ mod tests { #[test] fn name_subdir() { - let mut env = environment(&[ - "--input", - "foo/bar", - "--announce", - "http://bar", - "--piece-length", - "32KiB", - ]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); - fs::write(dir.join("bar"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo/bar", + "--announce", + "http://bar", + "--piece-length", + "32KiB", + ], + tree: { + foo: { + bar: "", + }, + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo/bar.torrent"); assert_eq!(metainfo.info.name, "bar"); @@ -610,23 +679,40 @@ mod tests { #[test] fn destination_override() { - let mut env = environment(&[ - "--input", - "foo", - "--output", - "x.torrent", - "--announce", - "http://bar", - ]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--output", + "x.torrent", + "--announce", + "http://bar", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); env.load_metainfo("x.torrent"); } #[test] fn created_by_default() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.created_by.unwrap(), consts::CREATED_BY_DEFAULT); @@ -634,14 +720,20 @@ mod tests { #[test] fn created_by_unset() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--no-created-by", - ]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--no-created-by", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.created_by, None); @@ -649,8 +741,19 @@ mod tests { #[test] fn encoding() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.encoding, Some("UTF-8".into())); @@ -658,8 +761,19 @@ mod tests { #[test] fn created_date_default() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], + tree: { + foo: "", + }, + }; let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() @@ -672,14 +786,20 @@ mod tests { #[test] fn created_date_unset() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--no-creation-date", - ]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--no-creation-date", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.creation_date, None); @@ -687,8 +807,15 @@ mod tests { #[test] fn single_small() { - let mut env = env! { - args: ["--input", "foo", "--announce", "http://bar"], + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], tree: { foo: "bar", }, @@ -707,18 +834,23 @@ mod tests { #[test] fn single_one_byte_piece() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--piece-length", - "1", - "--allow", - "small-piece-length", - ]); - let contents = "bar"; - fs::write(env.resolve("foo"), contents).unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--piece-length", + "1", + "--allow", + "small-piece-length", + ], + tree: { + foo: "bar", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!( @@ -728,7 +860,7 @@ mod tests { assert_eq!( metainfo.info.mode, Mode::Single { - length: Bytes(contents.len() as u64), + length: Bytes(3), md5sum: None, } ) @@ -736,9 +868,19 @@ mod tests { #[test] fn single_empty() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); - let contents = ""; - fs::write(env.resolve("foo"), contents).unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.info.pieces.count(), 0); @@ -753,9 +895,19 @@ mod tests { #[test] fn multiple_no_files() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], + tree: { + foo: {}, + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.info.pieces.count(), 0); @@ -764,8 +916,16 @@ mod tests { #[test] fn multiple_one_file_md5() { - let mut env = env! { - args: ["--input", "foo", "--announce", "http://bar", "--md5sum"], + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--md5sum", + ], tree: { foo: { bar: "bar", @@ -792,8 +952,15 @@ mod tests { #[test] fn multiple_one_file_md5_off() { - let mut env = env! { - args: ["--input", "foo", "--announce", "http://bar"], + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], tree: { foo: { bar: "bar", @@ -820,12 +987,24 @@ mod tests { #[test] fn multiple_three_files() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--md5sum"]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); - fs::write(dir.join("a"), "abc").unwrap(); - fs::write(dir.join("x"), "xyz").unwrap(); - fs::write(dir.join("h"), "hij").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--md5sum" + ], + tree: { + foo: { + a: "abc", + x: "xyz", + h: "hij", + }, + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["abchijxyz"])); @@ -858,7 +1037,18 @@ mod tests { #[test] fn open() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--open"]); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--open", + ], + tree: {}, + }; let opened = env.resolve("opened.txt"); let torrent = env.resolve("foo.torrent"); @@ -910,16 +1100,21 @@ mod tests { #[test] fn uneven_piece_length() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--piece-length", - "17KiB", - ]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--piece-length", + "17KiB", + ], + tree: { + foo: {}, + }, + }; assert_matches!( env.run(), Err(Error::PieceLengthUneven { bytes }) if bytes.0 == 17 * 1024 @@ -928,66 +1123,86 @@ mod tests { #[test] fn uneven_piece_length_allow() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--piece-length", - "17KiB", - "--allow", - "uneven-piece-length", - ]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--piece-length", + "17KiB", + "--allow", + "uneven-piece-length", + ], + tree: { + foo: {}, + }, + }; env.run().unwrap(); env.load_metainfo("foo.torrent"); } #[test] fn zero_piece_length() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--piece-length", - "0", - ]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--piece-length", + "0", + ], + tree: { + foo: {}, + }, + }; assert_matches!(env.run(), Err(Error::PieceLengthZero)); } #[test] fn small_piece_length() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--piece-length", - "8KiB", - ]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--piece-length", + "8KiB", + ], + tree: { + foo: "", + }, + }; assert_matches!(env.run(), Err(Error::PieceLengthSmall)); } #[test] fn small_piece_length_allow() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--piece-length", - "8KiB", - "--allow", - "small-piece-length", - ]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--piece-length", + "8KiB", + "--allow", + "small-piece-length", + ], + tree: { + foo: {}, + } + }; env.run().unwrap(); env.load_metainfo("foo.torrent"); } @@ -1038,15 +1253,21 @@ Content Size 9 bytes #[test] fn write_to_stdout() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--output", - "-", - ]); - fs::write(env.resolve("foo"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--output", + "-", + ], + tree: { + foo: "", + }, + }; env.run().unwrap(); let bytes = env.out_bytes(); Metainfo::from_bytes(&bytes); @@ -1054,9 +1275,20 @@ Content Size 9 bytes #[test] fn force_default() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); - fs::write(env.resolve("foo"), "").unwrap(); - fs::write(env.resolve("foo.torrent"), "").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar" + ], + tree: { + foo: "", + "foo.torrent": "foo", + }, + }; assert_matches!( env.run().unwrap_err(), Error::Filesystem {source, path} @@ -1066,20 +1298,43 @@ Content Size 9 bytes #[test] fn force_true() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--force"]); - fs::write(env.resolve("foo"), "").unwrap(); - fs::write(env.resolve("foo.torrent"), "foo").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--force", + ], + tree: { + foo: "", + "foo.torrent": "foo", + }, + }; env.run().unwrap(); env.load_metainfo("foo.torrent"); } #[test] fn exclude_junk() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); - fs::write(dir.join("Thumbs.db"), "abc").unwrap(); - fs::write(dir.join("Desktop.ini"), "abc").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], + tree: { + foo: { + "Thumbs.db": "abc", + "Desktop.ini": "abc", + }, + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_matches!( @@ -1091,17 +1346,23 @@ Content Size 9 bytes #[test] fn include_junk() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--include-junk", - ]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); - fs::write(dir.join("Thumbs.db"), "abc").unwrap(); - fs::write(dir.join("Desktop.ini"), "abc").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--include-junk", + ], + tree: { + foo: { + "Thumbs.db": "abc", + "Desktop.ini": "abc", + }, + }, + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_matches!( @@ -1113,32 +1374,43 @@ Content Size 9 bytes #[test] fn skip_hidden() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar"]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); - fs::write(dir.join(".hidden"), "abc").unwrap(); - #[cfg(target_os = "windows")] - { - let path = dir.join("hidden"); - fs::write(&path, "abc").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], + tree: { + foo: { + ".hidden": "abc", + hidden: "abc", + }, + } + }; + + if cfg!(target_os = "windows") { Command::new("attrib") .arg("+h") - .arg(&path) + .arg(env.resolve("foo/hidden")) .status() .unwrap(); - } - #[cfg(target_os = "macos")] - { - let path = dir.join("hidden"); - fs::write(&path, "abc").unwrap(); + } else if cfg!(target_os = "macos") { Command::new("chflags") .arg("hidden") - .arg(&path) + .arg(env.resolve("foo/hidden")) .status() .unwrap(); + } else { + fs::remove_file(env.resolve("foo/hidden")).unwrap(); } + env.run().unwrap(); + let metainfo = env.load_metainfo("foo.torrent"); + assert_matches!( metainfo.info.mode, Mode::Multiple { files } if files.len() == 0 @@ -1148,23 +1420,45 @@ Content Size 9 bytes #[test] fn include_hidden() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--include-hidden", - ]); - let dir = env.resolve("foo"); - fs::create_dir(&dir).unwrap(); - fs::write(dir.join(".hidden"), "abc").unwrap(); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--include-hidden", + ], + tree: { + foo: { + ".hidden": "abc", + hidden: "abc", + }, + } + }; + + if cfg!(target_os = "windows") { + Command::new("attrib") + .arg("+h") + .arg(env.resolve("foo/hidden")) + .status() + .unwrap(); + } else if cfg!(target_os = "macos") { + Command::new("chflags") + .arg("hidden") + .arg(env.resolve("foo/hidden")) + .status() + .unwrap(); + } + env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_matches!( metainfo.info.mode, - Mode::Multiple { files } if files.len() == 1 + Mode::Multiple { files } if files.len() == 2 ); - assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["abc"])); + assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["abcabc"])); } fn populate_symlinks(env: &Env) { @@ -1199,7 +1493,18 @@ Content Size 9 bytes #[test] fn skip_symlinks() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--md5sum"]); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--md5sum", + ], + tree: {}, + }; populate_symlinks(&env); env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); @@ -1213,14 +1518,19 @@ Content Size 9 bytes #[test] #[cfg(unix)] fn follow_symlinks() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--follow-symlinks", - "--md5sum", - ]); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--follow-symlinks", + "--md5sum", + ], + tree: {}, + }; populate_symlinks(&env); env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); @@ -1252,7 +1562,19 @@ Content Size 9 bytes #[test] #[cfg(unix)] fn symlink_root() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--md5sum"]); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--md5sum", + ], + tree: {}, + }; + let file_src = env.resolve("bar"); let file_link = env.resolve("foo"); @@ -1268,9 +1590,24 @@ Content Size 9 bytes #[test] fn skip_dot_dir_contents() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--md5sum"]); - env.create_dir("foo/.bar"); - env.create_file("foo/.bar/baz", "baz"); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--md5sum", + ], + tree: { + foo: { + ".bar": { + baz: "baz", + }, + }, + } + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_matches!( @@ -1282,11 +1619,26 @@ Content Size 9 bytes #[test] fn skip_hidden_attribute_dir_contents() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--md5sum"]); - env.create_dir("foo/bar"); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--md5sum" + ], + tree: { + foo: { + bar: {}, + }, + }, + }; + #[cfg(target_os = "windows")] { - env.create_file("foo/bar/baz", "baz"); + env.write("foo/bar/baz", "baz"); let path = env.resolve("foo/bar"); Command::new("attrib") .arg("+h") @@ -1294,9 +1646,10 @@ Content Size 9 bytes .status() .unwrap(); } + #[cfg(target_os = "macos")] { - env.create_file("foo/bar/baz", "baz"); + env.write("foo/bar/baz", "baz"); let path = env.resolve("foo/bar"); Command::new("chflags") .arg("hidden") @@ -1304,6 +1657,7 @@ Content Size 9 bytes .status() .unwrap(); } + env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_matches!( @@ -1315,11 +1669,25 @@ Content Size 9 bytes #[test] fn glob_exclude() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--glob", "!a"]); - env.create_dir("foo"); - env.create_file("foo/a", "a"); - env.create_file("foo/b", "b"); - env.create_file("foo/c", "c"); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--glob", + "!a" + ], + tree: { + foo: { + a: "a", + b: "b", + c: "c", + }, + } + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_matches!( @@ -1333,11 +1701,26 @@ Content Size 9 bytes #[test] fn glob_exclude_nomatch() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--glob", "!x"]); - env.create_dir("foo"); - env.create_file("foo/a", "a"); - env.create_file("foo/b", "b"); - env.create_file("foo/c", "c"); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--glob", + "!x" + ], + tree: { + foo: { + a: "a", + b: "b", + c: "c", + }, + } + }; + env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_matches!( @@ -1351,18 +1734,25 @@ Content Size 9 bytes #[test] fn glob_include() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--glob", - "[bc]", - ]); - env.create_dir("foo"); - env.create_file("foo/a", "a"); - env.create_file("foo/b", "b"); - env.create_file("foo/c", "c"); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--glob", + "[bc]", + ], + tree: { + foo: { + a: "a", + b: "b", + c: "c", + }, + } + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_matches!( @@ -1376,11 +1766,25 @@ Content Size 9 bytes #[test] fn glob_include_nomatch() { - let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--glob", "x"]); - env.create_dir("foo"); - env.create_file("foo/a", "a"); - env.create_file("foo/b", "b"); - env.create_file("foo/c", "c"); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--glob", + "x", + ], + tree: { + foo: { + a: "a", + b: "b", + c: "c", + }, + } + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_matches!( @@ -1392,37 +1796,49 @@ Content Size 9 bytes #[test] fn glob_precedence() { - let mut env = environment(&[ - "--input", - "foo", - "--announce", - "http://bar", - "--glob", - "!*", - "--glob", - "[ab]", - "--glob", - "!b", - ]); - env.create_dir("foo"); - env.create_file("foo/a", "a"); - env.create_file("foo/b", "b"); - env.create_file("foo/c", "c"); + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--glob", + "!*", + "--glob", + "[ab]", + "--glob", + "!b", + ], + tree: { + foo: { + a: "a", + b: "b", + c: "c", + }, + } + }; env.run().unwrap(); let metainfo = env.load_metainfo("foo.torrent"); assert_matches!( metainfo.info.mode, Mode::Multiple { files } if files.len() == 1 ); - let mut pieces = PieceList::new(); - pieces.push(Sha1::from("a").digest().into()); - assert_eq!(metainfo.info.pieces, pieces); + assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["a"])); } #[test] fn nodes_default() { - let mut env = env! { - args: ["--input", "foo", "--announce", "http://bar"], + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + ], tree: { foo: "", } @@ -1434,8 +1850,17 @@ Content Size 9 bytes #[test] fn nodes_invalid() { - let mut env = env! { - args: ["--input", "foo", "--announce", "http://bar", "--dht-node", "blah"], + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "http://bar", + "--dht-node", + "blah", + ], tree: { foo: "", }, @@ -1445,8 +1870,10 @@ Content Size 9 bytes #[test] fn nodes_valid() { - let mut env = env! { + let mut env = test_env! { args: [ + "torrent", + "create", "--input", "foo", "--announce", diff --git a/src/opt/torrent/verify.rs b/src/opt/torrent/verify.rs index caa089aa..5d4dbf96 100644 --- a/src/opt/torrent/verify.rs +++ b/src/opt/torrent/verify.rs @@ -10,7 +10,7 @@ pub(crate) struct Verify { #[structopt( name = "TORRENT", long = "metainfo", - help = "Verify input data against `TORRENT` metainfo file.", + help = "Verify input data against torrent metainfo in `TORRENT`.", parse(from_os_str) )] metainfo: PathBuf, @@ -36,9 +36,8 @@ impl Verify { let status = metainfo.verify(&base)?; - status.write(env)?; - if status.good() { + errln!(env, "Verification succeeded."); Ok(()) } else { Err(Error::Verify { status }) @@ -50,13 +49,96 @@ impl Verify { mod tests { use super::*; - fn environment(args: &[&str]) -> TestEnv { - testing::env(["torrent", "create"].iter().chain(args).cloned()) - } - #[test] fn require_metainfo_argument() { - let mut env = environment(&[]); + let mut env = test_env! { + args: [], + tree: {}, + }; assert!(matches!(env.run(), Err(Error::Clap { .. }))); } + + #[test] + fn pass() -> Result<()> { + let mut create_env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "https://bar", + ], + tree: { + foo: { + a: "abc", + d: "efg", + h: "ijk", + }, + }, + }; + + create_env.run()?; + + let torrent = create_env.resolve("foo.torrent"); + + let mut verify_env = test_env! { + args: [ + "torrent", + "verify", + "--metainfo", + torrent, + ], + tree: {}, + }; + + assert_matches!(verify_env.run(), Ok(())); + + assert_eq!(verify_env.err(), "Verification succeeded.\n"); + + Ok(()) + } + + #[test] + fn fail() -> Result<()> { + let mut create_env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "https://bar", + ], + tree: { + foo: { + a: "abc", + d: "efg", + h: "ijk", + }, + }, + }; + + create_env.run()?; + + create_env.write("foo/a", "xyz"); + + let torrent = create_env.resolve("foo.torrent"); + + let mut verify_env = test_env! { + args: [ + "torrent", + "verify", + "--metainfo", + torrent, + ], + tree: {}, + }; + + assert_matches!(verify_env.run(), Err(Error::Verify { .. })); + + assert_eq!(verify_env.err(), ""); + + Ok(()) + } } diff --git a/src/status.rs b/src/status.rs index fab01ef2..ebae10dc 100644 --- a/src/status.rs +++ b/src/status.rs @@ -15,20 +15,13 @@ impl Status { self.pieces } - pub(crate) fn good(&self) -> bool { - self.pieces && self.files.iter().all(FileStatus::good) + #[cfg(test)] + pub(crate) fn files(&self) -> &[FileStatus] { + &self.files } - pub(crate) fn write(&self, out: &mut Env) -> Result<()> { - for file in &self.files { - errln!(out, "{} {}", file.icon(), file.path().display()); - } - - if !self.pieces() { - errln!(out, "Piece hashes incorrect"); - } - - Ok(()) + pub(crate) fn good(&self) -> bool { + self.pieces && self.files.iter().all(FileStatus::good) } } diff --git a/src/test_env.rs b/src/test_env.rs index b447c6d0..e23e99a0 100644 --- a/src/test_env.rs +++ b/src/test_env.rs @@ -1,5 +1,24 @@ use crate::common::*; +macro_rules! test_env { + { + args: [$($arg:expr),* $(,)?], + tree: { + $($tree:tt)* + } $(,)? + } => { + { + let tempdir = temptree! { $($tree)* }; + + TestEnvBuilder::new() + .tempdir(tempdir) + .arg("imdl") + $(.arg($arg))* + .build() + } + } +} + pub(crate) struct TestEnv { env: Env, err: Capture, @@ -23,11 +42,7 @@ impl TestEnv { self.out.bytes() } - pub(crate) fn create_dir(&self, path: impl AsRef) { - fs::create_dir_all(self.env.resolve(path.as_ref())).unwrap(); - } - - pub(crate) fn create_file(&self, path: impl AsRef, bytes: impl AsRef<[u8]>) { + pub(crate) fn write(&self, path: impl AsRef, bytes: impl AsRef<[u8]>) { fs::write(self.env.resolve(path), bytes.as_ref()).unwrap(); } diff --git a/src/test_env_builder.rs b/src/test_env_builder.rs index 2ec1344c..68435766 100644 --- a/src/test_env_builder.rs +++ b/src/test_env_builder.rs @@ -1,7 +1,7 @@ use crate::common::*; pub(crate) struct TestEnvBuilder { - args: Vec, + args: Vec, out_is_term: bool, use_color: bool, tempdir: Option, @@ -22,21 +22,14 @@ impl TestEnvBuilder { self } - pub(crate) fn arg(mut self, arg: impl Into) -> Self { + pub(crate) fn arg(mut self, arg: impl Into) -> Self { self.args.push(arg.into()); self } - pub(crate) fn args(mut self, args: impl IntoIterator>) -> Self { - for arg in args { - self.args.push(arg.into()); - } - self - } - pub(crate) fn arg_slice(mut self, args: &[&str]) -> Self { for arg in args.iter().cloned() { - self.args.push(arg.to_owned()); + self.args.push(arg.into()); } self } diff --git a/src/testing.rs b/src/testing.rs deleted file mode 100644 index 133991c4..00000000 --- a/src/testing.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::common::*; - -pub(crate) fn env(iter: impl IntoIterator>) -> TestEnv { - TestEnvBuilder::new().arg("imdl").args(iter).build() -} diff --git a/src/verifier.rs b/src/verifier.rs index 262cc2a9..5f724f8e 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -1,6 +1,8 @@ use crate::common::*; -pub(crate) struct Verifier { +pub(crate) struct Verifier<'a> { + metainfo: &'a Metainfo, + base: &'a Path, buffer: Vec, piece_length: usize, pieces: PieceList, @@ -8,38 +10,40 @@ pub(crate) struct Verifier { piece_bytes_hashed: usize, } -impl Verifier { - pub(crate) fn new(piece_length: usize) -> Verifier { - Verifier { +impl<'a> Verifier<'a> { + fn new(metainfo: &'a Metainfo, base: &'a Path) -> Result> { + let piece_length = metainfo.info.piece_length.as_piece_length()?.into_usize(); + + Ok(Verifier { buffer: vec![0; piece_length], piece_bytes_hashed: 0, - sha1: Sha1::new(), pieces: PieceList::new(), + sha1: Sha1::new(), + base, + metainfo, piece_length, - } + }) } - pub(crate) fn verify(metainfo: &Metainfo, base: &Path) -> Result { - let piece_length = metainfo.info.piece_length.as_piece_length()?; - - let piece_length = piece_length.into_usize(); + pub(crate) fn verify(metainfo: &'a Metainfo, base: &'a Path) -> Result { + Self::new(metainfo, base)?.verify_metainfo() + } + fn verify_metainfo(mut self) -> Result { let mut status = Vec::new(); - let mut hasher = Self::new(piece_length); - - for (path, len, md5sum) in metainfo.files(&base) { + for (path, len, md5sum) in self.metainfo.files(&self.base) { status.push(FileStatus::status(&path, len, md5sum)); - hasher.hash(&path).ok(); + self.hash(&path).ok(); } - if hasher.piece_bytes_hashed > 0 { - hasher.pieces.push(hasher.sha1.digest().into()); - hasher.sha1.reset(); - hasher.piece_bytes_hashed = 0; + if self.piece_bytes_hashed > 0 { + self.pieces.push(self.sha1.digest().into()); + self.sha1.reset(); + self.piece_bytes_hashed = 0; } - let pieces = hasher.pieces == metainfo.info.pieces; + let pieces = self.pieces == self.metainfo.info.pieces; Ok(Status::new(pieces, status)) } @@ -77,3 +81,72 @@ impl Verifier { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn good() -> Result<()> { + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "https://bar", + ], + tree: { + foo: { + a: "abc", + d: "efg", + h: "ijk", + }, + }, + }; + + env.run()?; + + let metainfo = env.load_metainfo("foo.torrent"); + + assert!(metainfo.verify(&env.resolve("foo"))?.good()); + + Ok(()) + } + + #[test] + fn piece_mismatch() -> Result<()> { + let mut env = test_env! { + args: [ + "torrent", + "create", + "--input", + "foo", + "--announce", + "https://bar", + ], + tree: { + foo: { + a: "abc", + d: "efg", + h: "ijk", + }, + }, + }; + + env.run()?; + + env.write("foo/a", "xyz"); + + let metainfo = env.load_metainfo("foo.torrent"); + + let status = metainfo.verify(&env.resolve("foo"))?; + + assert!(status.files().iter().all(FileStatus::good)); + + assert!(!status.pieces()); + + Ok(()) + } +}