From f395bab7be9a17370331e16e98b5da91b10d9217 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Tue, 6 Jul 2021 09:51:27 +0800 Subject: [PATCH] [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); //------------------------------------------