[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:
Ming-Hung Tsai 2021-07-01 22:27:37 +08:00
parent 6660fde3c4
commit 12ef69c31b
13 changed files with 1077 additions and 784 deletions

View File

@ -1,89 +1,47 @@
use anyhow::Result; use anyhow::Result;
use duct::cmd;
use thinp::version::tools_version;
mod common; mod common;
use common::common_args::*;
use common::input_arg::*;
use common::test_dir::*; use common::test_dir::*;
use common::*; use common::*;
//------------------------------------------ //------------------------------------------
#[test] const USAGE: &str = "Usage: cache_check [options] {device|file}\n\
fn accepts_v() -> Result<()> { Options:\n \
let stdout = cache_check!("-V").read()?; {-q|--quiet}\n \
assert!(stdout.contains(tools_version())); {-h|--help}\n \
Ok(()) {-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] test_missing_input_arg!(CACHE_CHECK);
fn accepts_h() -> Result<()> { test_input_file_not_found!(CACHE_CHECK, ARG);
let stdout = cache_check!("-h").read()?; test_input_cannot_be_a_directory!(CACHE_CHECK, ARG);
assert_eq!(stdout, USAGE); test_unreadable_input_file!(CACHE_CHECK, ARG);
Ok(())
}
#[test] test_help_message_for_tiny_input_file!(CACHE_CHECK, ARG);
fn accepts_help() -> Result<()> { test_spot_xml_data!(CACHE_CHECK, "cache_check", ARG);
let stdout = cache_check!("--help").read()?; test_corrupted_input_data!(CACHE_CHECK, ARG);
assert_eq!(stdout, USAGE);
Ok(())
}
#[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] #[test]
fn failing_q() -> Result<()> { fn failing_q() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_zeroed_md(&mut td)?; let md = mk_zeroed_md(&mut td)?;
let output = cache_check!("-q", &md).unchecked().run()?; let output = run_fail_raw(CACHE_CHECK, &["-q", md.to_str().unwrap()])?;
assert!(!output.status.success());
assert_eq!(output.stdout.len(), 0); assert_eq!(output.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0); assert_eq!(output.stderr.len(), 0);
Ok(()) Ok(())
@ -93,8 +51,7 @@ fn failing_q() -> Result<()> {
fn failing_quiet() -> Result<()> { fn failing_quiet() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_zeroed_md(&mut td)?; let md = mk_zeroed_md(&mut td)?;
let output = cache_check!("--quiet", &md).unchecked().run()?; let output = run_fail_raw(CACHE_CHECK, &["--quiet", md.to_str().unwrap()])?;
assert!(!output.status.success());
assert_eq!(output.stdout.len(), 0); assert_eq!(output.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0); assert_eq!(output.stderr.len(), 0);
Ok(()) Ok(())

View 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
View 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)
}
};
}
//------------------------------------------

View File

@ -1,15 +1,16 @@
#![allow(dead_code)] #![allow(dead_code)]
use anyhow::Result; use anyhow::Result;
use duct::{cmd, Expression};
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::from_utf8;
use thinp::file_utils; use thinp::file_utils;
use thinp::io_engine::*; use thinp::io_engine::*;
pub mod cache_xml_generator; pub mod cache_xml_generator;
pub mod common_args;
pub mod input_arg;
pub mod output_option;
pub mod test_dir; pub mod test_dir;
pub mod thin_xml_generator; pub mod thin_xml_generator;
@ -28,8 +29,10 @@ pub mod msg {
#[cfg(feature = "rust_tests")] #[cfg(feature = "rust_tests")]
pub mod msg { pub mod msg {
pub const FILE_NOT_FOUND: &str = "Couldn't find input file"; 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_INPUT_ARG: &str =
pub const MISSING_OUTPUT_ARG: &str = "The following required arguments were not provided"; "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] pub const CACHE_CHECK: &str = path_to!("cache_check");
macro_rules! thin_restore { pub const CACHE_DUMP: &str = path_to!("cache_dump");
( $( $arg: expr ),* ) => {
{
use std::ffi::OsString;
let args: &[OsString] = &[$( Into::<OsString>::into($arg) ),*];
duct::cmd(path_to!("thin_restore"), args).stdout_capture().stderr_capture()
}
};
}
#[macro_export] pub const THIN_CHECK: &str = path_to!("thin_check");
macro_rules! thin_dump { pub const THIN_DELTA: &str = path_to_cpp!("thin_delta"); // TODO: rust version
( $( $arg: expr ),* ) => { pub const THIN_DUMP: &str = path_to!("thin_dump");
{ pub const THIN_METADATA_PACK: &str = path_to_rust!("thin_metadata_pack"); // rust-only
use std::ffi::OsString; pub const THIN_METADATA_UNPACK: &str = path_to_rust!("thin_metadata_unpack"); // rust-only
let args: &[OsString] = &[$( Into::<OsString>::into($arg) ),*]; pub const THIN_REPAIR: &str = path_to_cpp!("thin_repair"); // TODO: rust version
duct::cmd(path_to!("thin_dump"), args).stdout_capture().stderr_capture() 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
#[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()
}
};
}
//------------------------------------------ //------------------------------------------
// Returns stderr, a non zero status must be returned // Returns stdout. The command must return zero.
pub fn run_fail(command: Expression) -> Result<String> { pub fn run_ok(program: &str, args: &[&str]) -> Result<String> {
let output = command.stderr_capture().unchecked().run()?; let command = duct::cmd(program, args).stdout_capture().stderr_capture();
assert!(!output.status.success()); let output = command.run()?;
Ok(from_utf8(&output.stderr[0..]).unwrap().to_string()) 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> { pub fn mk_valid_xml(td: &mut TestDir) -> Result<PathBuf> {
let xml = td.mk_path("meta.xml"); let xml = td.mk_path("meta.xml");
let mut gen = SingleThinS::new(0, 1024, 2048, 2048); 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)?; write_xml(&xml, &mut gen)?;
let _file = file_utils::create_sized_file(&md, 4096 * 4096); 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) Ok(md)
} }
@ -234,13 +153,6 @@ pub fn mk_zeroed_md(td: &mut TestDir) -> Result<PathBuf> {
Ok(md) 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> { pub fn superblock_all_zeroes(path: &PathBuf) -> Result<bool> {
let mut input = OpenOptions::new().read(true).write(false).open(path)?; let mut input = OpenOptions::new().read(true).write(false).open(path)?;
let mut buf = vec![0; 4096]; let mut buf = vec![0; 4096];
@ -266,50 +178,58 @@ pub fn damage_superblock(path: &PathBuf) -> Result<()> {
// FIXME: replace mk_valid_md with this? // FIXME: replace mk_valid_md with this?
pub fn prep_metadata(td: &mut TestDir) -> Result<PathBuf> { pub fn prep_metadata(td: &mut TestDir) -> Result<PathBuf> {
let md = mk_zeroed_md(td)?; 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 // Create a 2GB device
thin_generate_metadata!("-o", &md, "--create-thin", "1").run()?; let args = ["-o", md.to_str().unwrap(), "--create-thin", "1"];
thin_generate_mappings!( run_ok(THIN_GENERATE_METADATA, &args)?;
let args = [
"-o", "-o",
&md, md.to_str().unwrap(),
"--dev-id", "--dev-id",
"1", "1",
"--size", "--size",
format!("{}", 1024 * 1024 * 2), "2097152",
"--rw=randwrite", "--rw=randwrite",
"--seq-nr=16" "--seq-nr=16",
) ];
.run()?; run_ok(THIN_GENERATE_MAPPINGS, &args)?;
// Take a few snapshots. // Take a few snapshots.
let mut snap_id = 2; let mut snap_id = 2;
for _i in 0..10 { for _i in 0..10 {
// take a snapshot // take a snapshot
thin_generate_metadata!( let args = [
"-o", "-o",
&md, md.to_str().unwrap(),
"--create-snap", "--create-snap",
format!("{}", snap_id), &snap_id.to_string(),
"--origin", "--origin",
"1" "1",
) ];
.run()?; run_ok(THIN_GENERATE_METADATA, &args)?;
// partially overwrite the origin (64MB) // partially overwrite the origin (64MB)
thin_generate_mappings!( let args = [
"-o", "-o",
&md, md.to_str().unwrap(),
"--dev-id", "--dev-id",
format!("{}", 1), "1",
"--size", "--size",
format!("{}", 1024 * 1024 * 2), "2097152",
"--io-size", "--io-size",
format!("{}", 64 * 1024 * 2), "131072",
"--rw=randwrite", "--rw=randwrite",
"--seq-nr=16" "--seq-nr=16",
) ];
.run()?; run_ok(THIN_GENERATE_MAPPINGS, &args)?;
snap_id += 1; snap_id += 1;
} }
@ -317,7 +237,8 @@ pub fn prep_metadata(td: &mut TestDir) -> Result<PathBuf> {
} }
pub fn set_needs_check(md: &PathBuf) -> Result<()> { pub fn set_needs_check(md: &PathBuf) -> Result<()> {
thin_generate_metadata!("-o", &md, "--set-needs-check").run()?; let args = ["-o", md.to_str().unwrap(), "--set-needs-check"];
run_ok(THIN_GENERATE_METADATA, &args)?;
Ok(()) Ok(())
} }
@ -327,21 +248,19 @@ pub fn generate_metadata_leaks(
expected: u32, expected: u32,
actual: u32, actual: u32,
) -> Result<()> { ) -> Result<()> {
let output = thin_generate_damage!( let args = [
"-o", "-o",
&md, md.to_str().unwrap(),
"--create-metadata-leaks", "--create-metadata-leaks",
"--nr-blocks", "--nr-blocks",
format!("{}", nr_blocks), &nr_blocks.to_string(),
"--expected", "--expected",
format!("{}", expected), &expected.to_string(),
"--actual", "--actual",
format!("{}", actual) &actual.to_string(),
) ];
.unchecked() run_ok(THIN_GENERATE_DAMAGE, &args)?;
.run()?;
assert!(output.status.success());
Ok(()) Ok(())
} }
@ -354,7 +273,7 @@ pub fn get_needs_check(md: &PathBuf) -> Result<bool> {
} }
pub fn md5(md: &PathBuf) -> Result<String> { 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 = std::str::from_utf8(&output.stdout[0..])?.to_string();
let csum = csum.split_ascii_whitespace().next().unwrap().to_string(); let csum = csum.split_ascii_whitespace().next().unwrap().to_string();
Ok(csum) Ok(csum)
@ -371,3 +290,5 @@ where
assert_eq!(csum, md5(p)?); assert_eq!(csum, md5(p)?);
Ok(()) Ok(())
} }
//------------------------------------------

View 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)
}
};
}
//-----------------------------------------

View File

@ -1,49 +1,50 @@
use anyhow::Result; use anyhow::Result;
use thinp::file_utils;
use thinp::version::tools_version;
mod common; mod common;
use common::common_args::*;
use common::input_arg::*;
use common::test_dir::*; use common::test_dir::*;
use common::thin_xml_generator::{write_xml, FragmentedS};
use common::*; use common::*;
//------------------------------------------ //------------------------------------------
#[test] const USAGE: &str = "Usage: thin_check [options] {device|file}\n\
fn accepts_v() -> Result<()> { Options:\n \
let stdout = thin_check!("-V").read()?; {-q|--quiet}\n \
assert!(stdout.contains(tools_version())); {-h|--help}\n \
Ok(()) {-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] test_missing_input_arg!(THIN_CHECK);
fn accepts_h() -> Result<()> { test_input_file_not_found!(THIN_CHECK, ARG);
let stdout = thin_check!("-h").read()?; test_input_cannot_be_a_directory!(THIN_CHECK, ARG);
assert_eq!(stdout, USAGE); test_unreadable_input_file!(THIN_CHECK, ARG);
Ok(())
}
#[test] test_help_message_for_tiny_input_file!(THIN_CHECK, ARG);
fn accepts_help() -> Result<()> { test_spot_xml_data!(THIN_CHECK, "thin_check", ARG);
let stdout = thin_check!("--help").read()?; test_corrupted_input_data!(THIN_CHECK, ARG);
assert_eq!(stdout, USAGE);
Ok(())
}
#[test] //------------------------------------------
fn rejects_bad_option() -> Result<()> { // test exclusive flags
let stderr = run_fail(thin_check!("--hedgehogs-only"))?;
assert!(stderr.contains("unrecognized option \'--hedgehogs-only\'")); 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(()) Ok(())
} }
@ -67,86 +68,74 @@ fn accepts_clear_needs_check_flag() -> Result<()> {
accepts_flag("--clear-needs-check-flag") accepts_flag("--clear-needs-check-flag")
} }
#[test]
fn accepts_auto_repair() -> Result<()> {
accepts_flag("--auto-repair")
}
//------------------------------------------
// test the --quiet flag
#[test] #[test]
fn accepts_quiet() -> Result<()> { fn accepts_quiet() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; let md = mk_valid_md(&mut td)?;
let md_path = md.to_str().unwrap();
let output = thin_check!("--quiet", &md).run()?; let output = run_ok_raw(THIN_CHECK, &["--quiet", md_path])?;
assert!(output.status.success());
assert_eq!(output.stdout.len(), 0); assert_eq!(output.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0); assert_eq!(output.stderr.len(), 0);
Ok(()) Ok(())
} }
#[test] //------------------------------------------
fn accepts_auto_repair() -> Result<()> { // test superblock-block-only
accepts_flag("--auto-repair")
}
#[test] #[test]
fn detects_corrupt_superblock_with_superblock_only() -> Result<()> { fn detects_corrupt_superblock_with_superblock_only() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_zeroed_md(&mut td)?; let md = mk_zeroed_md(&mut td)?;
let output = thin_check!("--super-block-only", &md).unchecked().run()?; let md_path = md.to_str().unwrap();
assert!(!output.status.success()); let _stderr = run_fail(THIN_CHECK, &["--super-block-only", md_path])?;
Ok(()) Ok(())
} }
#[test] //------------------------------------------
fn prints_help_message_for_tiny_metadata() -> Result<()> { // test info outputs
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] #[test]
fn prints_info_fields() -> Result<()> { fn prints_info_fields() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; 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("TRANSACTION_ID="));
assert!(stdout.contains("METADATA_FREE_BLOCKS=")); assert!(stdout.contains("METADATA_FREE_BLOCKS="));
Ok(()) Ok(())
} }
//------------------------------------------
// test compatibility between options
#[test] #[test]
fn auto_repair_incompatible_opts() -> Result<()> { fn auto_repair_incompatible_opts() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; let md = mk_valid_md(&mut td)?;
run_fail(thin_check!("--auto-repair", "-m", &md))?; let md_path = md.to_str().unwrap();
run_fail(thin_check!( run_fail(THIN_CHECK, &["--auto-repair", "-m", md_path])?;
"--auto-repair", run_fail(
"--override-mapping-root", THIN_CHECK,
"123", &["--auto-repair", "--override-mapping-root", "123", md_path],
&md )?;
))?; run_fail(
run_fail(thin_check!("--auto-repair", "--super-block-only", &md))?; THIN_CHECK,
run_fail(thin_check!("--auto-repair", "--skip-mappings", &md))?; &["--auto-repair", "--super-block-only", md_path],
run_fail(thin_check!( )?;
"--auto-repair", run_fail(THIN_CHECK, &["--auto-repair", "--skip-mappings", md_path])?;
"--ignore-non-fatal-errors", run_fail(
&md THIN_CHECK,
))?; &["--auto-repair", "--ignore-non-fatal-errors", md_path],
)?;
Ok(()) Ok(())
} }
@ -154,36 +143,45 @@ fn auto_repair_incompatible_opts() -> Result<()> {
fn clear_needs_check_incompatible_opts() -> Result<()> { fn clear_needs_check_incompatible_opts() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; let md = mk_valid_md(&mut td)?;
run_fail(thin_check!("--clear-needs-check-flag", "-m", &md))?; let md_path = md.to_str().unwrap();
run_fail(thin_check!( run_fail(THIN_CHECK, &["--clear-needs-check-flag", "-m", md_path])?;
"--clear-needs-check-flag", run_fail(
"--override-mapping-root", THIN_CHECK,
"123", &[
&md "--clear-needs-check-flag",
))?; "--override-mapping-root",
run_fail(thin_check!( "123",
"--clear-needs-check-flag", md_path,
"--super-block-only", ],
&md )?;
))?; run_fail(
run_fail(thin_check!( THIN_CHECK,
"--clear-needs-check-flag", &["--clear-needs-check-flag", "--super-block-only", md_path],
"--ignore-non-fatal-errors", )?;
&md run_fail(
))?; THIN_CHECK,
&[
"--clear-needs-check-flag",
"--ignore-non-fatal-errors",
md_path,
],
)?;
Ok(()) Ok(())
} }
//------------------------------------------ //------------------------------------------
// test clear-needs-check
#[test] #[test]
fn clear_needs_check() -> Result<()> { fn clear_needs_check() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?; let md = prep_metadata(&mut td)?;
let md_path = md.to_str().unwrap();
set_needs_check(&md)?; set_needs_check(&md)?;
assert!(get_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)?); assert!(!get_needs_check(&md)?);
Ok(()) Ok(())
} }
@ -192,9 +190,11 @@ fn clear_needs_check() -> Result<()> {
fn no_clear_needs_check_if_error() -> Result<()> { fn no_clear_needs_check_if_error() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?; let md = prep_metadata(&mut td)?;
let md_path = md.to_str().unwrap();
set_needs_check(&md)?; set_needs_check(&md)?;
generate_metadata_leaks(&md, 1, 0, 1)?; 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)?); assert!(get_needs_check(&md)?);
Ok(()) Ok(())
} }
@ -203,22 +203,31 @@ fn no_clear_needs_check_if_error() -> Result<()> {
fn clear_needs_check_if_skip_mappings() -> Result<()> { fn clear_needs_check_if_skip_mappings() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?; let md = prep_metadata(&mut td)?;
let md_path = md.to_str().unwrap();
set_needs_check(&md)?; set_needs_check(&md)?;
generate_metadata_leaks(&md, 1, 0, 1)?; generate_metadata_leaks(&md, 1, 0, 1)?;
assert!(get_needs_check(&md)?); 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)?); assert!(!get_needs_check(&md)?);
Ok(()) Ok(())
} }
//------------------------------------------
// test ignore-non-fatal-errors
#[test] #[test]
fn metadata_leaks_are_non_fatal() -> Result<()> { fn metadata_leaks_are_non_fatal() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?; let md = prep_metadata(&mut td)?;
let md_path = md.to_str().unwrap();
generate_metadata_leaks(&md, 1, 0, 1)?; generate_metadata_leaks(&md, 1, 0, 1)?;
run_fail(thin_check!(&md))?; run_fail(THIN_CHECK, &[md_path])?;
thin_check!("--ignore-non-fatal-errors", &md).run()?; run_ok(THIN_CHECK, &["--ignore-non-fatal-errors", md_path])?;
Ok(()) Ok(())
} }
@ -226,28 +235,34 @@ fn metadata_leaks_are_non_fatal() -> Result<()> {
fn fatal_errors_cant_be_ignored() -> Result<()> { fn fatal_errors_cant_be_ignored() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?; let md = prep_metadata(&mut td)?;
let md_path = md.to_str().unwrap();
generate_metadata_leaks(&md, 1, 1, 0)?; generate_metadata_leaks(&md, 1, 1, 0)?;
ensure_untouched(&md, || { ensure_untouched(&md, || {
run_fail(thin_check!("--ignore-non-fatal-errors", &md))?; run_fail(THIN_CHECK, &["--ignore-non-fatal-errors", md_path])?;
Ok(()) Ok(())
}) })
} }
//------------------------------------------
// test auto-repair
#[test] #[test]
fn auto_repair() -> Result<()> { fn auto_repair() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?; let md = prep_metadata(&mut td)?;
let md_path = md.to_str().unwrap();
// auto-repair should have no effect on good metadata. // auto-repair should have no effect on good metadata.
ensure_untouched(&md, || { ensure_untouched(&md, || {
thin_check!("--auto-repair", &md).run()?; run_ok(THIN_CHECK, &["--auto-repair", md_path])?;
Ok(()) Ok(())
})?; })?;
generate_metadata_leaks(&md, 16, 0, 1)?; generate_metadata_leaks(&md, 16, 0, 1)?;
run_fail(thin_check!(&md))?; run_fail(THIN_CHECK, &[md_path])?;
thin_check!("--auto-repair", &md).run()?; run_ok(THIN_CHECK, &["--auto-repair", md_path])?;
thin_check!(&md).run()?; run_ok(THIN_CHECK, &[md_path])?;
Ok(()) Ok(())
} }
@ -255,10 +270,11 @@ fn auto_repair() -> Result<()> {
fn auto_repair_has_limits() -> Result<()> { fn auto_repair_has_limits() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?; 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, || { ensure_untouched(&md, || {
run_fail(thin_check!("--auto-repair", &md))?; run_fail(THIN_CHECK, &["--auto-repair", md_path])?;
Ok(()) Ok(())
})?; })?;
Ok(()) Ok(())
@ -268,8 +284,10 @@ fn auto_repair_has_limits() -> Result<()> {
fn auto_repair_clears_needs_check() -> Result<()> { fn auto_repair_clears_needs_check() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = prep_metadata(&mut td)?; let md = prep_metadata(&mut td)?;
let md_path = md.to_str().unwrap();
set_needs_check(&md)?; set_needs_check(&md)?;
thin_check!("--auto-repair", &md).run()?; run_ok(THIN_CHECK, &["--auto-repair", md_path])?;
assert!(!get_needs_check(&md)?); assert!(!get_needs_check(&md)?);
Ok(()) Ok(())
} }

View File

@ -1,54 +1,35 @@
use anyhow::Result; use anyhow::Result;
use thinp::version::tools_version;
mod common; mod common;
use common::common_args::*;
use common::test_dir::*; use common::test_dir::*;
use common::*; use common::*;
//------------------------------------------ //------------------------------------------
#[test] const USAGE: &str = "Usage: thin_delta [options] <device or file>\n\
fn accepts_v() -> Result<()> { Options:\n \
let stdout = thin_delta!("-V").read()?; {--thin1, --snap1, --root1}\n \
assert!(stdout.contains(tools_version())); {--thin2, --snap2, --root2}\n \
Ok(()) {-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] #[test]
fn snap1_unspecified() -> Result<()> { fn snap1_unspecified() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; 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")); assert!(stderr.contains("--snap1 or --root1 not specified"));
Ok(()) Ok(())
} }
@ -57,15 +38,17 @@ fn snap1_unspecified() -> Result<()> {
fn snap2_unspecified() -> Result<()> { fn snap2_unspecified() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; 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")); assert!(stderr.contains("--snap2 or --root2 not specified"));
Ok(()) Ok(())
} }
#[test] #[test]
fn dev_unspecified() -> Result<()> { 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 // TODO: replace with msg::MISSING_INPUT_ARG once the rust version is ready
assert!(stderr.contains("No input file provided")); assert!(stderr.contains("No input file provided"));
Ok(()) Ok(())
} }
//------------------------------------------

View File

@ -2,29 +2,48 @@ use anyhow::Result;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Write; use std::io::Write;
use std::str::from_utf8; use std::str::from_utf8;
use thinp::file_utils;
mod common; mod common;
use common::common_args::*;
use common::input_arg::*;
use common::test_dir::*; use common::test_dir::*;
use common::*; use common::*;
//------------------------------------------ //------------------------------------------
#[test] const USAGE: &str = "Usage: thin_dump [options] {device|file}\n\
fn small_input_file() -> Result<()> { Options:\n \
let mut td = TestDir::new()?; {-h|--help}\n \
let md = td.mk_path("meta.bin"); {-f|--format} {xml|human_readable|custom}\n \
file_utils::create_sized_file(&md, 512)?; {-r|--repair}\n \
let _stderr = run_fail(thin_dump!(&md))?; {-m|--metadata-snap} [block#]\n \
Ok(()) {-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] #[test]
fn dump_restore_cycle() -> Result<()> { fn dump_restore_cycle() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; 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 xml = td.mk_path("meta.xml");
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
@ -36,29 +55,40 @@ fn dump_restore_cycle() -> Result<()> {
drop(file); drop(file);
let md2 = mk_zeroed_md(&mut td)?; 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); assert_eq!(output.stdout, output2.stdout);
Ok(()) Ok(())
} }
//------------------------------------------
// test no stderr with a normal dump
#[test] #[test]
fn no_stderr() -> Result<()> { fn no_stderr() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; 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); assert_eq!(output.stderr.len(), 0);
Ok(()) Ok(())
} }
//------------------------------------------
// test superblock overriding & repair
// TODO: share with thin_repair
fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> { fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; 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_eq!(output.stderr.len(), 0);
assert!(from_utf8(&output.stdout[0..])?.contains(pattern)); 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\"") override_something("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"")
} }
// FIXME: duplicate with superblock_succeeds in thin_repair.rs
#[test] #[test]
fn repair_superblock() -> Result<()> { fn repair_superblock() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; let md = mk_valid_md(&mut td)?;
let before = thin_dump!( let before = run_ok_raw(
"--transaction-id=5", THIN_DUMP,
"--data-block-size=128", &[
"--nr-data-blocks=4096000", "--transaction-id=5",
&md "--data-block-size=128",
) "--nr-data-blocks=4096000",
.run()?; md.to_str().unwrap(),
],
)?;
damage_superblock(&md)?; damage_superblock(&md)?;
let after = thin_dump!( let after = run_ok_raw(
"--repair", THIN_DUMP,
"--transaction-id=5", &[
"--data-block-size=128", "--repair",
"--nr-data-blocks=4096000", "--transaction-id=5",
&md "--data-block-size=128",
) "--nr-data-blocks=4096000",
.run()?; md.to_str().unwrap(),
],
)?;
assert_eq!(after.stderr.len(), 0); assert_eq!(after.stderr.len(), 0);
assert_eq!(before.stdout, after.stdout); assert_eq!(before.stdout, after.stdout);
Ok(()) Ok(())
} }
//------------------------------------------
// test compatibility between options
// TODO: share with thin_repair
#[test] #[test]
fn missing_transaction_id() -> Result<()> { fn missing_transaction_id() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; let md = mk_valid_md(&mut td)?;
damage_superblock(&md)?; damage_superblock(&md)?;
let stderr = run_fail(thin_dump!( let stderr = run_fail(
"--repair", THIN_DUMP,
"--data-block-size=128", &[
"--nr-data-blocks=4096000", "--repair",
&md "--data-block-size=128",
))?; "--nr-data-blocks=4096000",
md.to_str().unwrap(),
],
)?;
assert!(stderr.contains("transaction id")); assert!(stderr.contains("transaction id"));
Ok(()) Ok(())
} }
@ -127,12 +169,15 @@ fn missing_data_block_size() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; let md = mk_valid_md(&mut td)?;
damage_superblock(&md)?; damage_superblock(&md)?;
let stderr = run_fail(thin_dump!( let stderr = run_fail(
"--repair", THIN_DUMP,
"--transaction-id=5", &[
"--nr-data-blocks=4096000", "--repair",
&md "--transaction-id=5",
))?; "--nr-data-blocks=4096000",
md.to_str().unwrap(),
],
)?;
assert!(stderr.contains("data block size")); assert!(stderr.contains("data block size"));
Ok(()) Ok(())
} }
@ -142,12 +187,17 @@ fn missing_nr_data_blocks() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; let md = mk_valid_md(&mut td)?;
damage_superblock(&md)?; damage_superblock(&md)?;
let stderr = run_fail(thin_dump!( let stderr = run_fail(
"--repair", THIN_DUMP,
"--transaction-id=5", &[
"--data-block-size=128", "--repair",
&md "--transaction-id=5",
))?; "--data-block-size=128",
md.to_str().unwrap(),
],
)?;
assert!(stderr.contains("nr data blocks")); assert!(stderr.contains("nr data blocks"));
Ok(()) Ok(())
} }
//------------------------------------------

View File

@ -1,77 +1,39 @@
use anyhow::Result; use anyhow::Result;
use thinp::version::tools_version;
mod common; mod common;
use common::test_dir::*;
use common::common_args::*;
use common::input_arg::*;
use common::output_option::*;
use common::*; use common::*;
//------------------------------------------ //------------------------------------------
#[test] const USAGE: &str = concat!(
fn accepts_v() -> Result<()> { "thin_metadata_pack ",
let stdout = thin_metadata_pack!("-V").read()?; include_str!("../VERSION"),
assert!(stdout.contains(tools_version())); "Produces a compressed file of thin metadata. Only packs metadata blocks that are actually used.\n\
Ok(()) \n\
} USAGE:\n \
thin_metadata_pack -i <DEV> -o <FILE>\n\
#[test] \n\
fn accepts_version() -> Result<()> { FLAGS:\n \
let stdout = thin_metadata_pack!("--version").read()?; -h, --help Prints help information\n \
assert!(stdout.contains(tools_version())); -V, --version Prints version information\n\
Ok(()) \n\
} OPTIONS:\n \
-i <DEV> Specify thinp metadata binary device/file\n \
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"; -o <FILE> Specify packed output file"
);
#[test]
fn accepts_h() -> Result<()> { //------------------------------------------
let stdout = thin_metadata_pack!("-h").read()?;
assert_eq!(stdout, USAGE); test_accepts_help!(THIN_METADATA_PACK, USAGE);
Ok(()) test_accepts_version!(THIN_METADATA_PACK);
} test_rejects_bad_option!(THIN_METADATA_PACK);
#[test] test_missing_input_option!(THIN_METADATA_PACK);
fn accepts_help() -> Result<()> { test_missing_output_option!(THIN_METADATA_PACK, mk_valid_md);
let stdout = thin_metadata_pack!("--help").read()?; test_input_file_not_found!(THIN_METADATA_PACK, OPTION);
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(())
}
//------------------------------------------ //------------------------------------------

View File

@ -1,98 +1,64 @@
use anyhow::Result; use anyhow::Result;
use thinp::version::tools_version;
mod common; mod common;
use common::common_args::*;
use common::input_arg::*;
use common::output_option::*;
use common::test_dir::*; use common::test_dir::*;
use common::*; use common::*;
//------------------------------------------ //------------------------------------------
#[test] const USAGE: &str = concat!(
fn accepts_v() -> Result<()> { "thin_metadata_unpack ",
let stdout = thin_metadata_unpack!("-V").read()?; include_str!("../VERSION"),
assert!(stdout.contains(tools_version())); "Unpack a compressed file of thin metadata.\n\
Ok(()) \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] test_missing_input_option!(THIN_METADATA_PACK);
fn accepts_h() -> Result<()> { test_input_file_not_found!(THIN_METADATA_UNPACK, OPTION);
let stdout = thin_metadata_unpack!("-h").read()?; test_corrupted_input_data!(THIN_METADATA_UNPACK, OPTION);
assert_eq!(stdout, USAGE);
Ok(())
}
#[test] test_missing_output_option!(THIN_METADATA_UNPACK, mk_valid_md);
fn accepts_help() -> Result<()> {
let stdout = thin_metadata_unpack!("--help").read()?;
assert_eq!(stdout, USAGE);
Ok(())
}
#[test] //------------------------------------------
fn rejects_bad_option() -> Result<()> {
let stderr = run_fail(thin_metadata_unpack!("--hedgehogs-only"))?;
assert!(stderr.contains("Found argument \'--hedgehogs-only\'"));
Ok(())
}
#[test] // TODO: share with thin_restore/cache_restore/era_restore
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(())
}
#[test] #[test]
fn end_to_end() -> Result<()> { fn end_to_end() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md_in = mk_valid_md(&mut td)?; let md_in = mk_valid_md(&mut td)?;
let md_out = mk_zeroed_md(&mut td)?; let md_out = mk_zeroed_md(&mut td)?;
thin_metadata_pack!("-i", &md_in, "-o", "meta.pack").run()?; run_ok(
thin_metadata_unpack!("-i", "meta.pack", "-o", &md_out).run()?; 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 dump1 = run_ok(THIN_DUMP, &[md_in.to_str().unwrap()])?;
let dump2 = thin_dump!(&md_out).read()?; let dump2 = run_ok(THIN_DUMP, &[md_out.to_str().unwrap()])?;
assert_eq!(dump1, dump2); assert_eq!(dump1, dump2);
Ok(()) Ok(())
} }

View File

@ -1,91 +1,67 @@
use anyhow::Result; use anyhow::Result;
use std::str::from_utf8;
use thinp::version::tools_version;
mod common; mod common;
use common::common_args::*;
use common::input_arg::*;
use common::output_option::*;
use common::test_dir::*; use common::test_dir::*;
use common::*; use common::*;
//------------------------------------------ //------------------------------------------
#[test] const USAGE: &str = "Usage: thin_repair [options] {device|file}\n\
fn accepts_v() -> Result<()> { Options:\n \
let stdout = thin_repair!("-V").read()?; {-h|--help}\n \
assert!(stdout.contains(tools_version())); {-i|--input} <input metadata (binary format)>\n \
Ok(()) {-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] test_input_file_not_found!(THIN_REPAIR, OPTION);
fn accepts_h() -> Result<()> { test_corrupted_input_data!(THIN_REPAIR, OPTION);
let stdout = thin_repair!("-h").read()?;
assert_eq!(stdout, USAGE);
Ok(())
}
#[test] test_missing_output_option!(THIN_REPAIR, mk_valid_md);
fn accepts_help() -> Result<()> {
let stdout = thin_repair!("--help").read()?; //-----------------------------------------
assert_eq!(stdout, USAGE); // test output to a small file
Ok(())
} // TODO: share with thin_restore
#[test] #[test]
fn dont_repair_xml() -> Result<()> { fn dont_repair_xml() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_zeroed_md(&mut td)?; let md = mk_zeroed_md(&mut td)?;
let xml = mk_valid_xml(&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(()) 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] // TODO: share with thin_dump
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(())
}
fn override_thing(flag: &str, val: &str, pattern: &str) -> Result<()> { fn override_thing(flag: &str, val: &str, pattern: &str) -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md1 = mk_valid_md(&mut td)?; let md1 = mk_valid_md(&mut td)?;
let md2 = mk_zeroed_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); assert_eq!(output.stderr.len(), 0);
let output = thin_dump!(&md2).run()?; let md2_path = md2.to_str().unwrap();
assert!(from_utf8(&output.stdout[0..])?.contains(pattern)); let output = run_ok(THIN_DUMP, &[md2_path])?;
assert!(output.contains(pattern));
Ok(()) Ok(())
} }
@ -104,42 +80,63 @@ fn override_nr_data_blocks() -> Result<()> {
override_thing("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"") override_thing("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"")
} }
// FIXME: that's repair_superblock in thin_dump.rs
#[test] #[test]
fn superblock_succeeds() -> Result<()> { fn superblock_succeeds() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md1 = mk_valid_md(&mut td)?; let md1 = mk_valid_md(&mut td)?;
let original = thin_dump!( let md1_path = md1.to_str().unwrap();
"--transaction-id=5", let original = run_ok_raw(
"--data-block-size=128", THIN_DUMP,
"--nr-data-blocks=4096000", &[
&md1 "--transaction-id=5",
) "--data-block-size=128",
.run()?; "--nr-data-blocks=4096000",
md1_path,
],
)?;
assert_eq!(original.stderr.len(), 0); assert_eq!(original.stderr.len(), 0);
damage_superblock(&md1)?; damage_superblock(&md1)?;
let md2 = mk_zeroed_md(&mut td)?; let md2 = mk_zeroed_md(&mut td)?;
thin_repair!( let md2_path = md2.to_str().unwrap();
"--transaction-id=5", run_ok(
"--data-block-size=128", THIN_REPAIR,
"--nr-data-blocks=4096000", &[
"-i", "--transaction-id=5",
&md1, "--data-block-size=128",
"-o", "--nr-data-blocks=4096000",
&md2 "-i",
) md1_path,
.run()?; "-o",
let repaired = thin_dump!(&md2).run()?; md2_path,
],
)?;
let repaired = run_ok_raw(THIN_DUMP, &[md2_path])?;
assert_eq!(repaired.stderr.len(), 0); assert_eq!(repaired.stderr.len(), 0);
assert_eq!(original.stdout, repaired.stdout); assert_eq!(original.stdout, repaired.stdout);
Ok(()) Ok(())
} }
//-----------------------------------------
// TODO: share with thin_dump
fn missing_thing(flag1: &str, flag2: &str, pattern: &str) -> Result<()> { fn missing_thing(flag1: &str, flag2: &str, pattern: &str) -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md1 = mk_valid_md(&mut td)?; let md1 = mk_valid_md(&mut td)?;
damage_superblock(&md1)?; damage_superblock(&md1)?;
let md2 = mk_zeroed_md(&mut td)?; 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)); assert!(stderr.contains(pattern));
Ok(()) Ok(())
} }
@ -170,3 +167,5 @@ fn missing_nr_data_blocks() -> Result<()> {
"nr data blocks", "nr data blocks",
) )
} }
//-----------------------------------------

View File

@ -1,101 +1,51 @@
use anyhow::Result; use anyhow::Result;
use std::str::from_utf8;
use thinp::file_utils;
use thinp::version::tools_version;
mod common; mod common;
use common::common_args::*;
use common::input_arg::*;
use common::output_option::*;
use common::test_dir::*; use common::test_dir::*;
use common::*; use common::*;
//------------------------------------------ //------------------------------------------
#[test] const USAGE: &str = "Usage: thin_restore [options]\n\
fn accepts_v() -> Result<()> { Options:\n \
let stdout = thin_restore!("-V").read()?; {-h|--help}\n \
assert!(stdout.contains(tools_version())); {-i|--input} <input xml file>\n \
Ok(()) {-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] test_missing_input_option!(THIN_RESTORE);
fn accepts_h() -> Result<()> { test_input_file_not_found!(THIN_RESTORE, OPTION);
let stdout = thin_restore!("-h").read()?; test_corrupted_input_data!(THIN_RESTORE, OPTION);
assert_eq!(stdout, USAGE);
Ok(())
}
#[test] test_missing_output_option!(THIN_RESTORE, mk_valid_xml);
fn accepts_help() -> Result<()> { test_tiny_output_file!(THIN_RESTORE, mk_valid_xml);
let stdout = thin_restore!("--help").read()?;
assert_eq!(stdout, USAGE);
Ok(())
}
#[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] // TODO: share with cache_restore, era_restore
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(())
}
fn quiet_flag(flag: &str) -> Result<()> { fn quiet_flag(flag: &str) -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?; let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&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.stdout.len(), 0);
assert_eq!(output.stderr.len(), 0); assert_eq!(output.stderr.len(), 0);
Ok(()) Ok(())
@ -111,15 +61,21 @@ fn accepts_quiet() -> Result<()> {
quiet_flag("--quiet") quiet_flag("--quiet")
} }
//-----------------------------------------
// TODO: share with thin_dump
fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> { fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?; let xml = mk_valid_xml(&mut td)?;
let md = mk_zeroed_md(&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()?; let output = run_ok(THIN_DUMP, &[md_path])?;
assert!(from_utf8(&output.stdout)?.contains(pattern)); assert!(output.contains(pattern));
Ok(()) Ok(())
} }
@ -137,3 +93,5 @@ fn override_data_block_size() -> Result<()> {
fn override_nr_data_blocks() -> Result<()> { fn override_nr_data_blocks() -> Result<()> {
override_something("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"") override_something("--nr-data-blocks", "234500", "nr_data_blocks=\"234500\"")
} }
//-----------------------------------------

View File

@ -1,54 +1,36 @@
use anyhow::Result; use anyhow::Result;
use thinp::version::tools_version;
mod common; mod common;
use common::common_args::*;
use common::test_dir::*; use common::test_dir::*;
use common::*; use common::*;
//------------------------------------------ //------------------------------------------
#[test] const USAGE: &str = "Usage: thin_rmap [options] {device|file}\n\
fn accepts_v() -> Result<()> { Options:\n \
let stdout = thin_rmap!("-V").read()?; {-h|--help}\n \
assert!(stdout.contains(tools_version())); {-V|--version}\n \
Ok(()) {--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] #[test]
fn valid_region_format_should_pass() -> Result<()> { fn valid_region_format_should_pass() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; 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(()) Ok(())
} }
@ -67,7 +49,7 @@ fn invalid_regions_should_fail() -> Result<()> {
for r in &invalid_regions { for r in &invalid_regions {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; 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(()) Ok(())
} }
@ -76,7 +58,16 @@ fn invalid_regions_should_fail() -> Result<()> {
fn multiple_regions_should_pass() -> Result<()> { fn multiple_regions_should_pass() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let md = mk_valid_md(&mut td)?; 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(()) Ok(())
} }
@ -84,7 +75,7 @@ fn multiple_regions_should_pass() -> Result<()> {
fn junk_input() -> Result<()> { fn junk_input() -> Result<()> {
let mut td = TestDir::new()?; let mut td = TestDir::new()?;
let xml = mk_valid_xml(&mut td)?; 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(()) Ok(())
} }