diff --git a/src/thin/mod.rs b/src/thin/mod.rs index 208df1c..b88bba8 100644 --- a/src/thin/mod.rs +++ b/src/thin/mod.rs @@ -1,3 +1,3 @@ -mod superblock; +pub mod superblock; pub mod check; pub mod xml; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 6580f79..940058b 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,12 +1,13 @@ #![allow(dead_code)] use anyhow::Result; -use duct::{Expression}; +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 thin_xml_generator; pub mod cache_xml_generator; @@ -117,6 +118,39 @@ macro_rules! cache_check { }; } +#[macro_export] +macro_rules! thin_generate_metadata { + ( $( $arg: expr ),* ) => { + { + use std::ffi::OsString; + let args: &[OsString] = &[$( Into::::into($arg) ),*]; + duct::cmd("bin/thin_generate_metadata", args).stdout_capture().stderr_capture() + } + }; +} + +#[macro_export] +macro_rules! thin_generate_mappings { + ( $( $arg: expr ),* ) => { + { + use std::ffi::OsString; + let args: &[OsString] = &[$( Into::::into($arg) ),*]; + duct::cmd("bin/thin_generate_mappings", args).stdout_capture().stderr_capture() + } + }; +} + +#[macro_export] +macro_rules! thin_generate_damage { + ( $( $arg: expr ),* ) => { + { + use std::ffi::OsString; + let args: &[OsString] = &[$( Into::::into($arg) ),*]; + duct::cmd("bin/thin_generate_damage", args).stdout_capture().stderr_capture() + } + }; +} + //------------------------------------------ // Returns stderr, a non zero status must be returned @@ -148,7 +182,7 @@ pub fn mk_valid_md(td: &mut TestDir) -> Result { pub fn mk_zeroed_md(td: &mut TestDir) -> Result { let md = td.mk_path("meta.bin"); eprintln!("path = {:?}", md); - let _file = file_utils::create_sized_file(&md, 4096 * 4096); + let _file = file_utils::create_sized_file(&md, 1024 * 1024 * 16); Ok(md) } @@ -179,4 +213,109 @@ pub fn damage_superblock(path: &PathBuf) -> Result<()> { Ok(()) } -//------------------------------------------ +//----------------------------------------------- + +// FIXME: replace mk_valid_md with this? +pub fn prep_metadata(td: &mut TestDir) -> Result { + let md = mk_zeroed_md(td)?; + thin_generate_metadata!("-o", &md, "--format", "--nr-data-blocks", "102400").run()?; + + // Create a 2GB device + thin_generate_metadata!("-o", &md, "--create-thin", "1").run()?; + thin_generate_mappings!( + "-o", + &md, + "--dev-id", + "1", + "--size", + format!("{}", 1024 * 1024 * 2), + "--rw=randwrite", + "--seq-nr=16" + ) + .run()?; + + // Take a few snapshots. + let mut snap_id = 2; + for _i in 0..10 { + // take a snapshot + thin_generate_metadata!( + "-o", + &md, + "--create-snap", + format!("{}", snap_id), + "--origin", + "1" + ) + .run()?; + + // partially overwrite the origin (64MB) + thin_generate_mappings!( + "-o", + &md, + "--dev-id", + format!("{}", 1), + "--size", + format!("{}", 1024 * 1024 * 2), + "--io-size", + format!("{}", 64 * 1024 * 2), + "--rw=randwrite", + "--seq-nr=16" + ) + .run()?; + snap_id += 1; + } + + Ok(md) +} + +pub fn set_needs_check(md: &PathBuf) -> Result<()> { + thin_generate_metadata!("-o", &md, "--set-needs-check").run()?; + Ok(()) +} + +pub fn generate_metadata_leaks(md: &PathBuf, nr_blocks: u64, expected: u32, actual: u32) -> Result<()> { + let output = thin_generate_damage!( + "-o", + &md, + "--create-metadata-leaks", + "--nr-blocks", + format!("{}", nr_blocks), + "--expected", + format!("{}", expected), + "--actual", + format!("{}", actual) + ) + .unchecked() + .run()?; + + assert!(output.status.success()); + Ok(()) +} + +pub fn get_needs_check(md: &PathBuf) -> Result { + use thinp::thin::superblock::*; + + let engine = SyncIoEngine::new(&md, 1)?; + let sb = read_superblock(&engine, SUPERBLOCK_LOCATION)?; + Ok(sb.flags.needs_check) +} + +pub fn md5(md: &PathBuf) -> Result { + let output = 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) +} + +// This checksums the file before and after the thunk is run to +// ensure it is unchanged. +pub fn ensure_untouched(p: &PathBuf, thunk: F) -> Result<()> +where + F: Fn() -> Result<()>, +{ + let csum = md5(p)?; + thunk()?; + assert_eq!(csum, md5(p)?); + Ok(()) +} + diff --git a/tests/thin_check.rs b/tests/thin_check.rs index 4134588..8ff53f7 100644 --- a/tests/thin_check.rs +++ b/tests/thin_check.rs @@ -4,9 +4,9 @@ use thinp::version::TOOLS_VERSION; mod common; -use common::*; use common::test_dir::*; use common::thin_xml_generator::{write_xml, FragmentedS}; +use common::*; //------------------------------------------ @@ -134,10 +134,19 @@ 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", + "--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))?; + run_fail(thin_check!( + "--auto-repair", + "--ignore-non-fatal-errors", + &md + ))?; Ok(()) } @@ -146,11 +155,115 @@ 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", "--skip-mappings", &md))?; - run_fail(thin_check!("--clear-needs-check-flag", "--ignore-non-fatal-errors", &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", + "--skip-mappings", + &md + ))?; + run_fail(thin_check!( + "--clear-needs-check-flag", + "--ignore-non-fatal-errors", + &md + ))?; Ok(()) } - + +//------------------------------------------ + +#[test] +fn clear_needs_check() -> Result<()> { + let mut td = TestDir::new()?; + let md = prep_metadata(&mut td)?; + set_needs_check(&md)?; + + assert!(get_needs_check(&md)?); + thin_check!("--clear-needs-check-flag", &md).run()?; + assert!(!get_needs_check(&md)?); + Ok(()) +} + +#[test] +fn no_clear_needs_check_if_error() -> Result<()> { + let mut td = TestDir::new()?; + let md = prep_metadata(&mut td)?; + set_needs_check(&md)?; + generate_metadata_leaks(&md, 1, 0, 1)?; + run_fail(thin_check!("--clear-needs-check-flag", &md))?; + assert!(get_needs_check(&md)?); + Ok(()) +} + +#[test] +fn metadata_leaks_are_non_fatal() -> Result<()> { + let mut td = TestDir::new()?; + let md = prep_metadata(&mut td)?; + generate_metadata_leaks(&md, 1, 0, 1)?; + run_fail(thin_check!(&md))?; + thin_check!("--ignore-non-fatal-errors", &md).run()?; + Ok(()) +} + +#[test] +fn fatal_errors_cant_be_ignored() -> Result<()> { + let mut td = TestDir::new()?; + let md = prep_metadata(&mut td)?; + generate_metadata_leaks(&md, 1, 1, 0)?; + ensure_untouched(&md, || { + run_fail(thin_check!("--ignore-non-fatal-errors", &md))?; + Ok(()) + }) +} + +#[test] +fn auto_repair() -> Result<()> { + let mut td = TestDir::new()?; + let md = prep_metadata(&mut td)?; + + // auto-repair should have no effect on good metadata. + ensure_untouched(&md, || { + thin_check!("--auto-repair", &md).run()?; + Ok(()) + })?; + + generate_metadata_leaks(&md, 16, 0, 1)?; + run_fail(thin_check!(&md))?; + thin_check!("--auto-repair", &md).run()?; + thin_check!(&md).run()?; + Ok(()) +} + +#[test] +fn auto_repair_has_limits() -> Result<()> { + let mut td = TestDir::new()?; + let md = prep_metadata(&mut td)?; + generate_metadata_leaks(&md, 16, 1, 0)?; + + ensure_untouched(&md, || { + run_fail(thin_check!("--auto-repair", &md))?; + Ok(()) + })?; + Ok(()) +} + +#[test] +fn auto_repair_clears_needs_check() -> Result<()> { + let mut td = TestDir::new()?; + let md = prep_metadata(&mut td)?; + set_needs_check(&md)?; + thin_check!("--auto-repair", &md).run()?; + assert!(!get_needs_check(&md)?); + Ok(()) +} + //------------------------------------------