diff --git a/Cargo.lock b/Cargo.lock index 2509a64..4641cbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,7 @@ dependencies = [ "hex", "log", "nix 0.19.0", + "proptest", "rand 0.7.3", "rayon", "rust-crypto", @@ -56,6 +57,21 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" + [[package]] name = "bitflags" version = "1.2.1" @@ -77,6 +93,12 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + [[package]] name = "cargo-husky" version = "1.5.0" @@ -191,6 +213,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fs2" version = "0.4.3" @@ -331,6 +359,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -362,6 +399,32 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "proptest" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e6c80c1139113c28ee4670dc50cc42915228b51f56a9e407f0ec60f966646f" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "quick-error", + "rand 0.7.3", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.7" @@ -450,6 +513,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_xorshift" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rayon" version = "1.5.0" @@ -490,6 +562,12 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "regex-syntax" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -518,6 +596,18 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.5" @@ -692,6 +782,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index a0f5344..6572266 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,9 @@ thiserror = "1.0" uuid = { version = "0.8", features = ["v4"] } walkdir = "2.3" +[dev-dependencies] +proptest = "0.10" + [dev-dependencies.cargo-husky] version = "1" default-features = false diff --git a/src/repository/mod.rs b/src/repository/mod.rs index c1eeced..ae18404 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -168,3 +168,36 @@ impl<'a> Repository<'a> { Ok(hasher.finalize()[..].into()) } } + +#[cfg(test)] +mod must { + use super::Repository; + use crate::test::source::TempSource; + use anyhow::Result; + use tempfile::tempdir; + + #[test] + fn have_size_equal_to_sum_of_sizes_of_backed_up_files() -> Result<()> { + let source = TempSource::new().unwrap(); + let repository_path = tempdir().unwrap().into_path(); + Repository::init(&repository_path)?; + + let file_size1 = 13; + let file_size2 = 27; + + let mut backup_repository = Repository::open(&repository_path)?; + source.write_random_bytes_to_file("file1", file_size1)?; + backup_repository.store(&source.file_path("file1"))?; + + source.write_random_bytes_to_file("file2", file_size2)?; + backup_repository.store(&source.file_path("file2"))?; + + assert_eq!((file_size1 + file_size2) as u64, backup_repository.data_weight()?); + + backup_repository.save_index()?; + + assert_eq!((file_size1 + file_size2) as u64, backup_repository.data_weight()?); + + Ok(()) + } +} diff --git a/src/test/assertions.rs b/src/test/assertions.rs index 31c3e20..e043313 100644 --- a/src/test/assertions.rs +++ b/src/test/assertions.rs @@ -33,7 +33,17 @@ pub fn assert_same_after_restore(source_path: &Path) -> Result<()> { Ok(()) } -pub fn assert_restored_file_contents(repository_path: &Path, source_file_full_path: &Path, contents: &str) -> Result<()> { +pub fn assert_restored_file_contents(repository_path: &Path, source_file_full_path: &Path, contents: &[u8]) -> Result<()> { + let mut restore_repository = Repository::open(repository_path)?; + let item = restore_repository.newest_item_by_source_path(&source_file_full_path)?; + let restore_target = tempdir().unwrap(); + let restore_engine = restore::Engine::new(&mut restore_repository, &restore_target.path())?; + + restore_engine.restore(&item.unwrap())?; + let restored_file_path = restore_target.path().join(source_file_full_path.strip_prefix("/")?); + assert_target_file_contents(&restored_file_path, contents) +} +pub fn assert_restored_file_byte_contents(repository_path: &Path, source_file_full_path: &Path, contents: &[u8]) -> Result<()> { let mut restore_repository = Repository::open(repository_path)?; let item = restore_repository.newest_item_by_source_path(&source_file_full_path)?; let restore_target = tempdir().unwrap(); @@ -47,7 +57,7 @@ pub fn assert_restored_file_contents(repository_path: &Path, source_file_full_pa pub fn assert_restored_from_version_has_contents( repository_path: &Path, source_file_full_path: &Path, - old_contents: &str, + old_contents: &[u8], old_id: &ItemId, ) -> Result<()> { let mut restore_repository = Repository::open(repository_path)?; @@ -78,16 +88,27 @@ pub fn restore_all_from_reloaded_repository(repository_path: &Path, restore_targ } } -pub fn backup_file_with_contents( +pub fn backup_file_with_text_contents( source: &TempSource, repository_path: &Path, source_file_relative_path: &str, contents: &str, +) -> Result<()> { + { + backup_file_with_byte_contents(source, repository_path, source_file_relative_path, contents.as_bytes()) + } +} + +pub fn backup_file_with_byte_contents( + source: &TempSource, + repository_path: &Path, + source_file_relative_path: &str, + contents: &[u8], ) -> Result<()> { { let mut backup_repository = Repository::open(repository_path)?; let mut backup_engine = backup::Engine::new(source.path(), &mut backup_repository)?; - source.write_text_to_file(source_file_relative_path, contents).unwrap(); + source.write_bytes_to_file(source_file_relative_path, contents).unwrap(); backup_engine.backup()?; Ok(()) } @@ -136,13 +157,10 @@ pub fn get_sorted_files_recursively>(path: T) -> Result Result<()> { - let mut actual_contents = String::new(); +fn assert_target_file_contents(restored_path: &Path, expected_contents: &[u8]) -> Result<()> { + let mut actual_contents = vec![]; assert!(restored_path.exists(), "Expected '{}' to be there", restored_path.display()); - File::open(restored_path) - .unwrap() - .read_to_string(&mut actual_contents) - .unwrap(); + File::open(restored_path)?.read_to_end(&mut actual_contents)?; assert_eq!(expected_contents, actual_contents); Ok(()) } diff --git a/src/test/source.rs b/src/test/source.rs index 45cb1e1..5a266cf 100644 --- a/src/test/source.rs +++ b/src/test/source.rs @@ -25,6 +25,12 @@ impl TempSource { self.write_bytes_to_file(filename, text.as_bytes()) } + pub fn write_random_bytes_to_file(&self, filename: &str, size: usize) -> Result<(), Error> { + let random_bytes: Vec = (0..size).map(|_| rand::random::()).collect(); + self.write_bytes_to_file(filename, &random_bytes)?; + Ok(()) + } + pub fn path(&self) -> &Path { self.directory.path() } diff --git a/tests/system_tests.rs b/tests/system_tests.rs index ebe278a..a15309f 100644 --- a/tests/system_tests.rs +++ b/tests/system_tests.rs @@ -5,6 +5,8 @@ use bakare::backup; use bakare::repository::Repository; use bakare::test::{assertions::*, source::TempSource}; +use proptest::prelude::*; + #[test] fn restore_multiple_files() -> Result<()> { let source = TempSource::new().unwrap(); @@ -26,12 +28,12 @@ fn restore_files_after_reopening_repository() -> Result<()> { let source_file_relative_path = "some file path"; let original_contents = "some old contents"; - backup_file_with_contents(&source, &repository_path, source_file_relative_path, original_contents)?; + backup_file_with_text_contents(&source, &repository_path, source_file_relative_path, original_contents)?; restore_all_from_reloaded_repository(&repository_path, &restore_target)?; let source_file_full_path = &source.file_path(source_file_relative_path); - assert_restored_file_contents(repository_path, source_file_full_path, original_contents) + assert_restored_file_contents(repository_path, source_file_full_path, original_contents.as_bytes()) } #[test] @@ -44,15 +46,15 @@ fn restore_older_version_of_file() -> Result<()> { let source_file_full_path = source.file_path(source_file_relative_path); let old_contents = "some old contents"; - backup_file_with_contents(&source, &repository_path, source_file_relative_path, old_contents)?; + backup_file_with_text_contents(&source, &repository_path, source_file_relative_path, old_contents)?; let old_item = newest_item(&repository_path, &source_file_full_path)?; let old_id = old_item.id(); let new_contents = "totally new contents"; - backup_file_with_contents(&source, &repository_path, source_file_relative_path, new_contents)?; + backup_file_with_text_contents(&source, &repository_path, source_file_relative_path, new_contents)?; - assert_restored_from_version_has_contents(&repository_path, &source_file_full_path, old_contents, &old_id) + assert_restored_from_version_has_contents(&repository_path, &source_file_full_path, old_contents.as_bytes(), &old_id) } #[test] @@ -64,12 +66,12 @@ fn newer_version_should_be_greater_than_earlier_version() -> Result<()> { let source_file_relative_path = "some path"; let source_file_full_path = source.file_path(source_file_relative_path); - backup_file_with_contents(&source, &repository_path, source_file_relative_path, "old")?; + backup_file_with_text_contents(&source, &repository_path, source_file_relative_path, "old")?; let old_item = newest_item(&repository_path, &source_file_full_path)?; let old_version = old_item.version(); - backup_file_with_contents(&source, &repository_path, source_file_relative_path, "new")?; + backup_file_with_text_contents(&source, &repository_path, source_file_relative_path, "new")?; let new_item = newest_item(&repository_path, &source_file_full_path)?; let new_version = new_item.version(); @@ -79,26 +81,25 @@ fn newer_version_should_be_greater_than_earlier_version() -> Result<()> { Ok(()) } -#[test] -fn store_duplicated_files_just_once() -> Result<()> { - let source = TempSource::new().unwrap(); - let repository_path = &tempdir().unwrap().into_path(); - Repository::init(repository_path)?; - assert_eq!(data_weight(&repository_path)?, 0); +proptest! { + #[test] + fn store_duplicated_files_just_once(contents in any::<[u8;3]>()) { + let source = TempSource::new().unwrap(); + let repository_path = &tempdir().unwrap().into_path(); + Repository::init(repository_path).unwrap(); + assert_eq!(data_weight(&repository_path).unwrap(), 0); - let contents = "some contents"; + backup_file_with_byte_contents(&source, &repository_path, "1", &contents).unwrap(); + let first_weight = data_weight(&repository_path).unwrap(); + assert!(first_weight > 0); - backup_file_with_contents(&source, &repository_path, "1", contents)?; - let first_weight = data_weight(&repository_path)?; - assert!(first_weight > 0); + backup_file_with_byte_contents(&source, &repository_path, "2", &contents).unwrap(); + let second_weight = data_weight(&repository_path).unwrap(); + assert_eq!(first_weight, second_weight); - backup_file_with_contents(&source, &repository_path, "2", contents)?; - let second_weight = data_weight(&repository_path)?; - assert_eq!(first_weight, second_weight); - - assert_restored_file_contents(repository_path, &source.file_path("1"), contents)?; - assert_restored_file_contents(repository_path, &source.file_path("2"), contents)?; - Ok(()) + assert_restored_file_contents(repository_path, &source.file_path("1"), &contents).unwrap(); + assert_restored_file_contents(repository_path, &source.file_path("2"), &contents).unwrap(); + } } #[test] @@ -108,12 +109,12 @@ fn restore_latest_version_by_default() -> Result<()> { Repository::init(repository_path)?; let source_file_relative_path = "some path"; - backup_file_with_contents(&source, &repository_path, source_file_relative_path, "old contents")?; - backup_file_with_contents(&source, &repository_path, source_file_relative_path, "newer contents")?; - backup_file_with_contents(&source, &repository_path, source_file_relative_path, "newest contents")?; + backup_file_with_text_contents(&source, &repository_path, source_file_relative_path, "old contents")?; + backup_file_with_text_contents(&source, &repository_path, source_file_relative_path, "newer contents")?; + backup_file_with_text_contents(&source, &repository_path, source_file_relative_path, "newest contents")?; let source_file_full_path = &source.file_path(source_file_relative_path); - assert_restored_file_contents(repository_path, source_file_full_path, "newest contents") + assert_restored_file_contents(repository_path, source_file_full_path, "newest contents".as_bytes()) } #[test]