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::*;
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 MI: u64 = KI << 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 {
type Err = Error;

View File

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

View File

@ -1,7 +1,7 @@
use crate::common::*;
pub(crate) struct Env {
args: Vec<String>,
args: Vec<OsString>,
dir: Box<dyn AsRef<Path>>,
pub(crate) err: Box<dyn Write>,
pub(crate) out: Box<dyn Write>,
@ -76,7 +76,7 @@ impl Env {
D: AsRef<Path> + 'static,
O: Write + 'static,
E: Write + 'static,
S: Into<String>,
S: Into<OsString>,
I: IntoIterator<Item = S>,
{
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:") {

View File

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

View File

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

View File

@ -52,6 +52,16 @@ impl Metainfo {
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)]
pub(crate) fn dump(&self, path: impl AsRef<Path>) -> Result<(), Error> {
let path = path.as_ref();
@ -60,18 +70,9 @@ impl Metainfo {
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)]
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>(

File diff suppressed because it is too large Load Diff

View File

@ -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 = test_env! {
args: [],
tree: {},
};
assert!(matches!(env.run(), Err(Error::Clap { .. })));
}
#[test]
fn require_metainfo_argument() {
let mut env = environment(&[]);
assert!(matches!(env.run(), Err(Error::Clap { .. })));
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(())
}
}

View File

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

View File

@ -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<Path>) {
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]>) {
pub(crate) fn write(&self, path: impl AsRef<Path>, bytes: impl AsRef<[u8]>) {
fs::write(self.env.resolve(path), bytes.as_ref()).unwrap();
}

View File

@ -1,7 +1,7 @@
use crate::common::*;
pub(crate) struct TestEnvBuilder {
args: Vec<String>,
args: Vec<OsString>,
out_is_term: bool,
use_color: bool,
tempdir: Option<TempDir>,
@ -22,21 +22,14 @@ impl TestEnvBuilder {
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
}
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 {
for arg in args.iter().cloned() {
self.args.push(arg.to_owned());
self.args.push(arg.into());
}
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::*;
pub(crate) struct Verifier {
pub(crate) struct Verifier<'a> {
metainfo: &'a Metainfo,
base: &'a Path,
buffer: Vec<u8>,
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<Verifier<'a>> {
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<Status> {
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<Status> {
Self::new(metainfo, base)?.verify_metainfo()
}
fn verify_metainfo(mut self) -> Result<Status> {
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(())
}
}