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:
parent
2fb5bdb933
commit
d71bdffda1
96
src/bytes.rs
96
src/bytes.rs
|
@ -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;
|
||||
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
|
18
src/env.rs
18
src/env.rs
|
@ -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:") {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,9 +43,7 @@ mod err;
|
|||
mod outln;
|
||||
|
||||
#[cfg(test)]
|
||||
mod testing;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
mod test_env;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -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
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
111
src/verifier.rs
111
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<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(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user