From 1526ab34728c7d8e61a48834033407bdf9fa10eb Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Thu, 1 Jul 2021 20:14:58 +0800 Subject: [PATCH 1/8] [all] Apply cargo fmt, and fix clippy warning of branches_sharing_code --- src/bin/thin_explore.rs | 52 ++++++++++++++-------------------- src/io_engine.rs | 6 ++-- src/pack/node_encode.rs | 9 ++---- src/pdata/btree_leaf_walker.rs | 3 +- 4 files changed, 27 insertions(+), 43 deletions(-) diff --git a/src/bin/thin_explore.rs b/src/bin/thin_explore.rs index 193181b..26776ab 100644 --- a/src/bin/thin_explore.rs +++ b/src/bin/thin_explore.rs @@ -183,24 +183,19 @@ impl<'a> StatefulWidget for SBWidget<'a> { format!("{}k", sb.data_block_size * 2), ]; - let table = Table::new( - vec![ - Row::new(flags), - Row::new(block), - Row::new(uuid), - Row::new(version), - Row::new(time), - Row::new(transaction_id), - Row::new(metadata_snap), - Row::new(mapping_root), - Row::new(details_root), - Row::new(data_block_size), - ] - ) - .header( - Row::new(vec!["Field", "Value"]) - .style(Style::default().fg(Color::Yellow)) - ) + let table = Table::new(vec![ + Row::new(flags), + Row::new(block), + Row::new(uuid), + Row::new(version), + Row::new(time), + Row::new(transaction_id), + Row::new(metadata_snap), + Row::new(mapping_root), + Row::new(details_root), + Row::new(data_block_size), + ]) + .header(Row::new(vec!["Field", "Value"]).style(Style::default().fg(Color::Yellow))) .block( Block::default() .borders(Borders::ALL) @@ -251,19 +246,14 @@ impl<'a> Widget for HeaderWidget<'a> { let max_entries = vec!["max_entries".to_string(), format!("{}", hdr.max_entries)]; let value_size = vec!["value size".to_string(), format!("{}", hdr.value_size)]; - let table = Table::new( - vec![ - Row::new(block), - Row::new(kind), - Row::new(nr_entries), - Row::new(max_entries), - Row::new(value_size), - ] - ) - .header( - Row::new(vec!["Field", "Value"]) - .style(Style::default().fg(Color::Yellow)) - ) + let table = Table::new(vec![ + Row::new(block), + Row::new(kind), + Row::new(nr_entries), + Row::new(max_entries), + Row::new(value_size), + ]) + .header(Row::new(vec!["Field", "Value"]).style(Style::default().fg(Color::Yellow))) .block(Block::default().borders(Borders::ALL).title(self.title)) .widths(&[Constraint::Length(20), Constraint::Length(60)]) .style(Style::default().fg(Color::White)) diff --git a/src/io_engine.rs b/src/io_engine.rs index e38f995..2bd7687 100644 --- a/src/io_engine.rs +++ b/src/io_engine.rs @@ -258,8 +258,7 @@ impl AsyncIoEngine { let fd_inner = inner.input.as_raw_fd(); for (i, b) in blocks.iter().enumerate() { - let read_e = opcode::Read::new( - types::Fd(fd_inner), b.data, BLOCK_SIZE as u32) + let read_e = opcode::Read::new(types::Fd(fd_inner), b.data, BLOCK_SIZE as u32) .offset(b.loc as i64 * BLOCK_SIZE as i64); unsafe { @@ -310,8 +309,7 @@ impl AsyncIoEngine { let fd_inner = inner.input.as_raw_fd(); for (i, b) in blocks.iter().enumerate() { - let write_e = opcode::Write::new( - types::Fd(fd_inner), b.data, BLOCK_SIZE as u32) + let write_e = opcode::Write::new(types::Fd(fd_inner), b.data, BLOCK_SIZE as u32) .offset(b.loc as i64 * BLOCK_SIZE as i64); unsafe { diff --git a/src/pack/node_encode.rs b/src/pack/node_encode.rs index d146ca3..76af735 100644 --- a/src/pack/node_encode.rs +++ b/src/pack/node_encode.rs @@ -59,11 +59,11 @@ fn summarise_node(data: &[u8]) -> IResult<&[u8], NodeSummary> { pub fn pack_btree_node(w: &mut W, data: &[u8]) -> PResult<()> { let (_, info) = nom_to_pr(summarise_node(data))?; + let (i, hdr) = nom_to_pr(take(32usize)(data))?; + let (i, keys) = nom_to_pr(run64(i, info.max_entries))?; if info.is_leaf { if info.value_size == std::mem::size_of::() { - let (i, hdr) = nom_to_pr(take(32usize)(data))?; - let (i, keys) = nom_to_pr(run64(i, info.max_entries))?; let (tail, values) = nom_to_pr(run64(i, info.max_entries))?; io_to_pr(pack_literal(w, hdr))?; @@ -76,8 +76,7 @@ pub fn pack_btree_node(w: &mut W, data: &[u8]) -> PResult<()> { Ok(()) } else { // We don't bother packing the values if they aren't u64 - let (i, hdr) = nom_to_pr(take(32usize)(data))?; - let (tail, keys) = nom_to_pr(run64(i, info.max_entries))?; + let tail = i; io_to_pr(pack_literal(w, hdr))?; io_to_pr(pack_u64s(w, &keys))?; @@ -87,8 +86,6 @@ pub fn pack_btree_node(w: &mut W, data: &[u8]) -> PResult<()> { } } else { // Internal node, values are also u64s - let (i, hdr) = nom_to_pr(take(32usize)(data))?; - let (i, keys) = nom_to_pr(run64(i, info.max_entries))?; let (tail, values) = nom_to_pr(run64(i, info.max_entries))?; io_to_pr(pack_literal(w, hdr))?; diff --git a/src/pdata/btree_leaf_walker.rs b/src/pdata/btree_leaf_walker.rs index fdccdcd..b19da99 100644 --- a/src/pdata/btree_leaf_walker.rs +++ b/src/pdata/btree_leaf_walker.rs @@ -210,14 +210,13 @@ impl<'a> LeafWalker<'a> { let depth = self.get_depth::(path, root, true)?; + self.sm_inc(root); if depth == 0 { // root is a leaf - self.sm_inc(root); self.leaves.insert(root as usize); visitor.visit(&kr, root)?; Ok(()) } else { - self.sm_inc(root); let root = self.engine.read(root).map_err(|_| io_err(path))?; self.walk_node(depth - 1, path, visitor, &kr, &root, true) From d00388f68acb8443a9117d3a34da03f18e8bcb3e Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Mon, 5 Jul 2021 15:56:47 +0800 Subject: [PATCH 2/8] [thin_shrink] Support short options --- src/bin/thin_shrink.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bin/thin_shrink.rs b/src/bin/thin_shrink.rs index 0e27f79..ff7fb45 100644 --- a/src/bin/thin_shrink.rs +++ b/src/bin/thin_shrink.rs @@ -18,6 +18,7 @@ fn main() { Arg::with_name("INPUT") .help("Specify thinp metadata xml file") .required(true) + .short("i") .long("input") .value_name("INPUT") .takes_value(true), @@ -26,6 +27,7 @@ fn main() { Arg::with_name("OUTPUT") .help("Specify output xml file") .required(true) + .short("o") .long("output") .value_name("OUTPUT") .takes_value(true), From 6660fde3c420793257de949aab9145a5290d15f1 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Wed, 23 Jun 2021 17:42:41 +0800 Subject: [PATCH 3/8] [tests] Refine the test naming and error messages - Make the naming of test cases less ambiguous, e.g., rename "missing_input_file" to "missing_input_arg" or "input_file_not_found" - Unify the error messages on input/output options --- base/file_utils.cc | 22 ++++++++++++++------- functional-tests/cache-functional-tests.scm | 18 +++++++++++------ functional-tests/era-functional-tests.scm | 17 +++++++++++----- tests/cache_check.rs | 6 +++--- tests/common/mod.rs | 2 +- tests/thin_repair.rs | 6 +++--- tests/thin_restore.rs | 6 +++--- 7 files changed, 49 insertions(+), 28 deletions(-) diff --git a/base/file_utils.cc b/base/file_utils.cc index e6095f7..af7ce2a 100644 --- a/base/file_utils.cc +++ b/base/file_utils.cc @@ -71,8 +71,11 @@ void file_utils::check_file_exists(string const &file, bool must_be_regular_file) { struct stat info; int r = ::stat(file.c_str(), &info); - if (r) - throw runtime_error("Couldn't stat file"); + if (r) { + ostringstream msg; + msg << file << ": " << base::error_string(errno); + throw runtime_error(msg.str()); + } if (must_be_regular_file && !S_ISREG(info.st_mode)) throw runtime_error("Not a regular file"); @@ -116,8 +119,11 @@ file_utils::get_file_length(string const &file) { uint64_t nr_bytes; int r = ::stat(file.c_str(), &info); - if (r) - throw runtime_error("Couldn't stat path"); + if (r) { + ostringstream msg; + msg << file << ": " << base::error_string(errno); + throw runtime_error(msg.str()); + } if (S_ISREG(info.st_mode)) // It's okay to cast st_size to a uint64_t value. @@ -136,9 +142,11 @@ file_utils::get_file_length(string const &file) { throw runtime_error("ioctl BLKGETSIZE64 failed"); } ::close(fd); - } else - // FIXME: needs a better message - throw runtime_error("bad path"); + } else { + ostringstream msg; + msg << file << ": " << "Not a block device or regular file"; + throw runtime_error(msg.str()); + } return nr_bytes; } diff --git a/functional-tests/cache-functional-tests.scm b/functional-tests/cache-functional-tests.scm index 13dcaee..b8e2f41 100644 --- a/functional-tests/cache-functional-tests.scm +++ b/functional-tests/cache-functional-tests.scm @@ -82,9 +82,12 @@ (define-scenario (cache-restore missing-input-file) "the input file can't be found" (with-empty-metadata (md) - (run-fail-rcv (_ stderr) (cache-restore "-i no-such-file -o" md) - (assert-superblock-all-zeroes md) - (assert-starts-with "Couldn't stat file" stderr)))) + (let ((bad-path "no-such-file")) + (run-fail-rcv (_ stderr) (cache-restore "-i" bad-path "-o" md) + (assert-superblock-all-zeroes md) + (assert-starts-with + (string-append bad-path ": No such file or directory") + stderr))))) (define-scenario (cache-restore garbage-input-file) "the input file is just zeroes" @@ -264,9 +267,12 @@ (define-scenario (cache-repair missing-input-file) "the input file can't be found" (with-empty-metadata (md) - (run-fail-rcv (_ stderr) (cache-repair "-i no-such-file -o" md) - (assert-superblock-all-zeroes md) - (assert-starts-with "Couldn't stat path" stderr)))) + (let ((bad-path "no-such-file")) + (run-fail-rcv (_ stderr) (cache-repair "-i no-such-file -o" md) + (assert-superblock-all-zeroes md) + (assert-starts-with + (string-append bad-path ": No such file or directory") + stderr))))) (define-scenario (cache-repair garbage-input-file) "the input file is just zeroes" diff --git a/functional-tests/era-functional-tests.scm b/functional-tests/era-functional-tests.scm index 890f0ff..a81b41b 100644 --- a/functional-tests/era-functional-tests.scm +++ b/functional-tests/era-functional-tests.scm @@ -152,9 +152,12 @@ (define-scenario (era-restore missing-input-file) "the input file can't be found" (with-empty-metadata (md) - (run-fail-rcv (_ stderr) (era-restore "-i no-such-file -o" md) - (assert-superblock-all-zeroes md) - (assert-starts-with "Couldn't stat file" stderr)))) + (let ((bad-path "no-such-file")) + (run-fail-rcv (_ stderr) (era-restore "-i no-such-file -o" md) + (assert-superblock-all-zeroes md) + (assert-starts-with + (string-append bad-path ": No such file or directory") + stderr))))) (define-scenario (era-restore garbage-input-file) "the input file is just zeroes" @@ -197,7 +200,9 @@ (with-empty-metadata (md) (run-fail-rcv (stdout stderr) (era-restore "--quiet" "-i" bad-xml "-o" md) (assert-eof stdout) - (assert-starts-with "Couldn't stat file" stderr))))) + (assert-starts-with + (string-append bad-xml ": No such file or directory") + stderr))))) (define-scenario (era-restore q-fail) "No output with --q(failing)" @@ -205,7 +210,9 @@ (with-empty-metadata (md) (run-fail-rcv (stdout stderr) (era-restore "-q" "-i" bad-xml "-o" md) (assert-eof stdout) - (assert-starts-with "Couldn't stat file" stderr))))) + (assert-starts-with + (string-append bad-xml ": No such file or directory") + stderr))))) ;;;----------------------------------------------------------- ;;; era_dump scenarios diff --git a/tests/cache_check.rs b/tests/cache_check.rs index 8cf716c..f9ec093 100644 --- a/tests/cache_check.rs +++ b/tests/cache_check.rs @@ -40,16 +40,16 @@ fn accepts_help() -> Result<()> { } #[test] -fn missing_metadata() -> Result<()> { +fn missing_input_arg() -> Result<()> { let stderr = run_fail(cache_check!())?; assert!(stderr.contains(msg::MISSING_INPUT_ARG)); Ok(()) } #[test] -fn no_such_metadata() -> Result<()> { +fn input_file_not_found() -> Result<()> { let stderr = run_fail(cache_check!("/arbitrary/filename"))?; - assert!(stderr.contains("No such file or directory")); + assert!(stderr.contains(msg::FILE_NOT_FOUND)); Ok(()) } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index f47347f..9dd69f6 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -20,7 +20,7 @@ use test_dir::TestDir; #[cfg(not(feature = "rust_tests"))] pub mod msg { - pub const FILE_NOT_FOUND: &str = "Couldn't stat file"; + pub const FILE_NOT_FOUND: &str = "No such file or directory"; pub const MISSING_INPUT_ARG: &str = "No input file provided"; pub const MISSING_OUTPUT_ARG: &str = "No output file provided"; } diff --git a/tests/thin_repair.rs b/tests/thin_repair.rs index e0b6529..3dcc506 100644 --- a/tests/thin_repair.rs +++ b/tests/thin_repair.rs @@ -48,13 +48,13 @@ fn dont_repair_xml() -> Result<()> { } #[test] -fn missing_input_file() -> Result<()> { +fn input_file_not_found() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; let stderr = run_fail(thin_repair!("-i", "no-such-file", "-o", &md))?; assert!(superblock_all_zeroes(&md)?); // TODO: replace with msg::FILE_NOT_FOUND once the rust version is ready - assert!(stderr.contains("Couldn't stat file")); + assert!(stderr.contains("No such file or directory")); Ok(()) } @@ -69,7 +69,7 @@ fn garbage_input_file() -> Result<()> { } #[test] -fn missing_output_file() -> Result<()> { +fn missing_output_arg() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; let stderr = run_fail(thin_repair!("-i", &md))?; diff --git a/tests/thin_restore.rs b/tests/thin_restore.rs index b5aa434..0fc23bf 100644 --- a/tests/thin_restore.rs +++ b/tests/thin_restore.rs @@ -40,7 +40,7 @@ fn accepts_help() -> Result<()> { } #[test] -fn no_input_file() -> Result<()> { +fn missing_input_arg() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; let stderr = run_fail(thin_restore!("-o", &md))?; @@ -49,7 +49,7 @@ fn no_input_file() -> Result<()> { } #[test] -fn missing_input_file() -> Result<()> { +fn input_file_not_found() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; let stderr = run_fail(thin_restore!("-i", "no-such-file", "-o", &md))?; @@ -69,7 +69,7 @@ fn garbage_input_file() -> Result<()> { } #[test] -fn no_output_file() -> Result<()> { +fn missing_output_arg() -> Result<()> { let mut td = TestDir::new()?; let xml = mk_valid_xml(&mut td)?; let stderr = run_fail(thin_restore!("-i", &xml))?; From 12ef69c31bd650333541bc926495154599c1d83c Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Thu, 1 Jul 2021 22:27:37 +0800 Subject: [PATCH 4/8] [tests] Pull out common tests on i/o options into reusable modules - Introduce modules for testing input/output options - Provide macros for generating test cases - Hide details of subprocess execution --- tests/cache_check.rs | 95 ++++-------- tests/common/common_args.rs | 82 ++++++++++ tests/common/input_arg.rs | 263 +++++++++++++++++++++++++++++++ tests/common/mod.rs | 285 ++++++++++++---------------------- tests/common/output_option.rs | 143 +++++++++++++++++ tests/thin_check.rs | 238 +++++++++++++++------------- tests/thin_delta.rs | 57 +++---- tests/thin_dump.rs | 144 +++++++++++------ tests/thin_metadata_pack.rs | 98 ++++-------- tests/thin_metadata_unpack.rs | 114 +++++--------- tests/thin_repair.rs | 157 ++++++++++--------- tests/thin_restore.rs | 118 +++++--------- tests/thin_rmap.rs | 67 ++++---- 13 files changed, 1077 insertions(+), 784 deletions(-) create mode 100644 tests/common/common_args.rs create mode 100644 tests/common/input_arg.rs create mode 100644 tests/common/output_option.rs diff --git a/tests/cache_check.rs b/tests/cache_check.rs index f9ec093..9a629d1 100644 --- a/tests/cache_check.rs +++ b/tests/cache_check.rs @@ -1,89 +1,47 @@ use anyhow::Result; -use duct::cmd; -use thinp::version::tools_version; mod common; +use common::common_args::*; +use common::input_arg::*; use common::test_dir::*; use common::*; //------------------------------------------ -#[test] -fn accepts_v() -> Result<()> { - let stdout = cache_check!("-V").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +const USAGE: &str = "Usage: cache_check [options] {device|file}\n\ + Options:\n \ + {-q|--quiet}\n \ + {-h|--help}\n \ + {-V|--version}\n \ + {--clear-needs-check-flag}\n \ + {--super-block-only}\n \ + {--skip-mappings}\n \ + {--skip-hints}\n \ + {--skip-discards}"; -#[test] -fn accepts_version() -> Result<()> { - let stdout = cache_check!("--version").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +//------------------------------------------ -const USAGE: &str = "Usage: cache_check [options] {device|file}\nOptions:\n {-q|--quiet}\n {-h|--help}\n {-V|--version}\n {--clear-needs-check-flag}\n {--super-block-only}\n {--skip-mappings}\n {--skip-hints}\n {--skip-discards}"; +test_accepts_help!(CACHE_CHECK, USAGE); +test_accepts_version!(CACHE_CHECK); +test_rejects_bad_option!(CACHE_CHECK); -#[test] -fn accepts_h() -> Result<()> { - let stdout = cache_check!("-h").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} +test_missing_input_arg!(CACHE_CHECK); +test_input_file_not_found!(CACHE_CHECK, ARG); +test_input_cannot_be_a_directory!(CACHE_CHECK, ARG); +test_unreadable_input_file!(CACHE_CHECK, ARG); -#[test] -fn accepts_help() -> Result<()> { - let stdout = cache_check!("--help").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} +test_help_message_for_tiny_input_file!(CACHE_CHECK, ARG); +test_spot_xml_data!(CACHE_CHECK, "cache_check", ARG); +test_corrupted_input_data!(CACHE_CHECK, ARG); -#[test] -fn missing_input_arg() -> Result<()> { - let stderr = run_fail(cache_check!())?; - assert!(stderr.contains(msg::MISSING_INPUT_ARG)); - Ok(()) -} - -#[test] -fn input_file_not_found() -> Result<()> { - let stderr = run_fail(cache_check!("/arbitrary/filename"))?; - assert!(stderr.contains(msg::FILE_NOT_FOUND)); - Ok(()) -} - -#[test] -fn metadata_cannot_be_a_directory() -> Result<()> { - let stderr = run_fail(cache_check!("/tmp"))?; - assert!(stderr.contains("Not a block device or regular file")); - Ok(()) -} - -#[test] -fn unreadable_metadata() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_valid_md(&mut td)?; - cmd!("chmod", "-r", &md).run()?; - let stderr = run_fail(cache_check!(&md))?; - assert!(stderr.contains("Permission denied")); - Ok(()) -} - -#[test] -fn corrupt_metadata() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - run_fail(cache_check!(&md))?; - Ok(()) -} +//------------------------------------------ #[test] fn failing_q() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; - let output = cache_check!("-q", &md).unchecked().run()?; - assert!(!output.status.success()); + let output = run_fail_raw(CACHE_CHECK, &["-q", md.to_str().unwrap()])?; assert_eq!(output.stdout.len(), 0); assert_eq!(output.stderr.len(), 0); Ok(()) @@ -93,8 +51,7 @@ fn failing_q() -> Result<()> { fn failing_quiet() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; - let output = cache_check!("--quiet", &md).unchecked().run()?; - assert!(!output.status.success()); + let output = run_fail_raw(CACHE_CHECK, &["--quiet", md.to_str().unwrap()])?; assert_eq!(output.stdout.len(), 0); assert_eq!(output.stderr.len(), 0); Ok(()) diff --git a/tests/common/common_args.rs b/tests/common/common_args.rs new file mode 100644 index 0000000..f4b0a5d --- /dev/null +++ b/tests/common/common_args.rs @@ -0,0 +1,82 @@ +use crate::common::*; +use thinp::version::tools_version; + +//------------------------------------------ +// help + +pub fn test_help_short(program: &str, usage: &str) -> Result<()> { + let stdout = run_ok(program, &["-h"])?; + assert_eq!(stdout, usage); + Ok(()) +} + +pub fn test_help_long(program: &str, usage: &str) -> Result<()> { + let stdout = run_ok(program, &["--help"])?; + assert_eq!(stdout, usage); + Ok(()) +} + +#[macro_export] +macro_rules! test_accepts_help { + ($program: ident, $usage: expr) => { + #[test] + fn accepts_h() -> Result<()> { + test_help_short($program, $usage) + } + + #[test] + fn accepts_help() -> Result<()> { + test_help_long($program, $usage) + } + }; +} + +//------------------------------------------ +// version + +pub fn test_version_short(program: &str) -> Result<()> { + let stdout = run_ok(program, &["-V"])?; + assert!(stdout.contains(tools_version())); + Ok(()) +} + +pub fn test_version_long(program: &str) -> Result<()> { + let stdout = run_ok(program, &["--version"])?; + assert!(stdout.contains(tools_version())); + Ok(()) +} + +#[macro_export] +macro_rules! test_accepts_version { + ($program: ident) => { + #[test] + fn accepts_v() -> Result<()> { + test_version_short($program) + } + + #[test] + fn accepts_version() -> Result<()> { + test_version_long($program) + } + }; +} + +//------------------------------------------ + +pub fn test_rejects_bad_option(program: &str) -> Result<()> { + let stderr = run_fail(program, &["--hedgehogs-only"])?; + assert!(stderr.contains("unrecognized option \'--hedgehogs-only\'")); + Ok(()) +} + +#[macro_export] +macro_rules! test_rejects_bad_option { + ($program: ident) => { + #[test] + fn rejects_bad_option() -> Result<()> { + test_rejects_bad_option($program) + } + }; +} + +//------------------------------------------ diff --git a/tests/common/input_arg.rs b/tests/common/input_arg.rs new file mode 100644 index 0000000..30f4400 --- /dev/null +++ b/tests/common/input_arg.rs @@ -0,0 +1,263 @@ +use crate::common::thin_xml_generator::{write_xml, FragmentedS}; +use crate::common::*; + +//------------------------------------------ +// wrappers + +pub fn with_output_md_untouched( + td: &mut TestDir, + input: &str, + thunk: &dyn Fn(&[&str]) -> Result<()>, +) -> Result<()> { + let output = mk_zeroed_md(td)?; + ensure_untouched(&output, || { + let args = ["-i", input, "-o", output.to_str().unwrap()]; + thunk(&args) + }) +} + +pub fn input_arg_only( + _td: &mut TestDir, + input: &str, + thunk: &dyn Fn(&[&str]) -> Result<()>, +) -> Result<()> { + let args = [input]; + thunk(&args) +} + +//------------------------------------------ +// test invalid arguments + +pub fn test_missing_input_arg(program: &str) -> Result<()> { + let stderr = run_fail(program, &[])?; + assert!(stderr.contains(msg::MISSING_INPUT_ARG)); + Ok(()) +} + +#[macro_export] +macro_rules! test_missing_input_arg { + ($program: ident) => { + #[test] + fn missing_input_arg() -> Result<()> { + test_missing_input_arg($program) + } + }; +} + +pub fn test_missing_input_option(program: &str) -> Result<()> { + let mut td = TestDir::new()?; + let output = mk_zeroed_md(&mut td)?; + ensure_untouched(&output, || { + let args = ["-o", output.to_str().unwrap()]; + let stderr = run_fail(program, &args)?; + assert!(stderr.contains(msg::MISSING_INPUT_ARG)); + Ok(()) + }) +} + +#[macro_export] +macro_rules! test_missing_input_option { + ($program: ident) => { + #[test] + fn missing_input_option() -> Result<()> { + test_missing_input_option($program) + } + }; +} + +pub fn test_input_file_not_found(program: &str, wrapper: F) -> Result<()> +where + F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, +{ + let mut td = TestDir::new()?; + + wrapper(&mut td, "no-such-file", &|args: &[&str]| { + let stderr = run_fail(program, args)?; + assert!(stderr.contains(msg::FILE_NOT_FOUND)); + Ok(()) + }) +} + +#[macro_export] +macro_rules! test_input_file_not_found { + ($program: ident, ARG) => { + #[test] + fn input_file_not_found() -> Result<()> { + test_input_file_not_found($program, input_arg_only) + } + }; + ($program: ident, OPTION) => { + #[test] + fn input_file_not_found() -> Result<()> { + test_input_file_not_found($program, with_output_md_untouched) + } + }; +} + +pub fn test_input_cannot_be_a_directory(program: &str, wrapper: F) -> Result<()> +where + F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, +{ + let mut td = TestDir::new()?; + + wrapper(&mut td, "/tmp", &|args: &[&str]| { + let stderr = run_fail(program, args)?; + assert!(stderr.contains("Not a block device or regular file")); + Ok(()) + }) +} + +#[macro_export] +macro_rules! test_input_cannot_be_a_directory { + ($program: ident, ARG) => { + #[test] + fn input_cannot_be_a_directory() -> Result<()> { + test_input_cannot_be_a_directory($program, input_arg_only) + } + }; + ($program: ident, OPTION) => { + #[test] + fn input_cannot_be_a_directory() -> Result<()> { + test_input_cannot_be_a_directory($program, with_output_md_untouched) + } + }; +} + +pub fn test_unreadable_input_file(program: &str, wrapper: F) -> Result<()> +where + F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, +{ + let mut td = TestDir::new()?; + + // input an unreadable file + let input = mk_valid_md(&mut td)?; + duct::cmd!("chmod", "-r", &input).run()?; + + wrapper(&mut td, input.to_str().unwrap(), &|args: &[&str]| { + let stderr = run_fail(program, args)?; + assert!(stderr.contains("Permission denied")); + Ok(()) + }) +} + +#[macro_export] +macro_rules! test_unreadable_input_file { + ($program: ident, ARG) => { + #[test] + fn unreadable_input_file() -> Result<()> { + test_unreadable_input_file($program, input_arg_only) + } + }; + ($program: ident, OPTION) => { + #[test] + fn unreadable_input_file() -> Result<()> { + test_unreadable_input_file($program, with_output_md_untouched) + } + }; +} + +//------------------------------------------ +// test invalid content + +pub fn test_help_message_for_tiny_input_file(program: &str, wrapper: F) -> Result<()> +where + F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, +{ + let mut td = TestDir::new()?; + + let input = td.mk_path("meta.bin"); + file_utils::create_sized_file(&input, 1024)?; + + wrapper(&mut td, input.to_str().unwrap(), &|args: &[&str]| { + let stderr = run_fail(program, args)?; + assert!(stderr.contains("Metadata device/file too small. Is this binary metadata?")); + Ok(()) + }) +} + +#[macro_export] +macro_rules! test_help_message_for_tiny_input_file { + ($program: ident, ARG) => { + #[test] + fn prints_help_message_for_tiny_input_file() -> Result<()> { + test_help_message_for_tiny_input_file($program, input_arg_only) + } + }; + ($program: ident, OPTION) => { + #[test] + fn prints_help_message_for_tiny_input_file() -> Result<()> { + test_help_message_for_tiny_input_file($program, with_output_md_untouched) + } + }; +} + +pub fn test_spot_xml_data(program: &str, name: &str, wrapper: F) -> Result<()> +where + F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, +{ + let mut td = TestDir::new()?; + + // input a large xml file + let input = td.mk_path("meta.xml"); + let mut gen = FragmentedS::new(4, 10240); + write_xml(&input, &mut gen)?; + + wrapper(&mut td, input.to_str().unwrap(), &|args: &[&str]| { + let stderr = run_fail(program, args)?; + eprintln!("{}", stderr); + let msg = format!( + "This looks like XML. {} only checks the binary metadata format.", + name + ); + assert!(stderr.contains(&msg)); + Ok(()) + }) +} + +#[macro_export] +macro_rules! test_spot_xml_data { + ($program: ident, $name: expr, ARG) => { + #[test] + fn spot_xml_data() -> Result<()> { + test_spot_xml_data($program, $name, input_arg_only) + } + }; + ($program: ident, $name: expr, OPTION) => { + #[test] + fn spot_xml_data() -> Result<()> { + test_spot_xml_data($program, $name, with_output_md_untouched) + } + }; +} + +pub fn test_corrupted_input_data(program: &str, wrapper: F) -> Result<()> +where + F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, +{ + let mut td = TestDir::new()?; + let input = mk_zeroed_md(&mut td)?; + + wrapper(&mut td, input.to_str().unwrap(), &|args: &[&str]| { + let stderr = run_fail(program, args)?; + assert!(stderr.contains("bad checksum in superblock")); + Ok(()) + }) +} + +#[macro_export] +macro_rules! test_corrupted_input_data { + ($program: ident, ARG) => { + #[test] + fn corrupted_input_data() -> Result<()> { + test_corrupted_input_data($program, input_arg_only) + } + }; + ($program: ident, OPTION) => { + #[test] + fn corrupted_input_data() -> Result<()> { + test_corrupted_input_data($program, with_output_md_untouched) + } + }; +} + +//------------------------------------------ diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 9dd69f6..71503b2 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,15 +1,16 @@ #![allow(dead_code)] use anyhow::Result; -use duct::{cmd, Expression}; use std::fs::OpenOptions; use std::io::{Read, Write}; use std::path::PathBuf; -use std::str::from_utf8; use thinp::file_utils; use thinp::io_engine::*; pub mod cache_xml_generator; +pub mod common_args; +pub mod input_arg; +pub mod output_option; pub mod test_dir; pub mod thin_xml_generator; @@ -28,8 +29,10 @@ pub mod msg { #[cfg(feature = "rust_tests")] pub mod msg { pub const FILE_NOT_FOUND: &str = "Couldn't find input file"; - pub const MISSING_INPUT_ARG: &str = "The following required arguments were not provided"; - pub const MISSING_OUTPUT_ARG: &str = "The following required arguments were not provided"; + pub const MISSING_INPUT_ARG: &str = + "The following required arguments were not provided\n -i "; + pub const MISSING_OUTPUT_ARG: &str = + "The following required arguments were not provided\n -o "; } //------------------------------------------ @@ -64,150 +67,64 @@ macro_rules! path_to { }; } -// FIXME: write a macro to generate these commands -// Known issue of nested macro definition: https://github.com/rust-lang/rust/issues/35853 -// RFC: https://github.com/rust-lang/rfcs/blob/master/text/3086-macro-metavar-expr.md -#[macro_export] -macro_rules! thin_check { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to!("thin_check"), args).stdout_capture().stderr_capture() - } - }; -} +//------------------------------------------ -#[macro_export] -macro_rules! thin_restore { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to!("thin_restore"), args).stdout_capture().stderr_capture() - } - }; -} +pub const CACHE_CHECK: &str = path_to!("cache_check"); +pub const CACHE_DUMP: &str = path_to!("cache_dump"); -#[macro_export] -macro_rules! thin_dump { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to!("thin_dump"), args).stdout_capture().stderr_capture() - } - }; -} - -#[macro_export] -macro_rules! thin_rmap { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to_cpp!("thin_rmap"), args).stdout_capture().stderr_capture() - } - }; -} - -#[macro_export] -macro_rules! thin_repair { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to_cpp!("thin_repair"), args).stdout_capture().stderr_capture() - } - }; -} - -#[macro_export] -macro_rules! thin_delta { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to_cpp!("thin_delta"), args).stdout_capture().stderr_capture() - } - }; -} - -#[macro_export] -macro_rules! thin_metadata_pack { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to_rust!("thin_metadata_pack"), args).stdout_capture().stderr_capture() - } - }; -} - -#[macro_export] -macro_rules! thin_metadata_unpack { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to_rust!("thin_metadata_unpack"), args).stdout_capture().stderr_capture() - } - }; -} - -#[macro_export] -macro_rules! cache_check { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to!("cache_check"), args).stdout_capture().stderr_capture() - } - }; -} - -#[macro_export] -macro_rules! thin_generate_metadata { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to_cpp!("thin_generate_metadata"), args).stdout_capture().stderr_capture() - } - }; -} - -#[macro_export] -macro_rules! thin_generate_mappings { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd(path_to_cpp!("thin_generate_mappings"), args).stdout_capture().stderr_capture() - } - }; -} - -#[macro_export] -macro_rules! thin_generate_damage { - ( $( $arg: expr ),* ) => { - { - use std::ffi::OsString; - let args: &[OsString] = &[$( Into::::into($arg) ),*]; - duct::cmd("bin/thin_generate_damage", args).stdout_capture().stderr_capture() - } - }; -} +pub const THIN_CHECK: &str = path_to!("thin_check"); +pub const THIN_DELTA: &str = path_to_cpp!("thin_delta"); // TODO: rust version +pub const THIN_DUMP: &str = path_to!("thin_dump"); +pub const THIN_METADATA_PACK: &str = path_to_rust!("thin_metadata_pack"); // rust-only +pub const THIN_METADATA_UNPACK: &str = path_to_rust!("thin_metadata_unpack"); // rust-only +pub const THIN_REPAIR: &str = path_to_cpp!("thin_repair"); // TODO: rust version +pub const THIN_RESTORE: &str = path_to!("thin_restore"); +pub const THIN_RMAP: &str = path_to_cpp!("thin_rmap"); // TODO: rust version +pub const THIN_GENERATE_METADATA: &str = path_to_cpp!("thin_generate_metadata"); // cpp-only +pub const THIN_GENERATE_MAPPINGS: &str = path_to_cpp!("thin_generate_mappings"); // cpp-only +pub const THIN_GENERATE_DAMAGE: &str = path_to_cpp!("thin_generate_damage"); // cpp-only //------------------------------------------ -// Returns stderr, a non zero status must be returned -pub fn run_fail(command: Expression) -> Result { - let output = command.stderr_capture().unchecked().run()?; - assert!(!output.status.success()); - Ok(from_utf8(&output.stderr[0..]).unwrap().to_string()) +// Returns stdout. The command must return zero. +pub fn run_ok(program: &str, args: &[&str]) -> Result { + let command = duct::cmd(program, args).stdout_capture().stderr_capture(); + let output = command.run()?; + assert!(output.status.success()); + let stdout = std::str::from_utf8(&output.stdout[..]) + .unwrap() + .trim_end_matches(|c| c == '\n' || c == '\r') + .to_string(); + Ok(stdout) } +// Returns the entire output. The command must return zero. +pub fn run_ok_raw(program: &str, args: &[&str]) -> Result { + let command = duct::cmd(program, args).stdout_capture().stderr_capture(); + let output = command.run()?; + assert!(output.status.success()); + Ok(output) +} + +// Returns stderr, a non zero status must be returned +pub fn run_fail(program: &str, args: &[&str]) -> Result { + let command = duct::cmd(program, args).stdout_capture().stderr_capture(); + let output = command.unchecked().run()?; + assert!(!output.status.success()); + let stderr = std::str::from_utf8(&output.stderr[..]).unwrap().to_string(); + Ok(stderr) +} + +// Returns the entire output, a non zero status must be returned +pub fn run_fail_raw(program: &str, args: &[&str]) -> Result { + let command = duct::cmd(program, args).stdout_capture().stderr_capture(); + let output = command.unchecked().run()?; + assert!(!output.status.success()); + Ok(output) +} + +//------------------------------------------ + pub fn mk_valid_xml(td: &mut TestDir) -> Result { let xml = td.mk_path("meta.xml"); let mut gen = SingleThinS::new(0, 1024, 2048, 2048); @@ -223,7 +140,9 @@ pub fn mk_valid_md(td: &mut TestDir) -> Result { write_xml(&xml, &mut gen)?; let _file = file_utils::create_sized_file(&md, 4096 * 4096); - thin_restore!("-i", xml, "-o", &md).run()?; + let args = ["-i", xml.to_str().unwrap(), "-o", md.to_str().unwrap()]; + run_ok(THIN_RESTORE, &args)?; + Ok(md) } @@ -234,13 +153,6 @@ pub fn mk_zeroed_md(td: &mut TestDir) -> Result { Ok(md) } -pub fn accepts_flag(flag: &str) -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_valid_md(&mut td)?; - thin_check!(flag, &md).run()?; - Ok(()) -} - pub fn superblock_all_zeroes(path: &PathBuf) -> Result { let mut input = OpenOptions::new().read(true).write(false).open(path)?; let mut buf = vec![0; 4096]; @@ -266,50 +178,58 @@ pub fn damage_superblock(path: &PathBuf) -> Result<()> { // FIXME: replace mk_valid_md with this? pub fn prep_metadata(td: &mut TestDir) -> Result { let md = mk_zeroed_md(td)?; - thin_generate_metadata!("-o", &md, "--format", "--nr-data-blocks", "102400").run()?; + let args = [ + "-o", + md.to_str().unwrap(), + "--format", + "--nr-data-blocks", + "102400", + ]; + run_ok(THIN_GENERATE_METADATA, &args)?; // Create a 2GB device - thin_generate_metadata!("-o", &md, "--create-thin", "1").run()?; - thin_generate_mappings!( + let args = ["-o", md.to_str().unwrap(), "--create-thin", "1"]; + run_ok(THIN_GENERATE_METADATA, &args)?; + let args = [ "-o", - &md, + md.to_str().unwrap(), "--dev-id", "1", "--size", - format!("{}", 1024 * 1024 * 2), + "2097152", "--rw=randwrite", - "--seq-nr=16" - ) - .run()?; + "--seq-nr=16", + ]; + run_ok(THIN_GENERATE_MAPPINGS, &args)?; // Take a few snapshots. let mut snap_id = 2; for _i in 0..10 { // take a snapshot - thin_generate_metadata!( + let args = [ "-o", - &md, + md.to_str().unwrap(), "--create-snap", - format!("{}", snap_id), + &snap_id.to_string(), "--origin", - "1" - ) - .run()?; + "1", + ]; + run_ok(THIN_GENERATE_METADATA, &args)?; // partially overwrite the origin (64MB) - thin_generate_mappings!( + let args = [ "-o", - &md, + md.to_str().unwrap(), "--dev-id", - format!("{}", 1), + "1", "--size", - format!("{}", 1024 * 1024 * 2), + "2097152", "--io-size", - format!("{}", 64 * 1024 * 2), + "131072", "--rw=randwrite", - "--seq-nr=16" - ) - .run()?; + "--seq-nr=16", + ]; + run_ok(THIN_GENERATE_MAPPINGS, &args)?; snap_id += 1; } @@ -317,7 +237,8 @@ pub fn prep_metadata(td: &mut TestDir) -> Result { } pub fn set_needs_check(md: &PathBuf) -> Result<()> { - thin_generate_metadata!("-o", &md, "--set-needs-check").run()?; + let args = ["-o", md.to_str().unwrap(), "--set-needs-check"]; + run_ok(THIN_GENERATE_METADATA, &args)?; Ok(()) } @@ -327,21 +248,19 @@ pub fn generate_metadata_leaks( expected: u32, actual: u32, ) -> Result<()> { - let output = thin_generate_damage!( + let args = [ "-o", - &md, + md.to_str().unwrap(), "--create-metadata-leaks", "--nr-blocks", - format!("{}", nr_blocks), + &nr_blocks.to_string(), "--expected", - format!("{}", expected), + &expected.to_string(), "--actual", - format!("{}", actual) - ) - .unchecked() - .run()?; + &actual.to_string(), + ]; + run_ok(THIN_GENERATE_DAMAGE, &args)?; - assert!(output.status.success()); Ok(()) } @@ -354,7 +273,7 @@ pub fn get_needs_check(md: &PathBuf) -> Result { } pub fn md5(md: &PathBuf) -> Result { - let output = cmd!("md5sum", "-b", &md).stdout_capture().run()?; + let output = duct::cmd!("md5sum", "-b", &md).stdout_capture().run()?; let csum = std::str::from_utf8(&output.stdout[0..])?.to_string(); let csum = csum.split_ascii_whitespace().next().unwrap().to_string(); Ok(csum) @@ -371,3 +290,5 @@ where assert_eq!(csum, md5(p)?); Ok(()) } + +//------------------------------------------ diff --git a/tests/common/output_option.rs b/tests/common/output_option.rs new file mode 100644 index 0000000..3ec3a97 --- /dev/null +++ b/tests/common/output_option.rs @@ -0,0 +1,143 @@ +use crate::common::*; + +//----------------------------------------- +// test invalid arguments + +pub fn test_missing_output_option(program: &str, mk_input: F) -> Result<()> +where + F: Fn(&mut TestDir) -> Result, +{ + let mut td = TestDir::new()?; + let input = mk_input(&mut td)?; + let stderr = run_fail(program, &["-i", input.to_str().unwrap()])?; + assert!(stderr.contains(msg::MISSING_OUTPUT_ARG)); + Ok(()) +} + +#[macro_export] +macro_rules! test_missing_output_option { + ($program: ident, $mk_input: ident) => { + #[test] + fn missing_output_option() -> Result<()> { + test_missing_output_option($program, $mk_input) + } + }; +} + +pub fn test_output_file_not_found(program: &str, mk_input: F) -> Result<()> +where + F: Fn(&mut TestDir) -> Result, +{ + let mut td = TestDir::new()?; + let input = mk_input(&mut td)?; + let stderr = run_fail( + program, + &["-i", input.to_str().unwrap(), "-o", "no-such-file"], + )?; + assert!(stderr.contains(msg::FILE_NOT_FOUND)); + Ok(()) +} + +#[macro_export] +macro_rules! test_output_file_not_found { + ($program: ident, $mk_input: ident) => { + #[test] + fn output_file_not_found() -> Result<()> { + test_output_file_not_found($program, $mk_input) + } + }; +} + +pub fn test_output_cannot_be_a_directory(program: &str, mk_input: F) -> Result<()> +where + F: Fn(&mut TestDir) -> Result, +{ + let mut td = TestDir::new()?; + let input = mk_input(&mut td)?; + let stderr = run_fail(program, &["-i", input.to_str().unwrap(), "-o", "/tmp"])?; + assert!(stderr.contains(msg::FILE_NOT_FOUND)); + Ok(()) +} + +#[macro_export] +macro_rules! test_output_cannot_be_a_directory { + ($program: ident, $mk_input: ident) => { + #[test] + fn output_cannot_be_a_directory() -> Result<()> { + test_output_cannot_be_a_directory($program, $mk_input) + } + }; +} + +pub fn test_unwritable_output_file(program: &str, mk_input: F) -> Result<()> +where + F: Fn(&mut TestDir) -> Result, +{ + let mut td = TestDir::new()?; + let input = mk_input(&mut td)?; + + let output = td.mk_path("meta.bin"); + let _file = file_utils::create_sized_file(&output, 4096); + duct::cmd!("chmod", "-w", &output).run()?; + + let stderr = run_fail( + program, + &[ + "-i", + input.to_str().unwrap(), + "-o", + output.to_str().unwrap(), + ], + )?; + assert!(stderr.contains("Permission denied")); + Ok(()) +} + +#[macro_export] +macro_rules! test_unwritable_output_file { + ($program: ident, $mk_input: ident) => { + #[test] + fn unwritable_output_file() -> Result<()> { + test_unwritable_output_file($program, $mk_input) + } + }; +} + +//---------------------------------------- +// test invalid content + +// currently thin/cache_restore only +pub fn test_tiny_output_file(program: &str, mk_input: F) -> Result<()> +where + F: Fn(&mut TestDir) -> Result, +{ + let mut td = TestDir::new()?; + let input = mk_input(&mut td)?; + + let output = td.mk_path("meta.bin"); + let _file = file_utils::create_sized_file(&output, 4096); + + let stderr = run_fail( + program, + &[ + "-i", + input.to_str().unwrap(), + "-o", + output.to_str().unwrap(), + ], + )?; + assert!(stderr.contains("Output file too small")); + Ok(()) +} + +#[macro_export] +macro_rules! test_tiny_output_file { + ($program: ident, $mk_input: ident) => { + #[test] + fn tiny_output_file() -> Result<()> { + test_tiny_output_file($program, $mk_input) + } + }; +} + +//----------------------------------------- diff --git a/tests/thin_check.rs b/tests/thin_check.rs index b4a5fb1..159b447 100644 --- a/tests/thin_check.rs +++ b/tests/thin_check.rs @@ -1,49 +1,50 @@ use anyhow::Result; -use thinp::file_utils; -use thinp::version::tools_version; mod common; +use common::common_args::*; +use common::input_arg::*; use common::test_dir::*; -use common::thin_xml_generator::{write_xml, FragmentedS}; use common::*; //------------------------------------------ -#[test] -fn accepts_v() -> Result<()> { - let stdout = thin_check!("-V").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +const USAGE: &str = "Usage: thin_check [options] {device|file}\n\ + Options:\n \ + {-q|--quiet}\n \ + {-h|--help}\n \ + {-V|--version}\n \ + {-m|--metadata-snap}\n \ + {--auto-repair}\n \ + {--override-mapping-root}\n \ + {--clear-needs-check-flag}\n \ + {--ignore-non-fatal-errors}\n \ + {--skip-mappings}\n \ + {--super-block-only}"; -#[test] -fn accepts_version() -> Result<()> { - let stdout = thin_check!("--version").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +//------------------------------------------ -const USAGE: &str = "Usage: thin_check [options] {device|file}\nOptions:\n {-q|--quiet}\n {-h|--help}\n {-V|--version}\n {-m|--metadata-snap}\n {--auto-repair}\n {--override-mapping-root}\n {--clear-needs-check-flag}\n {--ignore-non-fatal-errors}\n {--skip-mappings}\n {--super-block-only}"; +test_accepts_help!(THIN_CHECK, USAGE); +test_accepts_version!(THIN_CHECK); +test_rejects_bad_option!(THIN_CHECK); -#[test] -fn accepts_h() -> Result<()> { - let stdout = thin_check!("-h").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} +test_missing_input_arg!(THIN_CHECK); +test_input_file_not_found!(THIN_CHECK, ARG); +test_input_cannot_be_a_directory!(THIN_CHECK, ARG); +test_unreadable_input_file!(THIN_CHECK, ARG); -#[test] -fn accepts_help() -> Result<()> { - let stdout = thin_check!("--help").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} +test_help_message_for_tiny_input_file!(THIN_CHECK, ARG); +test_spot_xml_data!(THIN_CHECK, "thin_check", ARG); +test_corrupted_input_data!(THIN_CHECK, ARG); -#[test] -fn rejects_bad_option() -> Result<()> { - let stderr = run_fail(thin_check!("--hedgehogs-only"))?; - assert!(stderr.contains("unrecognized option \'--hedgehogs-only\'")); +//------------------------------------------ +// test exclusive flags + +fn accepts_flag(flag: &str) -> Result<()> { + let mut td = TestDir::new()?; + let md = mk_valid_md(&mut td)?; + let md_path = md.to_str().unwrap(); + run_ok(THIN_CHECK, &[flag, md_path])?; Ok(()) } @@ -67,86 +68,74 @@ fn accepts_clear_needs_check_flag() -> Result<()> { accepts_flag("--clear-needs-check-flag") } +#[test] +fn accepts_auto_repair() -> Result<()> { + accepts_flag("--auto-repair") +} + +//------------------------------------------ +// test the --quiet flag + #[test] fn accepts_quiet() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; + let md_path = md.to_str().unwrap(); - let output = thin_check!("--quiet", &md).run()?; - assert!(output.status.success()); + let output = run_ok_raw(THIN_CHECK, &["--quiet", md_path])?; assert_eq!(output.stdout.len(), 0); assert_eq!(output.stderr.len(), 0); Ok(()) } -#[test] -fn accepts_auto_repair() -> Result<()> { - accepts_flag("--auto-repair") -} +//------------------------------------------ +// test superblock-block-only #[test] fn detects_corrupt_superblock_with_superblock_only() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; - let output = thin_check!("--super-block-only", &md).unchecked().run()?; - assert!(!output.status.success()); + let md_path = md.to_str().unwrap(); + let _stderr = run_fail(THIN_CHECK, &["--super-block-only", md_path])?; Ok(()) } -#[test] -fn prints_help_message_for_tiny_metadata() -> Result<()> { - let mut td = TestDir::new()?; - let md = td.mk_path("meta.bin"); - let _file = file_utils::create_sized_file(&md, 1024); - let stderr = run_fail(thin_check!(&md))?; - assert!(stderr.contains("Metadata device/file too small. Is this binary metadata?")); - Ok(()) -} - -#[test] -fn spot_xml_data() -> Result<()> { - let mut td = TestDir::new()?; - let xml = td.mk_path("meta.xml"); - - let mut gen = FragmentedS::new(4, 10240); - write_xml(&xml, &mut gen)?; - - let stderr = run_fail(thin_check!(&xml))?; - eprintln!("{}", stderr); - assert!( - stderr.contains("This looks like XML. thin_check only checks the binary metadata format.") - ); - Ok(()) -} +//------------------------------------------ +// test info outputs #[test] fn prints_info_fields() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let stdout = thin_check!(&md).read()?; + let md_path = md.to_str().unwrap(); + let stdout = run_ok(THIN_CHECK, &[md_path])?; assert!(stdout.contains("TRANSACTION_ID=")); assert!(stdout.contains("METADATA_FREE_BLOCKS=")); Ok(()) } +//------------------------------------------ +// test compatibility between options + #[test] fn auto_repair_incompatible_opts() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - run_fail(thin_check!("--auto-repair", "-m", &md))?; - run_fail(thin_check!( - "--auto-repair", - "--override-mapping-root", - "123", - &md - ))?; - run_fail(thin_check!("--auto-repair", "--super-block-only", &md))?; - run_fail(thin_check!("--auto-repair", "--skip-mappings", &md))?; - run_fail(thin_check!( - "--auto-repair", - "--ignore-non-fatal-errors", - &md - ))?; + let md_path = md.to_str().unwrap(); + run_fail(THIN_CHECK, &["--auto-repair", "-m", md_path])?; + run_fail( + THIN_CHECK, + &["--auto-repair", "--override-mapping-root", "123", md_path], + )?; + run_fail( + THIN_CHECK, + &["--auto-repair", "--super-block-only", md_path], + )?; + run_fail(THIN_CHECK, &["--auto-repair", "--skip-mappings", md_path])?; + run_fail( + THIN_CHECK, + &["--auto-repair", "--ignore-non-fatal-errors", md_path], + )?; Ok(()) } @@ -154,36 +143,45 @@ fn auto_repair_incompatible_opts() -> Result<()> { fn clear_needs_check_incompatible_opts() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - run_fail(thin_check!("--clear-needs-check-flag", "-m", &md))?; - run_fail(thin_check!( - "--clear-needs-check-flag", - "--override-mapping-root", - "123", - &md - ))?; - run_fail(thin_check!( - "--clear-needs-check-flag", - "--super-block-only", - &md - ))?; - run_fail(thin_check!( - "--clear-needs-check-flag", - "--ignore-non-fatal-errors", - &md - ))?; + let md_path = md.to_str().unwrap(); + run_fail(THIN_CHECK, &["--clear-needs-check-flag", "-m", md_path])?; + run_fail( + THIN_CHECK, + &[ + "--clear-needs-check-flag", + "--override-mapping-root", + "123", + md_path, + ], + )?; + run_fail( + THIN_CHECK, + &["--clear-needs-check-flag", "--super-block-only", md_path], + )?; + run_fail( + THIN_CHECK, + &[ + "--clear-needs-check-flag", + "--ignore-non-fatal-errors", + md_path, + ], + )?; Ok(()) } //------------------------------------------ +// test clear-needs-check #[test] fn clear_needs_check() -> Result<()> { let mut td = TestDir::new()?; let md = prep_metadata(&mut td)?; + let md_path = md.to_str().unwrap(); + set_needs_check(&md)?; assert!(get_needs_check(&md)?); - thin_check!("--clear-needs-check-flag", &md).run()?; + run_ok(THIN_CHECK, &["--clear-needs-check-flag", md_path])?; assert!(!get_needs_check(&md)?); Ok(()) } @@ -192,9 +190,11 @@ fn clear_needs_check() -> Result<()> { fn no_clear_needs_check_if_error() -> Result<()> { let mut td = TestDir::new()?; let md = prep_metadata(&mut td)?; + let md_path = md.to_str().unwrap(); + set_needs_check(&md)?; generate_metadata_leaks(&md, 1, 0, 1)?; - run_fail(thin_check!("--clear-needs-check-flag", &md))?; + run_fail(THIN_CHECK, &["--clear-needs-check-flag", md_path])?; assert!(get_needs_check(&md)?); Ok(()) } @@ -203,22 +203,31 @@ fn no_clear_needs_check_if_error() -> Result<()> { fn clear_needs_check_if_skip_mappings() -> Result<()> { let mut td = TestDir::new()?; let md = prep_metadata(&mut td)?; + let md_path = md.to_str().unwrap(); + set_needs_check(&md)?; generate_metadata_leaks(&md, 1, 0, 1)?; assert!(get_needs_check(&md)?); - thin_check!("--clear-needs-check-flag", "--skip-mappings", &md).run()?; + run_ok( + THIN_CHECK, + &["--clear-needs-check-flag", "--skip-mappings", md_path], + )?; assert!(!get_needs_check(&md)?); Ok(()) } +//------------------------------------------ +// test ignore-non-fatal-errors + #[test] fn metadata_leaks_are_non_fatal() -> Result<()> { let mut td = TestDir::new()?; let md = prep_metadata(&mut td)?; + let md_path = md.to_str().unwrap(); generate_metadata_leaks(&md, 1, 0, 1)?; - run_fail(thin_check!(&md))?; - thin_check!("--ignore-non-fatal-errors", &md).run()?; + run_fail(THIN_CHECK, &[md_path])?; + run_ok(THIN_CHECK, &["--ignore-non-fatal-errors", md_path])?; Ok(()) } @@ -226,28 +235,34 @@ fn metadata_leaks_are_non_fatal() -> Result<()> { fn fatal_errors_cant_be_ignored() -> Result<()> { let mut td = TestDir::new()?; let md = prep_metadata(&mut td)?; + let md_path = md.to_str().unwrap(); + generate_metadata_leaks(&md, 1, 1, 0)?; ensure_untouched(&md, || { - run_fail(thin_check!("--ignore-non-fatal-errors", &md))?; + run_fail(THIN_CHECK, &["--ignore-non-fatal-errors", md_path])?; Ok(()) }) } +//------------------------------------------ +// test auto-repair + #[test] fn auto_repair() -> Result<()> { let mut td = TestDir::new()?; let md = prep_metadata(&mut td)?; + let md_path = md.to_str().unwrap(); // auto-repair should have no effect on good metadata. ensure_untouched(&md, || { - thin_check!("--auto-repair", &md).run()?; + run_ok(THIN_CHECK, &["--auto-repair", md_path])?; Ok(()) })?; generate_metadata_leaks(&md, 16, 0, 1)?; - run_fail(thin_check!(&md))?; - thin_check!("--auto-repair", &md).run()?; - thin_check!(&md).run()?; + run_fail(THIN_CHECK, &[md_path])?; + run_ok(THIN_CHECK, &["--auto-repair", md_path])?; + run_ok(THIN_CHECK, &[md_path])?; Ok(()) } @@ -255,10 +270,11 @@ fn auto_repair() -> Result<()> { fn auto_repair_has_limits() -> Result<()> { let mut td = TestDir::new()?; let md = prep_metadata(&mut td)?; - generate_metadata_leaks(&md, 16, 1, 0)?; + let md_path = md.to_str().unwrap(); + generate_metadata_leaks(&md, 16, 1, 0)?; ensure_untouched(&md, || { - run_fail(thin_check!("--auto-repair", &md))?; + run_fail(THIN_CHECK, &["--auto-repair", md_path])?; Ok(()) })?; Ok(()) @@ -268,8 +284,10 @@ fn auto_repair_has_limits() -> Result<()> { fn auto_repair_clears_needs_check() -> Result<()> { let mut td = TestDir::new()?; let md = prep_metadata(&mut td)?; + let md_path = md.to_str().unwrap(); + set_needs_check(&md)?; - thin_check!("--auto-repair", &md).run()?; + run_ok(THIN_CHECK, &["--auto-repair", md_path])?; assert!(!get_needs_check(&md)?); Ok(()) } diff --git a/tests/thin_delta.rs b/tests/thin_delta.rs index 2d570de..cdd203d 100644 --- a/tests/thin_delta.rs +++ b/tests/thin_delta.rs @@ -1,54 +1,35 @@ use anyhow::Result; -use thinp::version::tools_version; mod common; + +use common::common_args::*; use common::test_dir::*; use common::*; //------------------------------------------ -#[test] -fn accepts_v() -> Result<()> { - let stdout = thin_delta!("-V").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +const USAGE: &str = "Usage: thin_delta [options] \n\ + Options:\n \ + {--thin1, --snap1, --root1}\n \ + {--thin2, --snap2, --root2}\n \ + {-m, --metadata-snap} [block#]\n \ + {--verbose}\n \ + {-h|--help}\n \ + {-V|--version}"; -#[test] -fn accepts_version() -> Result<()> { - let stdout = thin_delta!("--version").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +//------------------------------------------ -const USAGE: &str = "Usage: thin_delta [options] \nOptions:\n {--thin1, --snap1, --root1}\n {--thin2, --snap2, --root2}\n {-m, --metadata-snap} [block#]\n {--verbose}\n {-h|--help}\n {-V|--version}"; +test_accepts_help!(THIN_DELTA, USAGE); +test_accepts_version!(THIN_DELTA); +test_rejects_bad_option!(THIN_DELTA); -#[test] -fn accepts_h() -> Result<()> { - let stdout = thin_delta!("-h").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} - -#[test] -fn accepts_help() -> Result<()> { - let stdout = thin_delta!("--help").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} - -#[test] -fn rejects_bad_option() -> Result<()> { - let stderr = run_fail(thin_delta!("--hedgehogs-only"))?; - assert!(stderr.contains("unrecognized option \'--hedgehogs-only\'")); - Ok(()) -} +//------------------------------------------ #[test] fn snap1_unspecified() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let stderr = run_fail(thin_delta!("--snap2", "45", &md))?; + let stderr = run_fail(THIN_DELTA, &["--snap2", "45", md.to_str().unwrap()])?; assert!(stderr.contains("--snap1 or --root1 not specified")); Ok(()) } @@ -57,15 +38,17 @@ fn snap1_unspecified() -> Result<()> { fn snap2_unspecified() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let stderr = run_fail(thin_delta!("--snap1", "45", &md))?; + let stderr = run_fail(THIN_DELTA, &["--snap1", "45", md.to_str().unwrap()])?; assert!(stderr.contains("--snap2 or --root2 not specified")); Ok(()) } #[test] fn dev_unspecified() -> Result<()> { - let stderr = run_fail(thin_delta!("--snap1", "45", "--snap2", "46"))?; + let stderr = run_fail(THIN_DELTA, &["--snap1", "45", "--snap2", "46"])?; // TODO: replace with msg::MISSING_INPUT_ARG once the rust version is ready assert!(stderr.contains("No input file provided")); Ok(()) } + +//------------------------------------------ diff --git a/tests/thin_dump.rs b/tests/thin_dump.rs index 90b4298..1cb6819 100644 --- a/tests/thin_dump.rs +++ b/tests/thin_dump.rs @@ -2,29 +2,48 @@ use anyhow::Result; use std::fs::OpenOptions; use std::io::Write; use std::str::from_utf8; -use thinp::file_utils; mod common; + +use common::common_args::*; +use common::input_arg::*; use common::test_dir::*; use common::*; //------------------------------------------ -#[test] -fn small_input_file() -> Result<()> { - let mut td = TestDir::new()?; - let md = td.mk_path("meta.bin"); - file_utils::create_sized_file(&md, 512)?; - let _stderr = run_fail(thin_dump!(&md))?; - Ok(()) -} +const USAGE: &str = "Usage: thin_dump [options] {device|file}\n\ + Options:\n \ + {-h|--help}\n \ + {-f|--format} {xml|human_readable|custom}\n \ + {-r|--repair}\n \ + {-m|--metadata-snap} [block#]\n \ + {-o }\n \ + {--dev-id} \n \ + {--skip-mappings}\n \ + {-V|--version}"; + +//------------------------------------------ + +test_accepts_help!(THIN_DUMP, USAGE); +test_accepts_version!(THIN_DUMP); +test_rejects_bad_option!(THIN_DUMP); + +test_missing_input_arg!(THIN_DUMP); +test_input_file_not_found!(THIN_DUMP, ARG); +test_input_cannot_be_a_directory!(THIN_DUMP, ARG); +test_unreadable_input_file!(THIN_DUMP, ARG); + +//------------------------------------------ +// test dump & restore cycle #[test] fn dump_restore_cycle() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let output = thin_dump!(&md).run()?; + let md_path = md.to_str().unwrap(); + let output = run_ok_raw(THIN_DUMP, &[md_path])?; let xml = td.mk_path("meta.xml"); let mut file = OpenOptions::new() @@ -36,29 +55,40 @@ fn dump_restore_cycle() -> Result<()> { drop(file); let md2 = mk_zeroed_md(&mut td)?; - thin_restore!("-i", &xml, "-o", &md2).run()?; + let md2_path = md2.to_str().unwrap(); + let xml_path = xml.to_str().unwrap(); + run_ok(THIN_RESTORE, &["-i", xml_path, "-o", md2_path])?; - let output2 = thin_dump!(&md2).run()?; + let output2 = run_ok_raw(THIN_DUMP, &[md2_path])?; assert_eq!(output.stdout, output2.stdout); Ok(()) } +//------------------------------------------ +// test no stderr with a normal dump + #[test] fn no_stderr() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let output = thin_dump!(&md).run()?; + let md_path = md.to_str().unwrap(); + let output = run_ok_raw(THIN_DUMP, &[md_path])?; assert_eq!(output.stderr.len(), 0); Ok(()) } +//------------------------------------------ +// test superblock overriding & repair +// TODO: share with thin_repair + fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let output = thin_dump!(&md, flag, value).run()?; + let md_path = md.to_str().unwrap(); + let output = run_ok_raw(THIN_DUMP, &[md_path, flag, value])?; assert_eq!(output.stderr.len(), 0); assert!(from_utf8(&output.stdout[0..])?.contains(pattern)); @@ -80,44 +110,56 @@ fn override_nr_data_blocks() -> Result<()> { override_something("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"") } +// FIXME: duplicate with superblock_succeeds in thin_repair.rs #[test] fn repair_superblock() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let before = thin_dump!( - "--transaction-id=5", - "--data-block-size=128", - "--nr-data-blocks=4096000", - &md - ) - .run()?; + let before = run_ok_raw( + THIN_DUMP, + &[ + "--transaction-id=5", + "--data-block-size=128", + "--nr-data-blocks=4096000", + md.to_str().unwrap(), + ], + )?; damage_superblock(&md)?; - let after = thin_dump!( - "--repair", - "--transaction-id=5", - "--data-block-size=128", - "--nr-data-blocks=4096000", - &md - ) - .run()?; + let after = run_ok_raw( + THIN_DUMP, + &[ + "--repair", + "--transaction-id=5", + "--data-block-size=128", + "--nr-data-blocks=4096000", + md.to_str().unwrap(), + ], + )?; assert_eq!(after.stderr.len(), 0); assert_eq!(before.stdout, after.stdout); Ok(()) } +//------------------------------------------ +// test compatibility between options +// TODO: share with thin_repair + #[test] fn missing_transaction_id() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; damage_superblock(&md)?; - let stderr = run_fail(thin_dump!( - "--repair", - "--data-block-size=128", - "--nr-data-blocks=4096000", - &md - ))?; + let stderr = run_fail( + THIN_DUMP, + &[ + "--repair", + "--data-block-size=128", + "--nr-data-blocks=4096000", + md.to_str().unwrap(), + ], + )?; assert!(stderr.contains("transaction id")); Ok(()) } @@ -127,12 +169,15 @@ fn missing_data_block_size() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; damage_superblock(&md)?; - let stderr = run_fail(thin_dump!( - "--repair", - "--transaction-id=5", - "--nr-data-blocks=4096000", - &md - ))?; + let stderr = run_fail( + THIN_DUMP, + &[ + "--repair", + "--transaction-id=5", + "--nr-data-blocks=4096000", + md.to_str().unwrap(), + ], + )?; assert!(stderr.contains("data block size")); Ok(()) } @@ -142,12 +187,17 @@ fn missing_nr_data_blocks() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; damage_superblock(&md)?; - let stderr = run_fail(thin_dump!( - "--repair", - "--transaction-id=5", - "--data-block-size=128", - &md - ))?; + let stderr = run_fail( + THIN_DUMP, + &[ + "--repair", + "--transaction-id=5", + "--data-block-size=128", + md.to_str().unwrap(), + ], + )?; assert!(stderr.contains("nr data blocks")); Ok(()) } + +//------------------------------------------ diff --git a/tests/thin_metadata_pack.rs b/tests/thin_metadata_pack.rs index 84e3187..92917df 100644 --- a/tests/thin_metadata_pack.rs +++ b/tests/thin_metadata_pack.rs @@ -1,77 +1,39 @@ use anyhow::Result; -use thinp::version::tools_version; mod common; -use common::test_dir::*; + +use common::common_args::*; +use common::input_arg::*; +use common::output_option::*; use common::*; //------------------------------------------ -#[test] -fn accepts_v() -> Result<()> { - let stdout = thin_metadata_pack!("-V").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} - -#[test] -fn accepts_version() -> Result<()> { - let stdout = thin_metadata_pack!("--version").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} - -const USAGE: &str = "thin_metadata_pack 0.9.0\nProduces a compressed file of thin metadata. Only packs metadata blocks that are actually used.\n\nUSAGE:\n thin_metadata_pack -i -o \n\nFLAGS:\n -h, --help Prints help information\n -V, --version Prints version information\n\nOPTIONS:\n -i Specify thinp metadata binary device/file\n -o Specify packed output file"; - -#[test] -fn accepts_h() -> Result<()> { - let stdout = thin_metadata_pack!("-h").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} - -#[test] -fn accepts_help() -> Result<()> { - let stdout = thin_metadata_pack!("--help").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} - -#[test] -fn rejects_bad_option() -> Result<()> { - let stderr = run_fail(thin_metadata_pack!("--hedgehogs-only"))?; - assert!(stderr.contains("Found argument \'--hedgehogs-only\'")); - Ok(()) -} - -#[test] -fn missing_input_file() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_metadata_pack!("-o", &md))?; - assert!( - stderr.contains("error: The following required arguments were not provided:\n -i ") - ); - Ok(()) -} - -#[test] -fn no_such_input_file() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_metadata_pack!("-i", "no-such-file", "-o", &md))?; - assert!(stderr.contains("Couldn't find input file")); - Ok(()) -} - -#[test] -fn missing_output_file() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_metadata_pack!("-i", &md))?; - assert!(stderr - .contains("error: The following required arguments were not provided:\n -o ")); - Ok(()) -} +const USAGE: &str = concat!( + "thin_metadata_pack ", + include_str!("../VERSION"), + "Produces a compressed file of thin metadata. Only packs metadata blocks that are actually used.\n\ + \n\ + USAGE:\n \ + thin_metadata_pack -i -o \n\ + \n\ + FLAGS:\n \ + -h, --help Prints help information\n \ + -V, --version Prints version information\n\ + \n\ + OPTIONS:\n \ + -i Specify thinp metadata binary device/file\n \ + -o Specify packed output file" +); + +//------------------------------------------ + +test_accepts_help!(THIN_METADATA_PACK, USAGE); +test_accepts_version!(THIN_METADATA_PACK); +test_rejects_bad_option!(THIN_METADATA_PACK); + +test_missing_input_option!(THIN_METADATA_PACK); +test_missing_output_option!(THIN_METADATA_PACK, mk_valid_md); +test_input_file_not_found!(THIN_METADATA_PACK, OPTION); //------------------------------------------ diff --git a/tests/thin_metadata_unpack.rs b/tests/thin_metadata_unpack.rs index 7a5d5ee..c2fc068 100644 --- a/tests/thin_metadata_unpack.rs +++ b/tests/thin_metadata_unpack.rs @@ -1,98 +1,64 @@ use anyhow::Result; -use thinp::version::tools_version; mod common; + +use common::common_args::*; +use common::input_arg::*; +use common::output_option::*; use common::test_dir::*; use common::*; //------------------------------------------ -#[test] -fn accepts_v() -> Result<()> { - let stdout = thin_metadata_unpack!("-V").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +const USAGE: &str = concat!( + "thin_metadata_unpack ", + include_str!("../VERSION"), + "Unpack a compressed file of thin metadata.\n\ + \n\ + USAGE:\n \ + thin_metadata_unpack -i -o \n\ + \n\ + FLAGS:\n \ + -h, --help Prints help information\n \ + -V, --version Prints version information\n\ + \n\ + OPTIONS:\n \ + -i Specify thinp metadata binary device/file\n \ + -o Specify packed output file" +); -#[test] -fn accepts_version() -> Result<()> { - let stdout = thin_metadata_unpack!("--version").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +//------------------------------------------ -const USAGE: &str = "thin_metadata_unpack 0.9.0\nUnpack a compressed file of thin metadata.\n\nUSAGE:\n thin_metadata_unpack -i -o \n\nFLAGS:\n -h, --help Prints help information\n -V, --version Prints version information\n\nOPTIONS:\n -i Specify thinp metadata binary device/file\n -o Specify packed output file"; +test_accepts_help!(THIN_METADATA_UNPACK, USAGE); +test_accepts_version!(THIN_METADATA_UNPACK); +test_rejects_bad_option!(THIN_METADATA_UNPACK); -#[test] -fn accepts_h() -> Result<()> { - let stdout = thin_metadata_unpack!("-h").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} +test_missing_input_option!(THIN_METADATA_PACK); +test_input_file_not_found!(THIN_METADATA_UNPACK, OPTION); +test_corrupted_input_data!(THIN_METADATA_UNPACK, OPTION); -#[test] -fn accepts_help() -> Result<()> { - let stdout = thin_metadata_unpack!("--help").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} +test_missing_output_option!(THIN_METADATA_UNPACK, mk_valid_md); -#[test] -fn rejects_bad_option() -> Result<()> { - let stderr = run_fail(thin_metadata_unpack!("--hedgehogs-only"))?; - assert!(stderr.contains("Found argument \'--hedgehogs-only\'")); - Ok(()) -} +//------------------------------------------ -#[test] -fn missing_input_file() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_metadata_unpack!("-o", &md))?; - assert!( - stderr.contains("error: The following required arguments were not provided:\n -i ") - ); - Ok(()) -} - -#[test] -fn no_such_input_file() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_metadata_unpack!("-i", "no-such-file", "-o", &md))?; - assert!(stderr.contains("Couldn't find input file")); - Ok(()) -} - -#[test] -fn missing_output_file() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_metadata_unpack!("-i", &md))?; - assert!(stderr - .contains("error: The following required arguments were not provided:\n -o ")); - Ok(()) -} - -#[test] -fn garbage_input_file() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_metadata_unpack!("-i", &md, "-o", "junk"))?; - assert!(stderr.contains("Not a pack file.")); - Ok(()) -} +// TODO: share with thin_restore/cache_restore/era_restore #[test] fn end_to_end() -> Result<()> { let mut td = TestDir::new()?; let md_in = mk_valid_md(&mut td)?; let md_out = mk_zeroed_md(&mut td)?; - thin_metadata_pack!("-i", &md_in, "-o", "meta.pack").run()?; - thin_metadata_unpack!("-i", "meta.pack", "-o", &md_out).run()?; + run_ok( + THIN_METADATA_PACK, + &["-i", md_in.to_str().unwrap(), "-o", "meta.pack"], + )?; + run_ok( + THIN_METADATA_UNPACK, + &["-i", "meta.pack", "-o", md_out.to_str().unwrap()], + )?; - let dump1 = thin_dump!(&md_in).read()?; - let dump2 = thin_dump!(&md_out).read()?; + let dump1 = run_ok(THIN_DUMP, &[md_in.to_str().unwrap()])?; + let dump2 = run_ok(THIN_DUMP, &[md_out.to_str().unwrap()])?; assert_eq!(dump1, dump2); Ok(()) } diff --git a/tests/thin_repair.rs b/tests/thin_repair.rs index 3dcc506..1884e6f 100644 --- a/tests/thin_repair.rs +++ b/tests/thin_repair.rs @@ -1,91 +1,67 @@ use anyhow::Result; -use std::str::from_utf8; -use thinp::version::tools_version; mod common; + +use common::common_args::*; +use common::input_arg::*; +use common::output_option::*; use common::test_dir::*; use common::*; //------------------------------------------ -#[test] -fn accepts_v() -> Result<()> { - let stdout = thin_repair!("-V").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +const USAGE: &str = "Usage: thin_repair [options] {device|file}\n\ + Options:\n \ + {-h|--help}\n \ + {-i|--input} \n \ + {-o|--output} \n \ + {--transaction-id} \n \ + {--data-block-size} \n \ + {--nr-data-blocks} \n \ + {-V|--version}"; -#[test] -fn accepts_version() -> Result<()> { - let stdout = thin_repair!("--version").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +//----------------------------------------- -const USAGE: &str = "Usage: thin_repair [options] {device|file}\nOptions:\n {-h|--help}\n {-i|--input} \n {-o|--output} \n {--transaction-id} \n {--data-block-size} \n {--nr-data-blocks} \n {-V|--version}"; +test_accepts_help!(THIN_REPAIR, USAGE); +test_accepts_version!(THIN_REPAIR); +test_rejects_bad_option!(THIN_REPAIR); -#[test] -fn accepts_h() -> Result<()> { - let stdout = thin_repair!("-h").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} +test_input_file_not_found!(THIN_REPAIR, OPTION); +test_corrupted_input_data!(THIN_REPAIR, OPTION); -#[test] -fn accepts_help() -> Result<()> { - let stdout = thin_repair!("--help").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} +test_missing_output_option!(THIN_REPAIR, mk_valid_md); + +//----------------------------------------- +// test output to a small file + +// TODO: share with thin_restore #[test] fn dont_repair_xml() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; let xml = mk_valid_xml(&mut td)?; - run_fail(thin_repair!("-i", &xml, "-o", &md))?; + let xml_path = xml.to_str().unwrap(); + let md_path = md.to_str().unwrap(); + run_fail(THIN_REPAIR, &["-i", xml_path, "-o", md_path])?; Ok(()) } -#[test] -fn input_file_not_found() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_repair!("-i", "no-such-file", "-o", &md))?; - assert!(superblock_all_zeroes(&md)?); - // TODO: replace with msg::FILE_NOT_FOUND once the rust version is ready - assert!(stderr.contains("No such file or directory")); - Ok(()) -} +//----------------------------------------- -#[test] -fn garbage_input_file() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let md2 = mk_zeroed_md(&mut td)?; - run_fail(thin_repair!("-i", &md, "-o", &md2))?; - assert!(superblock_all_zeroes(&md2)?); - Ok(()) -} - -#[test] -fn missing_output_arg() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_valid_md(&mut td)?; - let stderr = run_fail(thin_repair!("-i", &md))?; - // TODO: replace with msg::MISSING_OUTPUT_ARG once the rust version is ready - assert!(stderr.contains("No output file provided.")); - Ok(()) -} +// TODO: share with thin_dump fn override_thing(flag: &str, val: &str, pattern: &str) -> Result<()> { let mut td = TestDir::new()?; let md1 = mk_valid_md(&mut td)?; let md2 = mk_zeroed_md(&mut td)?; - let output = thin_repair!(flag, val, "-i", &md1, "-o", &md2).run()?; + let md1_path = md1.to_str().unwrap(); + let md2_path = md2.to_str().unwrap(); + let output = run_ok_raw(THIN_REPAIR, &[flag, val, "-i", md1_path, "-o", md2_path])?; assert_eq!(output.stderr.len(), 0); - let output = thin_dump!(&md2).run()?; - assert!(from_utf8(&output.stdout[0..])?.contains(pattern)); + let md2_path = md2.to_str().unwrap(); + let output = run_ok(THIN_DUMP, &[md2_path])?; + assert!(output.contains(pattern)); Ok(()) } @@ -104,42 +80,63 @@ fn override_nr_data_blocks() -> Result<()> { override_thing("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"") } +// FIXME: that's repair_superblock in thin_dump.rs #[test] fn superblock_succeeds() -> Result<()> { let mut td = TestDir::new()?; let md1 = mk_valid_md(&mut td)?; - let original = thin_dump!( - "--transaction-id=5", - "--data-block-size=128", - "--nr-data-blocks=4096000", - &md1 - ) - .run()?; + let md1_path = md1.to_str().unwrap(); + let original = run_ok_raw( + THIN_DUMP, + &[ + "--transaction-id=5", + "--data-block-size=128", + "--nr-data-blocks=4096000", + md1_path, + ], + )?; assert_eq!(original.stderr.len(), 0); damage_superblock(&md1)?; let md2 = mk_zeroed_md(&mut td)?; - thin_repair!( - "--transaction-id=5", - "--data-block-size=128", - "--nr-data-blocks=4096000", - "-i", - &md1, - "-o", - &md2 - ) - .run()?; - let repaired = thin_dump!(&md2).run()?; + let md2_path = md2.to_str().unwrap(); + run_ok( + THIN_REPAIR, + &[ + "--transaction-id=5", + "--data-block-size=128", + "--nr-data-blocks=4096000", + "-i", + md1_path, + "-o", + md2_path, + ], + )?; + let repaired = run_ok_raw(THIN_DUMP, &[md2_path])?; assert_eq!(repaired.stderr.len(), 0); assert_eq!(original.stdout, repaired.stdout); Ok(()) } +//----------------------------------------- + +// TODO: share with thin_dump + fn missing_thing(flag1: &str, flag2: &str, pattern: &str) -> Result<()> { let mut td = TestDir::new()?; let md1 = mk_valid_md(&mut td)?; damage_superblock(&md1)?; let md2 = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_repair!(flag1, flag2, "-i", &md1, "-o", &md2))?; + let stderr = run_fail( + THIN_REPAIR, + &[ + flag1, + flag2, + "-i", + md1.to_str().unwrap(), + "-o", + md2.to_str().unwrap(), + ], + )?; assert!(stderr.contains(pattern)); Ok(()) } @@ -170,3 +167,5 @@ fn missing_nr_data_blocks() -> Result<()> { "nr data blocks", ) } + +//----------------------------------------- diff --git a/tests/thin_restore.rs b/tests/thin_restore.rs index 0fc23bf..79336e4 100644 --- a/tests/thin_restore.rs +++ b/tests/thin_restore.rs @@ -1,101 +1,51 @@ use anyhow::Result; -use std::str::from_utf8; -use thinp::file_utils; -use thinp::version::tools_version; mod common; + +use common::common_args::*; +use common::input_arg::*; +use common::output_option::*; use common::test_dir::*; use common::*; //------------------------------------------ -#[test] -fn accepts_v() -> Result<()> { - let stdout = thin_restore!("-V").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +const USAGE: &str = "Usage: thin_restore [options]\n\ + Options:\n \ + {-h|--help}\n \ + {-i|--input} \n \ + {-o|--output} \n \ + {--transaction-id} \n \ + {--data-block-size} \n \ + {--nr-data-blocks} \n \ + {-q|--quiet}\n \ + {-V|--version}"; -#[test] -fn accepts_version() -> Result<()> { - let stdout = thin_restore!("--version").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +//------------------------------------------ -const USAGE: &str = "Usage: thin_restore [options]\nOptions:\n {-h|--help}\n {-i|--input} \n {-o|--output} \n {--transaction-id} \n {--data-block-size} \n {--nr-data-blocks} \n {-q|--quiet}\n {-V|--version}"; +test_accepts_help!(THIN_RESTORE, USAGE); +test_accepts_version!(THIN_RESTORE); -#[test] -fn accepts_h() -> Result<()> { - let stdout = thin_restore!("-h").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} +test_missing_input_option!(THIN_RESTORE); +test_input_file_not_found!(THIN_RESTORE, OPTION); +test_corrupted_input_data!(THIN_RESTORE, OPTION); -#[test] -fn accepts_help() -> Result<()> { - let stdout = thin_restore!("--help").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} +test_missing_output_option!(THIN_RESTORE, mk_valid_xml); +test_tiny_output_file!(THIN_RESTORE, mk_valid_xml); -#[test] -fn missing_input_arg() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_restore!("-o", &md))?; - assert!(stderr.contains(msg::MISSING_INPUT_ARG)); - Ok(()) -} +//----------------------------------------- -#[test] -fn input_file_not_found() -> Result<()> { - let mut td = TestDir::new()?; - let md = mk_zeroed_md(&mut td)?; - let stderr = run_fail(thin_restore!("-i", "no-such-file", "-o", &md))?; - assert!(superblock_all_zeroes(&md)?); - assert!(stderr.contains(msg::FILE_NOT_FOUND)); - Ok(()) -} - -#[test] -fn garbage_input_file() -> Result<()> { - let mut td = TestDir::new()?; - let xml = mk_zeroed_md(&mut td)?; - let md = mk_zeroed_md(&mut td)?; - let _stderr = run_fail(thin_restore!("-i", &xml, "-o", &md))?; - assert!(superblock_all_zeroes(&md)?); - Ok(()) -} - -#[test] -fn missing_output_arg() -> Result<()> { - let mut td = TestDir::new()?; - let xml = mk_valid_xml(&mut td)?; - let stderr = run_fail(thin_restore!("-i", &xml))?; - assert!(stderr.contains(msg::MISSING_OUTPUT_ARG)); - Ok(()) -} - -#[test] -fn tiny_output_file() -> Result<()> { - let mut td = TestDir::new()?; - let xml = mk_valid_xml(&mut td)?; - let md = td.mk_path("meta.bin"); - let _file = file_utils::create_sized_file(&md, 4096); - let stderr = run_fail(thin_restore!("-i", &xml, "-o", &md))?; - assert!(stderr.contains("Output file too small")); - Ok(()) -} +// TODO: share with cache_restore, era_restore fn quiet_flag(flag: &str) -> Result<()> { let mut td = TestDir::new()?; let xml = mk_valid_xml(&mut td)?; let md = mk_zeroed_md(&mut td)?; - let output = thin_restore!("-i", &xml, "-o", &md, flag).run()?; + let xml_path = xml.to_str().unwrap(); + let md_path = md.to_str().unwrap(); + let output = run_ok_raw(THIN_RESTORE, &["-i", xml_path, "-o", md_path, flag])?; - assert!(output.status.success()); assert_eq!(output.stdout.len(), 0); assert_eq!(output.stderr.len(), 0); Ok(()) @@ -111,15 +61,21 @@ fn accepts_quiet() -> Result<()> { quiet_flag("--quiet") } +//----------------------------------------- + +// TODO: share with thin_dump + fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> { let mut td = TestDir::new()?; let xml = mk_valid_xml(&mut td)?; let md = mk_zeroed_md(&mut td)?; - thin_restore!("-i", &xml, "-o", &md, flag, value).run()?; + let xml_path = xml.to_str().unwrap(); + let md_path = md.to_str().unwrap(); + run_ok(THIN_RESTORE, &["-i", xml_path, "-o", md_path, flag, value])?; - let output = thin_dump!(&md).run()?; - assert!(from_utf8(&output.stdout)?.contains(pattern)); + let output = run_ok(THIN_DUMP, &[md_path])?; + assert!(output.contains(pattern)); Ok(()) } @@ -137,3 +93,5 @@ fn override_data_block_size() -> Result<()> { fn override_nr_data_blocks() -> Result<()> { override_something("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"") } + +//----------------------------------------- diff --git a/tests/thin_rmap.rs b/tests/thin_rmap.rs index 26bb571..eab535d 100644 --- a/tests/thin_rmap.rs +++ b/tests/thin_rmap.rs @@ -1,54 +1,36 @@ use anyhow::Result; -use thinp::version::tools_version; mod common; + +use common::common_args::*; use common::test_dir::*; use common::*; //------------------------------------------ -#[test] -fn accepts_v() -> Result<()> { - let stdout = thin_rmap!("-V").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +const USAGE: &str = "Usage: thin_rmap [options] {device|file}\n\ + Options:\n \ + {-h|--help}\n \ + {-V|--version}\n \ + {--region }*\n\ + Where:\n \ + is of the form ..\n \ + for example 5..45 denotes blocks 5 to 44 inclusive, but not block 45"; -#[test] -fn accepts_version() -> Result<()> { - let stdout = thin_rmap!("--version").read()?; - assert!(stdout.contains(tools_version())); - Ok(()) -} +//------------------------------------------ -const USAGE: &str = "Usage: thin_rmap [options] {device|file}\nOptions:\n {-h|--help}\n {-V|--version}\n {--region }*\nWhere:\n is of the form ..\n for example 5..45 denotes blocks 5 to 44 inclusive, but not block 45"; +test_accepts_help!(THIN_RMAP, USAGE); +test_accepts_version!(THIN_RMAP); +test_rejects_bad_option!(THIN_RMAP); -#[test] -fn accepts_h() -> Result<()> { - let stdout = thin_rmap!("-h").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} - -#[test] -fn accepts_help() -> Result<()> { - let stdout = thin_rmap!("--help").read()?; - assert_eq!(stdout, USAGE); - Ok(()) -} - -#[test] -fn rejects_bad_option() -> Result<()> { - let stderr = run_fail(thin_rmap!("--hedgehogs-only"))?; - assert!(stderr.contains("unrecognized option \'--hedgehogs-only\'")); - Ok(()) -} +//------------------------------------------ #[test] fn valid_region_format_should_pass() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - thin_rmap!("--region", "23..7890", &md).run()?; + let md_path = md.to_str().unwrap(); + run_ok(THIN_RMAP, &["--region", "23..7890", md_path])?; Ok(()) } @@ -67,7 +49,7 @@ fn invalid_regions_should_fail() -> Result<()> { for r in &invalid_regions { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - run_fail(thin_rmap!(r, &md))?; + run_fail(THIN_RMAP, &[&r.to_string(), md.to_str().unwrap()])?; } Ok(()) } @@ -76,7 +58,16 @@ fn invalid_regions_should_fail() -> Result<()> { fn multiple_regions_should_pass() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - thin_rmap!("--region", "1..23", "--region", "45..78", &md).run()?; + run_ok( + THIN_RMAP, + &[ + "--region", + "1..23", + "--region", + "45..78", + md.to_str().unwrap(), + ], + )?; Ok(()) } @@ -84,7 +75,7 @@ fn multiple_regions_should_pass() -> Result<()> { fn junk_input() -> Result<()> { let mut td = TestDir::new()?; let xml = mk_valid_xml(&mut td)?; - run_fail(thin_rmap!("--region", "0..-1", &xml))?; + run_fail(THIN_RMAP, &["--region", "0..-1", xml.to_str().unwrap()])?; Ok(()) } From f395bab7be9a17370331e16e98b5da91b10d9217 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Tue, 6 Jul 2021 09:51:27 +0800 Subject: [PATCH 5/8] [tests] Use traits to specify test parameters To deal with variety in target attributes and their expected outputs, the test parameters are categorized into traits, thus the test program could define test parameters in a more structured way, without having to pass multiple tightly-coupled parameters to test functions. --- tests/cache_check.rs | 66 +++++++++++--- tests/common/common_args.rs | 54 ++++++++---- tests/common/input_arg.rs | 158 +++++++++++++++++----------------- tests/common/mod.rs | 72 ++++++++++++++-- tests/common/output_option.rs | 66 +++++++------- tests/thin_check.rs | 66 +++++++++++--- tests/thin_delta.rs | 32 ++++++- tests/thin_dump.rs | 58 +++++++++++-- tests/thin_metadata_pack.rs | 67 ++++++++++++-- tests/thin_metadata_unpack.rs | 68 +++++++++++++-- tests/thin_repair.rs | 66 ++++++++++++-- tests/thin_restore.rs | 70 +++++++++++++-- tests/thin_rmap.rs | 32 ++++++- 13 files changed, 677 insertions(+), 198 deletions(-) diff --git a/tests/cache_check.rs b/tests/cache_check.rs index 9a629d1..f89c182 100644 --- a/tests/cache_check.rs +++ b/tests/cache_check.rs @@ -22,18 +22,64 @@ const USAGE: &str = "Usage: cache_check [options] {device|file}\n\ //------------------------------------------ -test_accepts_help!(CACHE_CHECK, USAGE); -test_accepts_version!(CACHE_CHECK); -test_rejects_bad_option!(CACHE_CHECK); +struct CacheCheck; -test_missing_input_arg!(CACHE_CHECK); -test_input_file_not_found!(CACHE_CHECK, ARG); -test_input_cannot_be_a_directory!(CACHE_CHECK, ARG); -test_unreadable_input_file!(CACHE_CHECK, ARG); +impl<'a> Program<'a> for CacheCheck { + fn name() -> &'a str { + "cache_check" + } -test_help_message_for_tiny_input_file!(CACHE_CHECK, ARG); -test_spot_xml_data!(CACHE_CHECK, "cache_check", ARG); -test_corrupted_input_data!(CACHE_CHECK, ARG); + fn path() -> &'a str { + CACHE_CHECK + } + + fn usage() -> &'a str { + USAGE + } + + fn arg_type() -> ArgType { + ArgType::InputArg + } + + fn bad_option_hint(option: &str) -> String { + msg::bad_option_hint(option) + } +} + +impl<'a> InputProgram<'a> for CacheCheck { + fn mk_valid_input(td: &mut TestDir) -> Result { + mk_valid_md(td) + } + + fn file_not_found() -> &'a str { + msg::FILE_NOT_FOUND + } + + fn missing_input_arg() -> &'a str { + msg::MISSING_INPUT_ARG + } + + fn corrupted_input() -> &'a str { + msg::BAD_SUPERBLOCK + } +} + +impl<'a> BinaryInputProgram<'_> for CacheCheck {} + +//------------------------------------------ + +test_accepts_help!(CacheCheck); +test_accepts_version!(CacheCheck); +test_rejects_bad_option!(CacheCheck); + +test_missing_input_arg!(CacheCheck); +test_input_file_not_found!(CacheCheck); +test_input_cannot_be_a_directory!(CacheCheck); +test_unreadable_input_file!(CacheCheck); + +test_help_message_for_tiny_input_file!(CacheCheck); +test_spot_xml_data!(CacheCheck); +test_corrupted_input_data!(CacheCheck); //------------------------------------------ diff --git a/tests/common/common_args.rs b/tests/common/common_args.rs index f4b0a5d..41387df 100644 --- a/tests/common/common_args.rs +++ b/tests/common/common_args.rs @@ -4,29 +4,35 @@ use thinp::version::tools_version; //------------------------------------------ // help -pub fn test_help_short(program: &str, usage: &str) -> Result<()> { - let stdout = run_ok(program, &["-h"])?; - assert_eq!(stdout, usage); +pub fn test_help_short<'a, P>() -> Result<()> +where + P: Program<'a>, +{ + let stdout = run_ok(P::path(), &["-h"])?; + assert_eq!(stdout, P::usage()); Ok(()) } -pub fn test_help_long(program: &str, usage: &str) -> Result<()> { - let stdout = run_ok(program, &["--help"])?; - assert_eq!(stdout, usage); +pub fn test_help_long<'a, P>() -> Result<()> +where + P: Program<'a>, +{ + let stdout = run_ok(P::path(), &["--help"])?; + assert_eq!(stdout, P::usage()); Ok(()) } #[macro_export] macro_rules! test_accepts_help { - ($program: ident, $usage: expr) => { + ($program: ident) => { #[test] fn accepts_h() -> Result<()> { - test_help_short($program, $usage) + test_help_short::<$program>() } #[test] fn accepts_help() -> Result<()> { - test_help_long($program, $usage) + test_help_long::<$program>() } }; } @@ -34,14 +40,20 @@ macro_rules! test_accepts_help { //------------------------------------------ // version -pub fn test_version_short(program: &str) -> Result<()> { - let stdout = run_ok(program, &["-V"])?; +pub fn test_version_short<'a, P>() -> Result<()> +where + P: Program<'a>, +{ + let stdout = run_ok(P::path(), &["-V"])?; assert!(stdout.contains(tools_version())); Ok(()) } -pub fn test_version_long(program: &str) -> Result<()> { - let stdout = run_ok(program, &["--version"])?; +pub fn test_version_long<'a, P>() -> Result<()> +where + P: Program<'a>, +{ + let stdout = run_ok(P::path(), &["--version"])?; assert!(stdout.contains(tools_version())); Ok(()) } @@ -51,21 +63,25 @@ macro_rules! test_accepts_version { ($program: ident) => { #[test] fn accepts_v() -> Result<()> { - test_version_short($program) + test_version_short::<$program>() } #[test] fn accepts_version() -> Result<()> { - test_version_long($program) + test_version_long::<$program>() } }; } //------------------------------------------ -pub fn test_rejects_bad_option(program: &str) -> Result<()> { - let stderr = run_fail(program, &["--hedgehogs-only"])?; - assert!(stderr.contains("unrecognized option \'--hedgehogs-only\'")); +pub fn test_rejects_bad_option<'a, P>() -> Result<()> +where + P: Program<'a>, +{ + let option = "--hedgehogs-only"; + let stderr = run_fail(P::path(), &[option])?; + assert!(stderr.contains(&P::bad_option_hint(option))); Ok(()) } @@ -74,7 +90,7 @@ macro_rules! test_rejects_bad_option { ($program: ident) => { #[test] fn rejects_bad_option() -> Result<()> { - test_rejects_bad_option($program) + test_rejects_bad_option::<$program>() } }; } diff --git a/tests/common/input_arg.rs b/tests/common/input_arg.rs index 30f4400..4e096db 100644 --- a/tests/common/input_arg.rs +++ b/tests/common/input_arg.rs @@ -4,7 +4,9 @@ use crate::common::*; //------------------------------------------ // wrappers -pub fn with_output_md_untouched( +type ArgsBuilder = fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>; + +fn with_output_md_untouched( td: &mut TestDir, input: &str, thunk: &dyn Fn(&[&str]) -> Result<()>, @@ -16,7 +18,19 @@ pub fn with_output_md_untouched( }) } -pub fn input_arg_only( +fn with_output_superblock_zeroed( + td: &mut TestDir, + input: &str, + thunk: &dyn Fn(&[&str]) -> Result<()>, +) -> Result<()> { + let output = mk_zeroed_md(td)?; + ensure_superblock_zeroed(&output, || { + let args = ["-i", input, "-o", output.to_str().unwrap()]; + thunk(&args) + }) +} + +fn input_arg_only( _td: &mut TestDir, input: &str, thunk: &dyn Fn(&[&str]) -> Result<()>, @@ -25,12 +39,22 @@ pub fn input_arg_only( thunk(&args) } +fn build_args_fn(t: ArgType) -> Result { + match t { + ArgType::InputArg => Ok(input_arg_only), + ArgType::IoOptions => Ok(with_output_md_untouched), + } +} + //------------------------------------------ // test invalid arguments -pub fn test_missing_input_arg(program: &str) -> Result<()> { - let stderr = run_fail(program, &[])?; - assert!(stderr.contains(msg::MISSING_INPUT_ARG)); +pub fn test_missing_input_arg<'a, P>() -> Result<()> +where + P: InputProgram<'a>, +{ + let stderr = run_fail(P::path(), &[])?; + assert!(stderr.contains(P::missing_input_arg())); Ok(()) } @@ -39,18 +63,21 @@ macro_rules! test_missing_input_arg { ($program: ident) => { #[test] fn missing_input_arg() -> Result<()> { - test_missing_input_arg($program) + test_missing_input_arg::<$program>() } }; } -pub fn test_missing_input_option(program: &str) -> Result<()> { +pub fn test_missing_input_option<'a, P>() -> Result<()> +where + P: InputProgram<'a>, +{ let mut td = TestDir::new()?; let output = mk_zeroed_md(&mut td)?; ensure_untouched(&output, || { let args = ["-o", output.to_str().unwrap()]; - let stderr = run_fail(program, &args)?; - assert!(stderr.contains(msg::MISSING_INPUT_ARG)); + let stderr = run_fail(P::path(), &args)?; + assert!(stderr.contains(P::missing_input_arg())); Ok(()) }) } @@ -60,48 +87,44 @@ macro_rules! test_missing_input_option { ($program: ident) => { #[test] fn missing_input_option() -> Result<()> { - test_missing_input_option($program) + test_missing_input_option::<$program>() } }; } -pub fn test_input_file_not_found(program: &str, wrapper: F) -> Result<()> +pub fn test_input_file_not_found<'a, P>() -> Result<()> where - F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, + P: InputProgram<'a>, { let mut td = TestDir::new()?; + let wrapper = build_args_fn(P::arg_type())?; wrapper(&mut td, "no-such-file", &|args: &[&str]| { - let stderr = run_fail(program, args)?; - assert!(stderr.contains(msg::FILE_NOT_FOUND)); + let stderr = run_fail(P::path(), args)?; + assert!(stderr.contains(P::file_not_found())); Ok(()) }) } #[macro_export] macro_rules! test_input_file_not_found { - ($program: ident, ARG) => { + ($program: ident) => { #[test] fn input_file_not_found() -> Result<()> { - test_input_file_not_found($program, input_arg_only) - } - }; - ($program: ident, OPTION) => { - #[test] - fn input_file_not_found() -> Result<()> { - test_input_file_not_found($program, with_output_md_untouched) + test_input_file_not_found::<$program>() } }; } -pub fn test_input_cannot_be_a_directory(program: &str, wrapper: F) -> Result<()> +pub fn test_input_cannot_be_a_directory<'a, P>() -> Result<()> where - F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, + P: InputProgram<'a>, { let mut td = TestDir::new()?; + let wrapper = build_args_fn(P::arg_type())?; wrapper(&mut td, "/tmp", &|args: &[&str]| { - let stderr = run_fail(program, args)?; + let stderr = run_fail(P::path(), args)?; assert!(stderr.contains("Not a block device or regular file")); Ok(()) }) @@ -109,23 +132,17 @@ where #[macro_export] macro_rules! test_input_cannot_be_a_directory { - ($program: ident, ARG) => { + ($program: ident) => { #[test] fn input_cannot_be_a_directory() -> Result<()> { - test_input_cannot_be_a_directory($program, input_arg_only) - } - }; - ($program: ident, OPTION) => { - #[test] - fn input_cannot_be_a_directory() -> Result<()> { - test_input_cannot_be_a_directory($program, with_output_md_untouched) + test_input_cannot_be_a_directory::<$program>() } }; } -pub fn test_unreadable_input_file(program: &str, wrapper: F) -> Result<()> +pub fn test_unreadable_input_file<'a, P>() -> Result<()> where - F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, + P: InputProgram<'a>, { let mut td = TestDir::new()?; @@ -133,8 +150,9 @@ where let input = mk_valid_md(&mut td)?; duct::cmd!("chmod", "-r", &input).run()?; + let wrapper = build_args_fn(P::arg_type())?; wrapper(&mut td, input.to_str().unwrap(), &|args: &[&str]| { - let stderr = run_fail(program, args)?; + let stderr = run_fail(P::path(), args)?; assert!(stderr.contains("Permission denied")); Ok(()) }) @@ -142,16 +160,10 @@ where #[macro_export] macro_rules! test_unreadable_input_file { - ($program: ident, ARG) => { + ($program: ident) => { #[test] fn unreadable_input_file() -> Result<()> { - test_unreadable_input_file($program, input_arg_only) - } - }; - ($program: ident, OPTION) => { - #[test] - fn unreadable_input_file() -> Result<()> { - test_unreadable_input_file($program, with_output_md_untouched) + test_unreadable_input_file::<$program>() } }; } @@ -159,17 +171,18 @@ macro_rules! test_unreadable_input_file { //------------------------------------------ // test invalid content -pub fn test_help_message_for_tiny_input_file(program: &str, wrapper: F) -> Result<()> +pub fn test_help_message_for_tiny_input_file<'a, P>() -> Result<()> where - F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, + P: BinaryInputProgram<'a>, { let mut td = TestDir::new()?; let input = td.mk_path("meta.bin"); file_utils::create_sized_file(&input, 1024)?; + let wrapper = build_args_fn(P::arg_type())?; wrapper(&mut td, input.to_str().unwrap(), &|args: &[&str]| { - let stderr = run_fail(program, args)?; + let stderr = run_fail(P::path(), args)?; assert!(stderr.contains("Metadata device/file too small. Is this binary metadata?")); Ok(()) }) @@ -177,23 +190,17 @@ where #[macro_export] macro_rules! test_help_message_for_tiny_input_file { - ($program: ident, ARG) => { + ($program: ident) => { #[test] fn prints_help_message_for_tiny_input_file() -> Result<()> { - test_help_message_for_tiny_input_file($program, input_arg_only) - } - }; - ($program: ident, OPTION) => { - #[test] - fn prints_help_message_for_tiny_input_file() -> Result<()> { - test_help_message_for_tiny_input_file($program, with_output_md_untouched) + test_help_message_for_tiny_input_file::<$program>() } }; } -pub fn test_spot_xml_data(program: &str, name: &str, wrapper: F) -> Result<()> +pub fn test_spot_xml_data<'a, P>() -> Result<()> where - F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, + P: BinaryInputProgram<'a>, { let mut td = TestDir::new()?; @@ -202,12 +209,13 @@ where let mut gen = FragmentedS::new(4, 10240); write_xml(&input, &mut gen)?; + let wrapper = build_args_fn(P::arg_type())?; wrapper(&mut td, input.to_str().unwrap(), &|args: &[&str]| { - let stderr = run_fail(program, args)?; + let stderr = run_fail(P::path(), args)?; eprintln!("{}", stderr); let msg = format!( "This looks like XML. {} only checks the binary metadata format.", - name + P::name() ); assert!(stderr.contains(&msg)); Ok(()) @@ -216,46 +224,38 @@ where #[macro_export] macro_rules! test_spot_xml_data { - ($program: ident, $name: expr, ARG) => { + ($program: ident) => { #[test] fn spot_xml_data() -> Result<()> { - test_spot_xml_data($program, $name, input_arg_only) - } - }; - ($program: ident, $name: expr, OPTION) => { - #[test] - fn spot_xml_data() -> Result<()> { - test_spot_xml_data($program, $name, with_output_md_untouched) + test_spot_xml_data::<$program>() } }; } -pub fn test_corrupted_input_data(program: &str, wrapper: F) -> Result<()> +pub fn test_corrupted_input_data<'a, P>() -> Result<()> where - F: Fn(&mut TestDir, &str, &dyn Fn(&[&str]) -> Result<()>) -> Result<()>, + P: InputProgram<'a>, { let mut td = TestDir::new()?; let input = mk_zeroed_md(&mut td)?; + let wrapper = match P::arg_type() { + ArgType::InputArg => input_arg_only, + ArgType::IoOptions => with_output_superblock_zeroed, + }; wrapper(&mut td, input.to_str().unwrap(), &|args: &[&str]| { - let stderr = run_fail(program, args)?; - assert!(stderr.contains("bad checksum in superblock")); + let stderr = run_fail(P::path(), args)?; + assert!(stderr.contains(P::corrupted_input())); Ok(()) }) } #[macro_export] macro_rules! test_corrupted_input_data { - ($program: ident, ARG) => { + ($program: ident) => { #[test] fn corrupted_input_data() -> Result<()> { - test_corrupted_input_data($program, input_arg_only) - } - }; - ($program: ident, OPTION) => { - #[test] - fn corrupted_input_data() -> Result<()> { - test_corrupted_input_data($program, with_output_md_untouched) + test_corrupted_input_data::<$program>() } }; } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 71503b2..a4801aa 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -19,22 +19,33 @@ use test_dir::TestDir; //------------------------------------------ -#[cfg(not(feature = "rust_tests"))] -pub mod msg { +pub mod cpp_msg { pub const FILE_NOT_FOUND: &str = "No such file or directory"; pub const MISSING_INPUT_ARG: &str = "No input file provided"; pub const MISSING_OUTPUT_ARG: &str = "No output file provided"; + pub const BAD_SUPERBLOCK: &str = "bad checksum in superblock"; + + pub fn bad_option_hint(option: &str) -> String { + format!("unrecognized option '{}'", option) + } } -#[cfg(feature = "rust_tests")] -pub mod msg { +pub mod rust_msg { pub const FILE_NOT_FOUND: &str = "Couldn't find input file"; - pub const MISSING_INPUT_ARG: &str = - "The following required arguments were not provided\n -i "; - pub const MISSING_OUTPUT_ARG: &str = - "The following required arguments were not provided\n -o "; + pub const MISSING_INPUT_ARG: &str = "The following required arguments were not provided"; // TODO: be specific + pub const MISSING_OUTPUT_ARG: &str = "The following required arguments were not provided"; // TODO: be specific + pub const BAD_SUPERBLOCK: &str = "bad checksum in superblock"; + + pub fn bad_option_hint(option: &str) -> String { + format!("Found argument '{}' which wasn't expected", option) + } } +#[cfg(not(feature = "rust_tests"))] +pub use cpp_msg as msg; +#[cfg(feature = "rust_tests")] +pub use rust_msg as msg; + //------------------------------------------ #[macro_export] @@ -86,6 +97,42 @@ pub const THIN_GENERATE_DAMAGE: &str = path_to_cpp!("thin_generate_damage"); // //------------------------------------------ +pub enum ArgType { + InputArg, + IoOptions, +} + +pub trait Program<'a> { + fn name() -> &'a str; + fn path() -> &'a str; + fn usage() -> &'a str; + fn arg_type() -> ArgType; + + // error messages + fn bad_option_hint(option: &str) -> String; +} + +pub trait InputProgram<'a>: Program<'a> { + fn mk_valid_input(td: &mut TestDir) -> Result; + + // error messages + fn missing_input_arg() -> &'a str; + fn file_not_found() -> &'a str; + fn corrupted_input() -> &'a str; +} + +pub trait BinaryInputProgram<'a>: InputProgram<'a> {} + +pub trait OutputProgram<'a>: InputProgram<'a> { + // error messages + fn missing_output_arg() -> &'a str; + fn file_not_found() -> &'a str; +} + +pub trait BinaryOutputProgram<'a>: OutputProgram<'a> {} + +//------------------------------------------ + // Returns stdout. The command must return zero. pub fn run_ok(program: &str, args: &[&str]) -> Result { let command = duct::cmd(program, args).stdout_capture().stderr_capture(); @@ -291,4 +338,13 @@ where Ok(()) } +pub fn ensure_superblock_zeroed(p: &PathBuf, thunk: F) -> Result<()> +where + F: Fn() -> Result<()>, +{ + thunk()?; + assert!(superblock_all_zeroes(p)?); + Ok(()) +} + //------------------------------------------ diff --git a/tests/common/output_option.rs b/tests/common/output_option.rs index 3ec3a97..a5e5e7b 100644 --- a/tests/common/output_option.rs +++ b/tests/common/output_option.rs @@ -3,85 +3,85 @@ use crate::common::*; //----------------------------------------- // test invalid arguments -pub fn test_missing_output_option(program: &str, mk_input: F) -> Result<()> +pub fn test_missing_output_option<'a, P>() -> Result<()> where - F: Fn(&mut TestDir) -> Result, + P: OutputProgram<'a>, { let mut td = TestDir::new()?; - let input = mk_input(&mut td)?; - let stderr = run_fail(program, &["-i", input.to_str().unwrap()])?; - assert!(stderr.contains(msg::MISSING_OUTPUT_ARG)); + let input = P::mk_valid_input(&mut td)?; + let stderr = run_fail(P::path(), &["-i", input.to_str().unwrap()])?; + assert!(stderr.contains(P::missing_output_arg())); Ok(()) } #[macro_export] macro_rules! test_missing_output_option { - ($program: ident, $mk_input: ident) => { + ($program: ident) => { #[test] fn missing_output_option() -> Result<()> { - test_missing_output_option($program, $mk_input) + test_missing_output_option::<$program>() } }; } -pub fn test_output_file_not_found(program: &str, mk_input: F) -> Result<()> +pub fn test_output_file_not_found<'a, P>() -> Result<()> where - F: Fn(&mut TestDir) -> Result, + P: OutputProgram<'a>, { let mut td = TestDir::new()?; - let input = mk_input(&mut td)?; + let input = P::mk_valid_input(&mut td)?; let stderr = run_fail( - program, + P::path(), &["-i", input.to_str().unwrap(), "-o", "no-such-file"], )?; - assert!(stderr.contains(msg::FILE_NOT_FOUND)); + assert!(stderr.contains(

::file_not_found())); Ok(()) } #[macro_export] macro_rules! test_output_file_not_found { - ($program: ident, $mk_input: ident) => { + ($program: ident) => { #[test] fn output_file_not_found() -> Result<()> { - test_output_file_not_found($program, $mk_input) + test_output_file_not_found::<$program>() } }; } -pub fn test_output_cannot_be_a_directory(program: &str, mk_input: F) -> Result<()> +pub fn test_output_cannot_be_a_directory<'a, P>() -> Result<()> where - F: Fn(&mut TestDir) -> Result, + P: OutputProgram<'a>, { let mut td = TestDir::new()?; - let input = mk_input(&mut td)?; - let stderr = run_fail(program, &["-i", input.to_str().unwrap(), "-o", "/tmp"])?; - assert!(stderr.contains(msg::FILE_NOT_FOUND)); + let input = P::mk_valid_input(&mut td)?; + let stderr = run_fail(P::path(), &["-i", input.to_str().unwrap(), "-o", "/tmp"])?; + assert!(stderr.contains("Not a block device or regular file")); Ok(()) } #[macro_export] macro_rules! test_output_cannot_be_a_directory { - ($program: ident, $mk_input: ident) => { + ($program: ident) => { #[test] fn output_cannot_be_a_directory() -> Result<()> { - test_output_cannot_be_a_directory($program, $mk_input) + test_output_cannot_be_a_directory::<$program>() } }; } -pub fn test_unwritable_output_file(program: &str, mk_input: F) -> Result<()> +pub fn test_unwritable_output_file<'a, P>() -> Result<()> where - F: Fn(&mut TestDir) -> Result, + P: OutputProgram<'a>, { let mut td = TestDir::new()?; - let input = mk_input(&mut td)?; + let input = P::mk_valid_input(&mut td)?; let output = td.mk_path("meta.bin"); let _file = file_utils::create_sized_file(&output, 4096); duct::cmd!("chmod", "-w", &output).run()?; let stderr = run_fail( - program, + P::path(), &[ "-i", input.to_str().unwrap(), @@ -95,10 +95,10 @@ where #[macro_export] macro_rules! test_unwritable_output_file { - ($program: ident, $mk_input: ident) => { + ($program: ident) => { #[test] fn unwritable_output_file() -> Result<()> { - test_unwritable_output_file($program, $mk_input) + test_unwritable_output_file::<$program>() } }; } @@ -107,18 +107,18 @@ macro_rules! test_unwritable_output_file { // test invalid content // currently thin/cache_restore only -pub fn test_tiny_output_file(program: &str, mk_input: F) -> Result<()> +pub fn test_tiny_output_file<'a, P>() -> Result<()> where - F: Fn(&mut TestDir) -> Result, + P: BinaryOutputProgram<'a>, { let mut td = TestDir::new()?; - let input = mk_input(&mut td)?; + let input = P::mk_valid_input(&mut td)?; let output = td.mk_path("meta.bin"); let _file = file_utils::create_sized_file(&output, 4096); let stderr = run_fail( - program, + P::path(), &[ "-i", input.to_str().unwrap(), @@ -132,10 +132,10 @@ where #[macro_export] macro_rules! test_tiny_output_file { - ($program: ident, $mk_input: ident) => { + ($program: ident) => { #[test] fn tiny_output_file() -> Result<()> { - test_tiny_output_file($program, $mk_input) + test_tiny_output_file::<$program>() } }; } diff --git a/tests/thin_check.rs b/tests/thin_check.rs index 159b447..577a34d 100644 --- a/tests/thin_check.rs +++ b/tests/thin_check.rs @@ -22,20 +22,66 @@ const USAGE: &str = "Usage: thin_check [options] {device|file}\n\ {--skip-mappings}\n \ {--super-block-only}"; +//----------------------------------------- + +struct ThinCheck; + +impl<'a> Program<'a> for ThinCheck { + fn name() -> &'a str { + "thin_check" + } + + fn path() -> &'a str { + THIN_CHECK + } + + fn usage() -> &'a str { + USAGE + } + + fn arg_type() -> ArgType { + ArgType::InputArg + } + + fn bad_option_hint(option: &str) -> String { + msg::bad_option_hint(option) + } +} + +impl<'a> InputProgram<'a> for ThinCheck { + fn mk_valid_input(td: &mut TestDir) -> Result { + mk_valid_md(td) + } + + fn file_not_found() -> &'a str { + msg::FILE_NOT_FOUND + } + + fn missing_input_arg() -> &'a str { + msg::MISSING_INPUT_ARG + } + + fn corrupted_input() -> &'a str { + msg::BAD_SUPERBLOCK + } +} + +impl<'a> BinaryInputProgram<'_> for ThinCheck {} + //------------------------------------------ -test_accepts_help!(THIN_CHECK, USAGE); -test_accepts_version!(THIN_CHECK); -test_rejects_bad_option!(THIN_CHECK); +test_accepts_help!(ThinCheck); +test_accepts_version!(ThinCheck); +test_rejects_bad_option!(ThinCheck); -test_missing_input_arg!(THIN_CHECK); -test_input_file_not_found!(THIN_CHECK, ARG); -test_input_cannot_be_a_directory!(THIN_CHECK, ARG); -test_unreadable_input_file!(THIN_CHECK, ARG); +test_missing_input_arg!(ThinCheck); +test_input_file_not_found!(ThinCheck); +test_input_cannot_be_a_directory!(ThinCheck); +test_unreadable_input_file!(ThinCheck); -test_help_message_for_tiny_input_file!(THIN_CHECK, ARG); -test_spot_xml_data!(THIN_CHECK, "thin_check", ARG); -test_corrupted_input_data!(THIN_CHECK, ARG); +test_help_message_for_tiny_input_file!(ThinCheck); +test_spot_xml_data!(ThinCheck); +test_corrupted_input_data!(ThinCheck); //------------------------------------------ // test exclusive flags diff --git a/tests/thin_delta.rs b/tests/thin_delta.rs index cdd203d..c9256a8 100644 --- a/tests/thin_delta.rs +++ b/tests/thin_delta.rs @@ -19,9 +19,35 @@ const USAGE: &str = "Usage: thin_delta [options] \n\ //------------------------------------------ -test_accepts_help!(THIN_DELTA, USAGE); -test_accepts_version!(THIN_DELTA); -test_rejects_bad_option!(THIN_DELTA); +struct ThinDelta; + +impl<'a> Program<'a> for ThinDelta { + fn name() -> &'a str { + "thin_delta" + } + + fn path() -> &'a str { + THIN_DELTA + } + + fn usage() -> &'a str { + USAGE + } + + fn arg_type() -> ArgType { + ArgType::InputArg + } + + fn bad_option_hint(option: &str) -> String { + cpp_msg::bad_option_hint(option) + } +} + +//------------------------------------------ + +test_accepts_help!(ThinDelta); +test_accepts_version!(ThinDelta); +test_rejects_bad_option!(ThinDelta); //------------------------------------------ diff --git a/tests/thin_dump.rs b/tests/thin_dump.rs index 1cb6819..6d6eb11 100644 --- a/tests/thin_dump.rs +++ b/tests/thin_dump.rs @@ -23,16 +23,60 @@ const USAGE: &str = "Usage: thin_dump [options] {device|file}\n\ {--skip-mappings}\n \ {-V|--version}"; +//----------------------------------------- + +struct ThinDump; + +impl<'a> Program<'a> for ThinDump { + fn name() -> &'a str { + "thin_dump" + } + + fn path() -> &'a str { + THIN_DUMP + } + + fn usage() -> &'a str { + USAGE + } + + fn arg_type() -> ArgType { + ArgType::InputArg + } + + fn bad_option_hint(option: &str) -> String { + msg::bad_option_hint(option) + } +} + +impl<'a> InputProgram<'a> for ThinDump { + fn mk_valid_input(td: &mut TestDir) -> Result { + mk_valid_md(td) + } + + fn file_not_found() -> &'a str { + msg::FILE_NOT_FOUND + } + + fn missing_input_arg() -> &'a str { + msg::MISSING_INPUT_ARG + } + + fn corrupted_input() -> &'a str { + msg::BAD_SUPERBLOCK + } +} + //------------------------------------------ -test_accepts_help!(THIN_DUMP, USAGE); -test_accepts_version!(THIN_DUMP); -test_rejects_bad_option!(THIN_DUMP); +test_accepts_help!(ThinDump); +test_accepts_version!(ThinDump); +test_rejects_bad_option!(ThinDump); -test_missing_input_arg!(THIN_DUMP); -test_input_file_not_found!(THIN_DUMP, ARG); -test_input_cannot_be_a_directory!(THIN_DUMP, ARG); -test_unreadable_input_file!(THIN_DUMP, ARG); +test_missing_input_arg!(ThinDump); +test_input_file_not_found!(ThinDump); +test_input_cannot_be_a_directory!(ThinDump); +test_unreadable_input_file!(ThinDump); //------------------------------------------ // test dump & restore cycle diff --git a/tests/thin_metadata_pack.rs b/tests/thin_metadata_pack.rs index 92917df..616bc83 100644 --- a/tests/thin_metadata_pack.rs +++ b/tests/thin_metadata_pack.rs @@ -5,6 +5,7 @@ mod common; use common::common_args::*; use common::input_arg::*; use common::output_option::*; +use common::test_dir::*; use common::*; //------------------------------------------ @@ -28,12 +29,66 @@ const USAGE: &str = concat!( //------------------------------------------ -test_accepts_help!(THIN_METADATA_PACK, USAGE); -test_accepts_version!(THIN_METADATA_PACK); -test_rejects_bad_option!(THIN_METADATA_PACK); +struct ThinMetadataPack; -test_missing_input_option!(THIN_METADATA_PACK); -test_missing_output_option!(THIN_METADATA_PACK, mk_valid_md); -test_input_file_not_found!(THIN_METADATA_PACK, OPTION); +impl<'a> Program<'a> for ThinMetadataPack { + fn name() -> &'a str { + "thin_metadata_pack" + } + + fn path() -> &'a str { + THIN_METADATA_PACK + } + + fn usage() -> &'a str { + USAGE + } + + fn arg_type() -> ArgType { + ArgType::IoOptions + } + + fn bad_option_hint(option: &str) -> String { + rust_msg::bad_option_hint(option) + } +} + +impl<'a> InputProgram<'a> for ThinMetadataPack { + fn mk_valid_input(td: &mut TestDir) -> Result { + mk_valid_md(td) + } + + fn file_not_found() -> &'a str { + rust_msg::FILE_NOT_FOUND + } + + fn missing_input_arg() -> &'a str { + rust_msg::MISSING_INPUT_ARG + } + + fn corrupted_input() -> &'a str { + rust_msg::BAD_SUPERBLOCK + } +} + +impl<'a> OutputProgram<'a> for ThinMetadataPack { + fn file_not_found() -> &'a str { + rust_msg::FILE_NOT_FOUND + } + + fn missing_output_arg() -> &'a str { + rust_msg::MISSING_OUTPUT_ARG + } +} //------------------------------------------ + +test_accepts_help!(ThinMetadataPack); +test_accepts_version!(ThinMetadataPack); +test_rejects_bad_option!(ThinMetadataPack); + +test_missing_input_option!(ThinMetadataPack); +test_missing_output_option!(ThinMetadataPack); +test_input_file_not_found!(ThinMetadataPack); + +//----------------------------------------- diff --git a/tests/thin_metadata_unpack.rs b/tests/thin_metadata_unpack.rs index c2fc068..9a66d58 100644 --- a/tests/thin_metadata_unpack.rs +++ b/tests/thin_metadata_unpack.rs @@ -29,15 +29,69 @@ const USAGE: &str = concat!( //------------------------------------------ -test_accepts_help!(THIN_METADATA_UNPACK, USAGE); -test_accepts_version!(THIN_METADATA_UNPACK); -test_rejects_bad_option!(THIN_METADATA_UNPACK); +struct ThinMetadataUnpack; -test_missing_input_option!(THIN_METADATA_PACK); -test_input_file_not_found!(THIN_METADATA_UNPACK, OPTION); -test_corrupted_input_data!(THIN_METADATA_UNPACK, OPTION); +impl<'a> Program<'a> for ThinMetadataUnpack { + fn name() -> &'a str { + "thin_metadata_pack" + } -test_missing_output_option!(THIN_METADATA_UNPACK, mk_valid_md); + fn path() -> &'a str { + THIN_METADATA_UNPACK + } + + fn usage() -> &'a str { + USAGE + } + + fn arg_type() -> ArgType { + ArgType::IoOptions + } + + fn bad_option_hint(option: &str) -> String { + rust_msg::bad_option_hint(option) + } +} + +impl<'a> InputProgram<'a> for ThinMetadataUnpack { + fn mk_valid_input(td: &mut TestDir) -> Result { + mk_zeroed_md(td) // FIXME: make a real pack file + } + + fn file_not_found() -> &'a str { + rust_msg::FILE_NOT_FOUND + } + + fn missing_input_arg() -> &'a str { + rust_msg::MISSING_INPUT_ARG + } + + fn corrupted_input() -> &'a str { + "Not a pack file" + } +} + +impl<'a> OutputProgram<'a> for ThinMetadataUnpack { + fn file_not_found() -> &'a str { + rust_msg::FILE_NOT_FOUND + } + + fn missing_output_arg() -> &'a str { + rust_msg::MISSING_OUTPUT_ARG + } +} + +//------------------------------------------ + +test_accepts_help!(ThinMetadataUnpack); +test_accepts_version!(ThinMetadataUnpack); +test_rejects_bad_option!(ThinMetadataUnpack); + +test_missing_input_option!(ThinMetadataUnpack); +test_input_file_not_found!(ThinMetadataUnpack); +test_corrupted_input_data!(ThinMetadataUnpack); + +test_missing_output_option!(ThinMetadataUnpack); //------------------------------------------ diff --git a/tests/thin_repair.rs b/tests/thin_repair.rs index 1884e6f..31ecaa8 100644 --- a/tests/thin_repair.rs +++ b/tests/thin_repair.rs @@ -22,14 +22,68 @@ const USAGE: &str = "Usage: thin_repair [options] {device|file}\n\ //----------------------------------------- -test_accepts_help!(THIN_REPAIR, USAGE); -test_accepts_version!(THIN_REPAIR); -test_rejects_bad_option!(THIN_REPAIR); +struct ThinRepair; -test_input_file_not_found!(THIN_REPAIR, OPTION); -test_corrupted_input_data!(THIN_REPAIR, OPTION); +impl<'a> Program<'a> for ThinRepair { + fn name() -> &'a str { + "thin_repair" + } -test_missing_output_option!(THIN_REPAIR, mk_valid_md); + fn path() -> &'a str { + THIN_REPAIR + } + + fn usage() -> &'a str { + USAGE + } + + fn arg_type() -> ArgType { + ArgType::IoOptions + } + + fn bad_option_hint(option: &str) -> String { + cpp_msg::bad_option_hint(option) + } +} + +impl<'a> InputProgram<'a> for ThinRepair { + fn mk_valid_input(td: &mut TestDir) -> Result { + mk_valid_md(td) + } + + fn file_not_found() -> &'a str { + cpp_msg::FILE_NOT_FOUND + } + + fn missing_input_arg() -> &'a str { + cpp_msg::MISSING_INPUT_ARG + } + + fn corrupted_input() -> &'a str { + "The following field needs to be provided on the command line due to corruption in the superblock" + } +} + +impl<'a> OutputProgram<'a> for ThinRepair { + fn file_not_found() -> &'a str { + cpp_msg::FILE_NOT_FOUND + } + + fn missing_output_arg() -> &'a str { + cpp_msg::MISSING_OUTPUT_ARG + } +} + +//----------------------------------------- + +test_accepts_help!(ThinRepair); +test_accepts_version!(ThinRepair); +test_rejects_bad_option!(ThinRepair); + +test_input_file_not_found!(ThinRepair); +test_corrupted_input_data!(ThinRepair); + +test_missing_output_option!(ThinRepair); //----------------------------------------- // test output to a small file diff --git a/tests/thin_restore.rs b/tests/thin_restore.rs index 79336e4..5208d71 100644 --- a/tests/thin_restore.rs +++ b/tests/thin_restore.rs @@ -23,15 +23,71 @@ const USAGE: &str = "Usage: thin_restore [options]\n\ //------------------------------------------ -test_accepts_help!(THIN_RESTORE, USAGE); -test_accepts_version!(THIN_RESTORE); +struct ThinRestore; -test_missing_input_option!(THIN_RESTORE); -test_input_file_not_found!(THIN_RESTORE, OPTION); -test_corrupted_input_data!(THIN_RESTORE, OPTION); +impl<'a> Program<'a> for ThinRestore { + fn name() -> &'a str { + "thin_restore" + } -test_missing_output_option!(THIN_RESTORE, mk_valid_xml); -test_tiny_output_file!(THIN_RESTORE, mk_valid_xml); + fn path() -> &'a str { + THIN_RESTORE + } + + fn usage() -> &'a str { + USAGE + } + + fn arg_type() -> ArgType { + ArgType::IoOptions + } + + fn bad_option_hint(option: &str) -> String { + msg::bad_option_hint(option) + } +} + +impl<'a> InputProgram<'a> for ThinRestore { + fn mk_valid_input(td: &mut TestDir) -> Result { + mk_valid_md(td) + } + + fn file_not_found() -> &'a str { + msg::FILE_NOT_FOUND + } + + fn missing_input_arg() -> &'a str { + msg::MISSING_INPUT_ARG + } + + fn corrupted_input() -> &'a str { + "" // we don't intent to verify error messages of XML parsing + } +} + +impl<'a> OutputProgram<'a> for ThinRestore { + fn file_not_found() -> &'a str { + msg::FILE_NOT_FOUND + } + + fn missing_output_arg() -> &'a str { + msg::MISSING_OUTPUT_ARG + } +} + +impl<'a> BinaryOutputProgram<'_> for ThinRestore {} + +//----------------------------------------- + +test_accepts_help!(ThinRestore); +test_accepts_version!(ThinRestore); + +test_missing_input_option!(ThinRestore); +test_input_file_not_found!(ThinRestore); +test_corrupted_input_data!(ThinRestore); + +test_missing_output_option!(ThinRestore); +test_tiny_output_file!(ThinRestore); //----------------------------------------- diff --git a/tests/thin_rmap.rs b/tests/thin_rmap.rs index eab535d..1acae99 100644 --- a/tests/thin_rmap.rs +++ b/tests/thin_rmap.rs @@ -19,9 +19,35 @@ const USAGE: &str = "Usage: thin_rmap [options] {device|file}\n\ //------------------------------------------ -test_accepts_help!(THIN_RMAP, USAGE); -test_accepts_version!(THIN_RMAP); -test_rejects_bad_option!(THIN_RMAP); +struct ThinRmap; + +impl<'a> Program<'a> for ThinRmap { + fn name() -> &'a str { + "thin_rmap" + } + + fn path() -> &'a str { + THIN_RMAP + } + + fn usage() -> &'a str { + USAGE + } + + fn arg_type() -> ArgType { + ArgType::InputArg + } + + fn bad_option_hint(option: &str) -> String { + cpp_msg::bad_option_hint(option) + } +} + +//------------------------------------------ + +test_accepts_help!(ThinRmap); +test_accepts_version!(ThinRmap); +test_rejects_bad_option!(ThinRmap); //------------------------------------------ From 87ada9b4936a5fd26b0b7c469f1af5131981142e Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Thu, 8 Jul 2021 00:31:09 +0800 Subject: [PATCH 6/8] [tests] Add basic tests for cache_dump --- tests/cache_dump.rs | 85 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/cache_dump.rs diff --git a/tests/cache_dump.rs b/tests/cache_dump.rs new file mode 100644 index 0000000..a8f3754 --- /dev/null +++ b/tests/cache_dump.rs @@ -0,0 +1,85 @@ +use anyhow::Result; + +mod common; + +use common::common_args::*; +use common::input_arg::*; +use common::test_dir::*; +use common::*; + +//------------------------------------------ + +const USAGE: &str = "Usage: cache_dump [options] {device|file}\n\ + Options:\n \ + {-h|--help}\n \ + {-o }\n \ + {-V|--version}\n \ + {--repair}"; + +//------------------------------------------ + +struct CacheDump; + +impl<'a> Program<'a> for CacheDump { + fn name() -> &'a str { + "cache_dump" + } + + fn path() -> &'a str { + CACHE_DUMP + } + + fn usage() -> &'a str { + USAGE + } + + fn arg_type() -> ArgType { + ArgType::InputArg + } + + fn bad_option_hint(option: &str) -> String { + msg::bad_option_hint(option) + } +} + +impl<'a> InputProgram<'a> for CacheDump { + fn mk_valid_input(td: &mut TestDir) -> Result { + mk_valid_md(td) + } + + fn file_not_found() -> &'a str { + msg::FILE_NOT_FOUND + } + + fn missing_input_arg() -> &'a str { + msg::MISSING_INPUT_ARG + } + + fn corrupted_input() -> &'a str { + msg::BAD_SUPERBLOCK + } +} + +//------------------------------------------ + +test_accepts_help!(CacheDump); +test_accepts_version!(CacheDump); +test_rejects_bad_option!(CacheDump); + +test_missing_input_arg!(CacheDump); +test_input_file_not_found!(CacheDump); +test_input_cannot_be_a_directory!(CacheDump); +test_unreadable_input_file!(CacheDump); + +//------------------------------------------ + +/* + (define-scenario (cache-dump restore-is-noop) + "cache_dump followed by cache_restore is a noop." + (with-valid-metadata (md) + (run-ok-rcv (d1-stdout _) (cache-dump md) + (with-temp-file-containing ((xml "cache.xml" d1-stdout)) + (run-ok (cache-restore "-i" xml "-o" md)) + (run-ok-rcv (d2-stdout _) (cache-dump md) + (assert-equal d1-stdout d2-stdout)))))) +*/ From 6cecf0f6735153458bc3f6b1de6b81924a507353 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Thu, 8 Jul 2021 01:15:28 +0800 Subject: [PATCH 7/8] [file_utils] Check the file type to prevent unexpected writes by thin_repair --- base/file_utils.cc | 8 ++++++-- tests/thin_repair.rs | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/base/file_utils.cc b/base/file_utils.cc index af7ce2a..638c752 100644 --- a/base/file_utils.cc +++ b/base/file_utils.cc @@ -77,8 +77,12 @@ file_utils::check_file_exists(string const &file, bool must_be_regular_file) { throw runtime_error(msg.str()); } - if (must_be_regular_file && !S_ISREG(info.st_mode)) - throw runtime_error("Not a regular file"); + if (!S_ISREG(info.st_mode)) { + if (must_be_regular_file) + throw runtime_error("Not a regular file"); + if (!S_ISBLK(info.st_mode)) + throw runtime_error("Not a block device or regular file"); + } } file_utils::file_descriptor diff --git a/tests/thin_repair.rs b/tests/thin_repair.rs index 31ecaa8..aa2deba 100644 --- a/tests/thin_repair.rs +++ b/tests/thin_repair.rs @@ -81,6 +81,7 @@ test_accepts_version!(ThinRepair); test_rejects_bad_option!(ThinRepair); test_input_file_not_found!(ThinRepair); +test_input_cannot_be_a_directory!(ThinRepair); test_corrupted_input_data!(ThinRepair); test_missing_output_option!(ThinRepair); From a50c9d97e2e5cdbed062c2001b8480c7c58acc28 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Thu, 8 Jul 2021 11:41:28 +0800 Subject: [PATCH 8/8] [thin_metadata_unpack] Fix truncated output file on invalid input Check the input header before creating or truncating the output file --- src/bin/thin_metadata_unpack.rs | 2 +- src/pack/toplevel.rs | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/bin/thin_metadata_unpack.rs b/src/bin/thin_metadata_unpack.rs index 0664557..94874a3 100644 --- a/src/bin/thin_metadata_unpack.rs +++ b/src/bin/thin_metadata_unpack.rs @@ -39,7 +39,7 @@ fn main() { } if let Err(reason) = thinp::pack::toplevel::unpack(&input_file, &output_file) { - println!("Application error: {}", reason); + eprintln!("Application error: {}", reason); process::exit(1); } } diff --git a/src/pack/toplevel.rs b/src/pack/toplevel.rs index d4adf7d..2298cea 100644 --- a/src/pack/toplevel.rs +++ b/src/pack/toplevel.rs @@ -163,24 +163,28 @@ fn read_header(mut r: R) -> io::Result where R: byteorder::ReadBytesExt, { - use std::process::exit; - let magic = r.read_u64::()?; if magic != MAGIC { - eprintln!("Not a pack file."); - exit(1); + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Not a pack file", + )); } let version = r.read_u64::()?; if version != PACK_VERSION { - eprintln!("unsupported pack file version ({}).", PACK_VERSION); - exit(1); + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("unsupported pack file version ({}).", PACK_VERSION), + )); } let block_size = r.read_u64::()?; if block_size != BLOCK_SIZE { - eprintln!("block size is not {}", BLOCK_SIZE); - exit(1); + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("block size is not {}", BLOCK_SIZE), + )); } r.read_u64::() @@ -270,6 +274,8 @@ pub fn unpack(input_file: &Path, output_file: &Path) -> Result<(), Box Result<(), Box