Refactor tests and improve verification

Improve verification somewhat, but mostly refactor tests
to use test tree, and be nicer to read and write.

type: changed
This commit is contained in:
Casey Rodarmor 2020-03-05 21:44:20 -08:00
parent 2fb5bdb933
commit d71bdffda1
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
13 changed files with 1060 additions and 519 deletions

View File

@ -1,5 +1,53 @@
use crate::common::*; use crate::common::*;
impl Div<Bytes> for Bytes {
type Output = u64;
fn div(self, rhs: Bytes) -> u64 {
self.0 / rhs.0
}
}
impl Div<u64> for Bytes {
type Output = Bytes;
fn div(self, rhs: u64) -> Bytes {
Bytes::from(self.0 / rhs)
}
}
impl Mul<u64> for Bytes {
type Output = Bytes;
fn mul(self, rhs: u64) -> Self {
Bytes::from(self.0 * rhs)
}
}
impl DivAssign<u64> for Bytes {
fn div_assign(&mut self, rhs: u64) {
self.0 /= rhs;
}
}
impl MulAssign<u64> for Bytes {
fn mul_assign(&mut self, rhs: u64) {
self.0 *= rhs;
}
}
impl AddAssign<Bytes> for Bytes {
fn add_assign(&mut self, rhs: Bytes) {
self.0 += rhs.0;
}
}
impl SubAssign<u64> for Bytes {
fn sub_assign(&mut self, rhs: u64) {
self.0 -= rhs;
}
}
const KI: u64 = 1 << 10; const KI: u64 = 1 << 10;
const MI: u64 = KI << 10; const MI: u64 = KI << 10;
const GI: u64 = MI << 10; const GI: u64 = MI << 10;
@ -52,54 +100,6 @@ impl<I: Into<u64>> From<I> for Bytes {
} }
} }
impl Div<Bytes> for Bytes {
type Output = u64;
fn div(self, rhs: Bytes) -> u64 {
self.0 / rhs.0
}
}
impl Div<u64> for Bytes {
type Output = Bytes;
fn div(self, rhs: u64) -> Bytes {
Bytes::from(self.0 / rhs)
}
}
impl DivAssign<u64> for Bytes {
fn div_assign(&mut self, rhs: u64) {
self.0 /= rhs;
}
}
impl Mul<u64> for Bytes {
type Output = Bytes;
fn mul(self, rhs: u64) -> Self {
Bytes::from(self.0 * rhs)
}
}
impl MulAssign<u64> for Bytes {
fn mul_assign(&mut self, rhs: u64) {
self.0 *= rhs;
}
}
impl AddAssign<Bytes> for Bytes {
fn add_assign(&mut self, rhs: Bytes) {
self.0 += rhs.0;
}
}
impl SubAssign<u64> for Bytes {
fn sub_assign(&mut self, rhs: u64) {
self.0 -= rhs;
}
}
impl FromStr for Bytes { impl FromStr for Bytes {
type Err = Error; type Err = Error;

View File

@ -77,9 +77,6 @@ mod test {
pub(crate) use tempfile::TempDir; pub(crate) use tempfile::TempDir;
pub(crate) use temptree::temptree; pub(crate) use temptree::temptree;
// test modules
pub(crate) use crate::testing;
// test structs and enums // test structs and enums
pub(crate) use crate::{capture::Capture, test_env::TestEnv, test_env_builder::TestEnvBuilder}; pub(crate) use crate::{capture::Capture, test_env::TestEnv, test_env_builder::TestEnvBuilder};
} }

View File

@ -1,7 +1,7 @@
use crate::common::*; use crate::common::*;
pub(crate) struct Env { pub(crate) struct Env {
args: Vec<String>, args: Vec<OsString>,
dir: Box<dyn AsRef<Path>>, dir: Box<dyn AsRef<Path>>,
pub(crate) err: Box<dyn Write>, pub(crate) err: Box<dyn Write>,
pub(crate) out: Box<dyn Write>, pub(crate) out: Box<dyn Write>,
@ -76,7 +76,7 @@ impl Env {
D: AsRef<Path> + 'static, D: AsRef<Path> + 'static,
O: Write + 'static, O: Write + 'static,
E: Write + 'static, E: Write + 'static,
S: Into<String>, S: Into<OsString>,
I: IntoIterator<Item = S>, I: IntoIterator<Item = S>,
{ {
Self { Self {
@ -155,8 +155,8 @@ mod tests {
#[test] #[test]
fn error_message_on_stdout() { fn error_message_on_stdout() {
let mut env = testing::env( let mut env = test_env! {
[ args: [
"torrent", "torrent",
"create", "create",
"--input", "--input",
@ -165,11 +165,11 @@ mod tests {
"udp:bar.com", "udp:bar.com",
"--announce-tier", "--announce-tier",
"foo", "foo",
] ],
.iter() tree: {
.cloned(), foo: "",
); }
fs::write(env.resolve("foo"), "").unwrap(); };
env.status().ok(); env.status().ok();
let err = env.err(); let err = env.err();
if !err.starts_with("error: Failed to parse announce URL:") { if !err.starts_with("error: Failed to parse announce URL:") {

View File

@ -63,36 +63,6 @@ impl FileStatus {
Ok(()) 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 { fn md5(&self) -> bool {
match (self.md5_actual, self.md5_expected) { match (self.md5_actual, self.md5_expected) {
(Some(actual), Some(expected)) => actual == expected, (Some(actual), Some(expected)) => actual == expected,
@ -108,7 +78,4 @@ impl FileStatus {
pub(crate) fn bad(&self) -> bool { pub(crate) fn bad(&self) -> bool {
!self.good() !self.good()
} }
pub(crate) fn path(&self) -> &Path {
&self.path
}
} }

View File

@ -43,9 +43,7 @@ mod err;
mod outln; mod outln;
#[cfg(test)] #[cfg(test)]
mod testing; #[macro_use]
#[cfg(test)]
mod test_env; mod test_env;
#[cfg(test)] #[cfg(test)]

View File

@ -52,6 +52,16 @@ impl Metainfo {
Self::deserialize(path, &bytes) Self::deserialize(path, &bytes)
} }
pub(crate) fn deserialize(path: impl AsRef<Path>, bytes: &[u8]) -> Result<Metainfo, Error> {
let path = path.as_ref();
let metainfo = bendy::serde::de::from_bytes(&bytes).context(error::MetainfoLoad { path })?;
Ok(metainfo)
}
pub(crate) fn serialize(&self) -> Result<Vec<u8>, Error> {
bendy::serde::ser::to_bytes(&self).context(error::MetainfoSerialize)
}
#[cfg(test)] #[cfg(test)]
pub(crate) fn dump(&self, path: impl AsRef<Path>) -> Result<(), Error> { pub(crate) fn dump(&self, path: impl AsRef<Path>) -> Result<(), Error> {
let path = path.as_ref(); let path = path.as_ref();
@ -60,18 +70,9 @@ impl Metainfo {
Ok(()) Ok(())
} }
pub(crate) fn deserialize(path: impl AsRef<Path>, bytes: &[u8]) -> Result<Metainfo, Error> {
let path = path.as_ref();
bendy::serde::de::from_bytes(&bytes).context(error::MetainfoLoad { path })
}
pub(crate) fn serialize(&self) -> Result<Vec<u8>, Error> {
bendy::serde::ser::to_bytes(&self).context(error::MetainfoSerialize)
}
#[cfg(test)] #[cfg(test)]
pub(crate) fn from_bytes(bytes: &[u8]) -> Metainfo { pub(crate) fn from_bytes(bytes: &[u8]) -> Metainfo {
bendy::serde::de::from_bytes(bytes).unwrap() Self::deserialize("<TEST>", bytes).unwrap()
} }
pub(crate) fn files<'a>( pub(crate) fn files<'a>(

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ pub(crate) struct Verify {
#[structopt( #[structopt(
name = "TORRENT", name = "TORRENT",
long = "metainfo", long = "metainfo",
help = "Verify input data against `TORRENT` metainfo file.", help = "Verify input data against torrent metainfo in `TORRENT`.",
parse(from_os_str) parse(from_os_str)
)] )]
metainfo: PathBuf, metainfo: PathBuf,
@ -36,9 +36,8 @@ impl Verify {
let status = metainfo.verify(&base)?; let status = metainfo.verify(&base)?;
status.write(env)?;
if status.good() { if status.good() {
errln!(env, "Verification succeeded.");
Ok(()) Ok(())
} else { } else {
Err(Error::Verify { status }) Err(Error::Verify { status })
@ -50,13 +49,96 @@ impl Verify {
mod tests { mod tests {
use super::*; use super::*;
fn environment(args: &[&str]) -> TestEnv { #[test]
testing::env(["torrent", "create"].iter().chain(args).cloned()) fn require_metainfo_argument() {
let mut env = test_env! {
args: [],
tree: {},
};
assert!(matches!(env.run(), Err(Error::Clap { .. })));
} }
#[test] #[test]
fn require_metainfo_argument() { fn pass() -> Result<()> {
let mut env = environment(&[]); let mut create_env = test_env! {
assert!(matches!(env.run(), Err(Error::Clap { .. }))); 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(())
} }
} }

View File

@ -15,20 +15,13 @@ impl Status {
self.pieces self.pieces
} }
pub(crate) fn good(&self) -> bool { #[cfg(test)]
self.pieces && self.files.iter().all(FileStatus::good) pub(crate) fn files(&self) -> &[FileStatus] {
&self.files
} }
pub(crate) fn write(&self, out: &mut Env) -> Result<()> { pub(crate) fn good(&self) -> bool {
for file in &self.files { self.pieces && self.files.iter().all(FileStatus::good)
errln!(out, "{} {}", file.icon(), file.path().display());
}
if !self.pieces() {
errln!(out, "Piece hashes incorrect");
}
Ok(())
} }
} }

View File

@ -1,5 +1,24 @@
use crate::common::*; 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 { pub(crate) struct TestEnv {
env: Env, env: Env,
err: Capture, err: Capture,
@ -23,11 +42,7 @@ impl TestEnv {
self.out.bytes() self.out.bytes()
} }
pub(crate) fn create_dir(&self, path: impl AsRef<Path>) { pub(crate) fn write(&self, path: impl AsRef<Path>, bytes: impl AsRef<[u8]>) {
fs::create_dir_all(self.env.resolve(path.as_ref())).unwrap();
}
pub(crate) fn create_file(&self, path: impl AsRef<Path>, bytes: impl AsRef<[u8]>) {
fs::write(self.env.resolve(path), bytes.as_ref()).unwrap(); fs::write(self.env.resolve(path), bytes.as_ref()).unwrap();
} }

View File

@ -1,7 +1,7 @@
use crate::common::*; use crate::common::*;
pub(crate) struct TestEnvBuilder { pub(crate) struct TestEnvBuilder {
args: Vec<String>, args: Vec<OsString>,
out_is_term: bool, out_is_term: bool,
use_color: bool, use_color: bool,
tempdir: Option<TempDir>, tempdir: Option<TempDir>,
@ -22,21 +22,14 @@ impl TestEnvBuilder {
self self
} }
pub(crate) fn arg(mut self, arg: impl Into<String>) -> Self { pub(crate) fn arg(mut self, arg: impl Into<OsString>) -> Self {
self.args.push(arg.into()); self.args.push(arg.into());
self self
} }
pub(crate) fn args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
for arg in args {
self.args.push(arg.into());
}
self
}
pub(crate) fn arg_slice(mut self, args: &[&str]) -> Self { pub(crate) fn arg_slice(mut self, args: &[&str]) -> Self {
for arg in args.iter().cloned() { for arg in args.iter().cloned() {
self.args.push(arg.to_owned()); self.args.push(arg.into());
} }
self self
} }

View File

@ -1,5 +0,0 @@
use crate::common::*;
pub(crate) fn env(iter: impl IntoIterator<Item = impl Into<String>>) -> TestEnv {
TestEnvBuilder::new().arg("imdl").args(iter).build()
}

View File

@ -1,6 +1,8 @@
use crate::common::*; use crate::common::*;
pub(crate) struct Verifier { pub(crate) struct Verifier<'a> {
metainfo: &'a Metainfo,
base: &'a Path,
buffer: Vec<u8>, buffer: Vec<u8>,
piece_length: usize, piece_length: usize,
pieces: PieceList, pieces: PieceList,
@ -8,38 +10,40 @@ pub(crate) struct Verifier {
piece_bytes_hashed: usize, piece_bytes_hashed: usize,
} }
impl Verifier { impl<'a> Verifier<'a> {
pub(crate) fn new(piece_length: usize) -> Verifier { fn new(metainfo: &'a Metainfo, base: &'a Path) -> Result<Verifier<'a>> {
Verifier { let piece_length = metainfo.info.piece_length.as_piece_length()?.into_usize();
Ok(Verifier {
buffer: vec![0; piece_length], buffer: vec![0; piece_length],
piece_bytes_hashed: 0, piece_bytes_hashed: 0,
sha1: Sha1::new(),
pieces: PieceList::new(), pieces: PieceList::new(),
sha1: Sha1::new(),
base,
metainfo,
piece_length, piece_length,
} })
} }
pub(crate) fn verify(metainfo: &Metainfo, base: &Path) -> Result<Status> { pub(crate) fn verify(metainfo: &'a Metainfo, base: &'a Path) -> Result<Status> {
let piece_length = metainfo.info.piece_length.as_piece_length()?; Self::new(metainfo, base)?.verify_metainfo()
}
let piece_length = piece_length.into_usize();
fn verify_metainfo(mut self) -> Result<Status> {
let mut status = Vec::new(); let mut status = Vec::new();
let mut hasher = Self::new(piece_length); for (path, len, md5sum) in self.metainfo.files(&self.base) {
for (path, len, md5sum) in metainfo.files(&base) {
status.push(FileStatus::status(&path, len, md5sum)); status.push(FileStatus::status(&path, len, md5sum));
hasher.hash(&path).ok(); self.hash(&path).ok();
} }
if hasher.piece_bytes_hashed > 0 { if self.piece_bytes_hashed > 0 {
hasher.pieces.push(hasher.sha1.digest().into()); self.pieces.push(self.sha1.digest().into());
hasher.sha1.reset(); self.sha1.reset();
hasher.piece_bytes_hashed = 0; 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)) Ok(Status::new(pieces, status))
} }
@ -77,3 +81,72 @@ impl Verifier {
Ok(()) 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(())
}
}