[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
This commit is contained in:
		@@ -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(())
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										82
									
								
								tests/common/common_args.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								tests/common/common_args.rs
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//------------------------------------------
 | 
			
		||||
							
								
								
									
										263
									
								
								tests/common/input_arg.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								tests/common/input_arg.rs
									
									
									
									
									
										Normal file
									
								
							@@ -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<F>(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<F>(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<F>(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<F>(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<F>(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<F>(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)
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//------------------------------------------
 | 
			
		||||
@@ -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 <FILE>";
 | 
			
		||||
    pub const MISSING_OUTPUT_ARG: &str =
 | 
			
		||||
        "The following required arguments were not provided\n    -o <FILE>";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//------------------------------------------
 | 
			
		||||
@@ -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::<OsString>::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::<OsString>::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::<OsString>::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::<OsString>::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::<OsString>::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::<OsString>::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::<OsString>::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::<OsString>::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::<OsString>::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::<OsString>::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::<OsString>::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::<OsString>::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<String> {
 | 
			
		||||
    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<String> {
 | 
			
		||||
    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<std::process::Output> {
 | 
			
		||||
    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<String> {
 | 
			
		||||
    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<std::process::Output> {
 | 
			
		||||
    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<PathBuf> {
 | 
			
		||||
    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<PathBuf> {
 | 
			
		||||
    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<PathBuf> {
 | 
			
		||||
    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<bool> {
 | 
			
		||||
    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<PathBuf> {
 | 
			
		||||
    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<PathBuf> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<bool> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn md5(md: &PathBuf) -> Result<String> {
 | 
			
		||||
    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(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//------------------------------------------
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										143
									
								
								tests/common/output_option.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								tests/common/output_option.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
use crate::common::*;
 | 
			
		||||
 | 
			
		||||
//-----------------------------------------
 | 
			
		||||
// test invalid arguments
 | 
			
		||||
 | 
			
		||||
pub fn test_missing_output_option<F>(program: &str, mk_input: F) -> Result<()>
 | 
			
		||||
where
 | 
			
		||||
    F: Fn(&mut TestDir) -> Result<PathBuf>,
 | 
			
		||||
{
 | 
			
		||||
    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<F>(program: &str, mk_input: F) -> Result<()>
 | 
			
		||||
where
 | 
			
		||||
    F: Fn(&mut TestDir) -> Result<PathBuf>,
 | 
			
		||||
{
 | 
			
		||||
    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<F>(program: &str, mk_input: F) -> Result<()>
 | 
			
		||||
where
 | 
			
		||||
    F: Fn(&mut TestDir) -> Result<PathBuf>,
 | 
			
		||||
{
 | 
			
		||||
    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<F>(program: &str, mk_input: F) -> Result<()>
 | 
			
		||||
where
 | 
			
		||||
    F: Fn(&mut TestDir) -> Result<PathBuf>,
 | 
			
		||||
{
 | 
			
		||||
    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<F>(program: &str, mk_input: F) -> Result<()>
 | 
			
		||||
where
 | 
			
		||||
    F: Fn(&mut TestDir) -> Result<PathBuf>,
 | 
			
		||||
{
 | 
			
		||||
    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)
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//-----------------------------------------
 | 
			
		||||
@@ -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(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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] <device or file>\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] <device or file>\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(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//------------------------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <xml file>}\n  \
 | 
			
		||||
                       {--dev-id} <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(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//------------------------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <DEV> -o <FILE>\n\nFLAGS:\n    -h, --help       Prints help information\n    -V, --version    Prints version information\n\nOPTIONS:\n    -i <DEV>         Specify thinp metadata binary device/file\n    -o <FILE>        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 <DEV>")
 | 
			
		||||
    );
 | 
			
		||||
    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 <FILE>"));
 | 
			
		||||
    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 <DEV> -o <FILE>\n\
 | 
			
		||||
     \n\
 | 
			
		||||
     FLAGS:\n    \
 | 
			
		||||
         -h, --help       Prints help information\n    \
 | 
			
		||||
         -V, --version    Prints version information\n\
 | 
			
		||||
     \n\
 | 
			
		||||
     OPTIONS:\n    \
 | 
			
		||||
         -i <DEV>         Specify thinp metadata binary device/file\n    \
 | 
			
		||||
         -o <FILE>        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);
 | 
			
		||||
 | 
			
		||||
//------------------------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <DEV> -o <FILE>\n\
 | 
			
		||||
     \n\
 | 
			
		||||
     FLAGS:\n    \
 | 
			
		||||
         -h, --help       Prints help information\n    \
 | 
			
		||||
         -V, --version    Prints version information\n\
 | 
			
		||||
     \n\
 | 
			
		||||
     OPTIONS:\n    \
 | 
			
		||||
         -i <DEV>         Specify thinp metadata binary device/file\n    \
 | 
			
		||||
         -o <FILE>        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 <DEV> -o <FILE>\n\nFLAGS:\n    -h, --help       Prints help information\n    -V, --version    Prints version information\n\nOPTIONS:\n    -i <DEV>         Specify thinp metadata binary device/file\n    -o <FILE>        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 <DEV>")
 | 
			
		||||
    );
 | 
			
		||||
    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 <FILE>"));
 | 
			
		||||
    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(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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} <input metadata (binary format)>\n  \
 | 
			
		||||
                       {-o|--output} <output metadata (binary format)>\n  \
 | 
			
		||||
                       {--transaction-id} <natural>\n  \
 | 
			
		||||
                       {--data-block-size} <natural>\n  \
 | 
			
		||||
                       {--nr-data-blocks} <natural>\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} <input metadata (binary format)>\n  {-o|--output} <output metadata (binary format)>\n  {--transaction-id} <natural>\n  {--data-block-size} <natural>\n  {--nr-data-blocks} <natural>\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",
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//-----------------------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -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} <input xml file>\n  \
 | 
			
		||||
                       {-o|--output} <output device or file>\n  \
 | 
			
		||||
                       {--transaction-id} <natural>\n  \
 | 
			
		||||
                       {--data-block-size} <natural>\n  \
 | 
			
		||||
                       {--nr-data-blocks} <natural>\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} <input xml file>\n  {-o|--output} <output device or file>\n  {--transaction-id} <natural>\n  {--data-block-size} <natural>\n  {--nr-data-blocks} <natural>\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\"")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//-----------------------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <block range>}*\n\
 | 
			
		||||
                     Where:\n  \
 | 
			
		||||
                       <block range> is of the form <begin>..<one-past-the-end>\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 <block range>}*\nWhere:\n  <block range> is of the form <begin>..<one-past-the-end>\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(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user