From 35df50857d8da28b83bcc8fff2c69b144d579b6b Mon Sep 17 00:00:00 2001 From: figsoda Date: Thu, 4 Nov 2021 09:28:19 -0400 Subject: [PATCH] rewrite tests --- .gitignore | 3 + Cargo.lock | 284 +++++++++++++++++++++++++++++++ Cargo.toml | 7 +- tests/compress_and_decompress.rs | 215 ----------------------- tests/compress_empty_dir.rs | 42 ----- tests/integration.rs | 114 +++++++++++++ tests/mime.rs | 55 ++++++ tests/utils.rs | 79 ++------- 8 files changed, 477 insertions(+), 322 deletions(-) delete mode 100644 tests/compress_and_decompress.rs delete mode 100644 tests/compress_empty_dir.rs create mode 100644 tests/integration.rs create mode 100644 tests/mime.rs diff --git a/.gitignore b/.gitignore index 82cb679..e716fb8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ makeshift_testing.py # These are backup files generated by rustfmt **/*.rs.bk + +# crash logs generated by proptest +*.proptest-regressions diff --git a/Cargo.lock b/Cargo.lock index 6c05aed..de477fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,29 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "assert_cmd" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e996dc7940838b7ef1096b882e29ec30a3149a3a443cdc8dba19ed382eca1fe2" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "atty" version = "0.2.14" @@ -25,12 +48,38 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -132,6 +181,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "dir-diff" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2860407d7d7e2e004bb2128510ad9e8d669e76fa005ccf567977b5d71b8b4a0b" +dependencies = [ + "walkdir", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "filetime" version = "0.2.15" @@ -157,6 +233,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fs-err" version = "2.6.0" @@ -217,6 +299,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + [[package]] name = "jobserver" version = "0.1.24" @@ -291,6 +382,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.8.0" @@ -310,10 +410,12 @@ dependencies = [ name = "ouch" version = "0.3.1" dependencies = [ + "assert_cmd", "atty", "bzip2", "clap", "clap_generate", + "dir-diff", "flate2", "fs-err", "infer", @@ -321,15 +423,44 @@ dependencies = [ "linked-hash-map", "lz4_flex", "once_cell", + "parse-display", + "proptest", "rand", "tar", "tempfile", + "test-strategy", "walkdir", "xz2", "zip", "zstd", ] +[[package]] +name = "parse-display" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898bf4c2a569dedbfd4e6c3f0bbd0ae825e5b6b0b69bae3e3c1000158689334a" +dependencies = [ + "once_cell", + "parse-display-derive", + "regex", +] + +[[package]] +name = "parse-display-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1779d1e28ab04568223744c2af4aa4e642e67b92c76bdce0929a6d2c36267199" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn", +] + [[package]] name = "pkg-config" version = "0.3.22" @@ -342,6 +473,33 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +[[package]] +name = "predicates" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6ce811d0b2e103743eec01db1c50612221f173084ce2f7941053e94b6bb474" +dependencies = [ + "difflib", + "itertools", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" + +[[package]] +name = "predicates-tree" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338c7be2905b732ae3984a2f40032b5e94fd8f52505b186c7d4d68d193445df7" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -375,6 +533,38 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "proptest" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "quick-error 2.0.1", + "rand", + "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 = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.10" @@ -424,6 +614,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.2.10" @@ -433,6 +632,29 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -442,6 +664,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + [[package]] name = "same-file" version = "1.0.6" @@ -463,6 +697,29 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "structmeta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59915b528a896f2e3bfa1a6ace65f7bb0ff9f9863de6213b0c01cb6fd3c3ac71" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn", +] + +[[package]] +name = "structmeta-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73800bcca56045d5ab138a48cd28a96093335335deaa916f22b5749c4150c79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "1.0.81" @@ -508,6 +765,24 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a4ec180a2de59b57434704ccfad967f789b12737738798fa08798cd5824c16" + +[[package]] +name = "test-strategy" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22c726321a7c108ca1de4ed2e6a362ead7193ecfbe0b326c5dff602b65a09e6a" +dependencies = [ + "proc-macro2", + "quote", + "structmeta", + "syn", +] + [[package]] name = "textwrap" version = "0.14.2" @@ -592,6 +867,15 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[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.2" diff --git a/Cargo.toml b/Cargo.toml index 52b2774..7aa78ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,9 +33,14 @@ clap = "=3.0.0-beta.5" clap_generate = "=3.0.0-beta.5" [dev-dependencies] -tempfile = "3.2.0" +assert_cmd = "2.0.2" +dir-diff = "0.3.2" infer = "0.5.0" +parse-display = "0.5.3" +proptest = "1.0.0" rand = { version = "0.8.3", default-features = false, features = ["small_rng", "std"] } +tempfile = "3.2.0" +test-strategy = "0.1.2" [profile.release] lto = true diff --git a/tests/compress_and_decompress.rs b/tests/compress_and_decompress.rs deleted file mode 100644 index 3c866ad..0000000 --- a/tests/compress_and_decompress.rs +++ /dev/null @@ -1,215 +0,0 @@ -mod utils; - -use std::{ - env, - io::prelude::*, - path::{Path, PathBuf}, - time::Duration, -}; - -use fs_err as fs; -use ouch::{commands::run, Opts, QuestionPolicy, Subcommand}; -use rand::{rngs::SmallRng, RngCore, SeedableRng}; -use tempfile::NamedTempFile; -use utils::*; - -#[test] -/// Makes sure that the files ouch produces are what they claim to be, checking their -/// types through MIME sniffing. -fn sanity_check_through_mime() { - // Somehow this test causes test failures when run in parallel with test_each_format - // This is a temporary hack that should allow the tests to pass while this bug isn't solved. - std::thread::sleep(Duration::from_secs(2)); - - let temp_dir = tempfile::tempdir().expect("to build a temporary directory"); - - let mut test_file = NamedTempFile::new_in(temp_dir.path()).expect("to be able to build a temporary file"); - - let bytes = generate_random_file_content(&mut SmallRng::from_entropy()); - test_file.write_all(&bytes).expect("to successfully write bytes to the file"); - - let formats = [ - "tar", "zip", "tar.gz", "tgz", "tbz", "tbz2", "txz", "tlz", "tlzma", "tzst", "tar.bz", "tar.bz2", "tar.lzma", - "tar.xz", "tar.zst", - ]; - - let expected_mimes = [ - "application/x-tar", - "application/zip", - "application/gzip", - "application/gzip", - "application/x-bzip2", - "application/x-bzip2", - "application/x-xz", - "application/x-xz", - "application/x-xz", - "application/zstd", - "application/x-bzip2", - "application/x-bzip2", - "application/x-xz", - "application/x-xz", - "application/zstd", - ]; - - assert_eq!(formats.len(), expected_mimes.len()); - - for (format, expected_mime) in formats.iter().zip(expected_mimes.iter()) { - let temp_dir_path = temp_dir.path(); - let paths_to_compress = &[test_file.path().into()]; - - let compressed_file_path = compress_files(temp_dir_path, paths_to_compress, format); - - let sniffed = - infer::get_from_path(compressed_file_path).expect("the file to be read").expect("the MIME to be found"); - - assert_eq!(&sniffed.mime_type(), expected_mime); - } -} - -#[test] -/// Tests each format that supports multiple files with random input. -/// TODO: test the remaining formats. -fn test_each_format() { - test_compressing_and_decompressing_archive("tar"); - test_compressing_and_decompressing_archive("tar.gz"); - test_compressing_and_decompressing_archive("tar.bz"); - test_compressing_and_decompressing_archive("tar.bz2"); - test_compressing_and_decompressing_archive("tar.xz"); - test_compressing_and_decompressing_archive("tar.lz"); - test_compressing_and_decompressing_archive("tar.lz4"); - test_compressing_and_decompressing_archive("tar.lzma"); - test_compressing_and_decompressing_archive("tar.zst"); - test_compressing_and_decompressing_archive("tgz"); - test_compressing_and_decompressing_archive("tbz"); - test_compressing_and_decompressing_archive("tbz2"); - test_compressing_and_decompressing_archive("txz"); - test_compressing_and_decompressing_archive("tlz4"); - test_compressing_and_decompressing_archive("tlz"); - test_compressing_and_decompressing_archive("tlzma"); - test_compressing_and_decompressing_archive("tzst"); - test_compressing_and_decompressing_archive("zip"); - test_compressing_and_decompressing_archive("zip.gz"); - test_compressing_and_decompressing_archive("zip.bz"); - test_compressing_and_decompressing_archive("zip.bz2"); - test_compressing_and_decompressing_archive("zip.xz"); - test_compressing_and_decompressing_archive("zip.lz"); - test_compressing_and_decompressing_archive("zip.lzma"); - - // Why not - test_compressing_and_decompressing_archive( - "tar.gz.gz.gz.gz.gz.gz.gz.gz.zst.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.lz.lz.lz.lz.lz.lz.lz.lz.lz.lz.bz.bz.bz.bz.bz.bz.bz", - ); -} - -type FileContent = Vec; - -/// Compress and decompresses random files to archive formats, checks if contents match -fn test_compressing_and_decompressing_archive(format: &str) { - // System temporary directory depends on the platform, for linux it's /tmp - let system_tmp = env::temp_dir(); - - // Create a temporary testing folder that will be deleted on scope drop - let testing_dir = - tempfile::Builder::new().prefix("ouch-testing").tempdir_in(system_tmp).expect("Could not create testing_dir"); - let testing_dir_path = testing_dir.path(); - - // Quantity of compressed files vary from 1 to 10 - let mut rng = SmallRng::from_entropy(); - let quantity_of_files = rng.next_u32() % 10 + 1; - - let contents_of_files: Vec = - (0..quantity_of_files).map(|_| generate_random_file_content(&mut rng)).collect(); - - // Create them - let mut file_paths = create_files(testing_dir_path, &contents_of_files); - // Compress them - let compressed_archive_path = compress_files(testing_dir_path, &file_paths, format); - // Decompress them - let mut extracted_paths = extract_files(&compressed_archive_path); - - // // DEBUG UTIL: - // // Uncomment line below to freeze the code and see compressed and extracted files in - // // the temporary directory before their auto-destruction. - // std::thread::sleep(std::time::Duration::from_secs(1_000_000)); - - file_paths.sort(); - extracted_paths.sort(); - - assert_correct_paths(&file_paths, &extracted_paths, format); - compare_file_contents(&extracted_paths, &contents_of_files, format); -} - -/// Crate file contents from 1024 up to 8192 random bytes -fn generate_random_file_content(rng: &mut impl RngCore) -> FileContent { - let quantity = 1024 + rng.next_u32() % (8192 - 1024); - let mut vec = vec![0; quantity as usize]; - rng.fill_bytes(&mut vec); - vec -} - -/// Create files using the indexes as file names (eg. 0, 1, 2 and 3) -/// -/// Return the path to each one. -fn create_files(at: &Path, contents: &[FileContent]) -> Vec { - contents - .iter() - .enumerate() - .map(|(i, content)| { - let path = at.join(i.to_string()); - let mut file = fs::File::create(&path).expect("Could not create dummy test file"); - file.write_all(content).expect("Could not write to dummy test file"); - path - }) - .collect() -} - -fn extract_files(archive_path: &Path) -> Vec { - // We will extract in the same folder as the archive - // If the archive is at: - // /tmp/ouch-testing-tar.Rbq4DusBrtF8/archive.tar - // Then the extraction_output_folder will be: - // /tmp/ouch-testing-tar.Rbq4DusBrtF8/extraction_results/ - let mut extraction_output_folder = archive_path.to_path_buf(); - // Remove the name of the extracted archive - assert!(extraction_output_folder.pop()); - // Add the suffix "results" - extraction_output_folder.push("extraction_results"); - - let command = Opts { - yes: false, - no: false, - cmd: Subcommand::Decompress { - files: vec![archive_path.to_owned()], - output_dir: Some(extraction_output_folder.clone()), - }, - }; - run(command, QuestionPolicy::Ask).expect("Failed to extract"); - - fs::read_dir(extraction_output_folder).unwrap().map(Result::unwrap).map(|entry| entry.path()).collect() -} - -fn assert_correct_paths(original: &[PathBuf], extracted: &[PathBuf], format: &str) { - assert_eq!( - original.len(), - extracted.len(), - "Number of compressed files does not match number of decompressed when testing archive format '{:?}'.", - format - ); - for (original, extracted) in original.iter().zip(extracted) { - assert_eq!(original.file_name(), extracted.file_name(), ""); - } -} - -fn compare_file_contents(extracted: &[PathBuf], contents: &[FileContent], format: &str) { - extracted.iter().zip(contents).for_each(|(extracted_path, expected_content)| { - let actual_content = fs::read(extracted_path).unwrap(); - - assert_eq!( - expected_content, - actual_content.as_slice(), - "Contents of file with path '{:?}' does not match after compression and decompression while testing archive format '{:?}.'", - extracted_path.canonicalize().unwrap(), - format - ); - }); -} diff --git a/tests/compress_empty_dir.rs b/tests/compress_empty_dir.rs deleted file mode 100644 index 57bfe35..0000000 --- a/tests/compress_empty_dir.rs +++ /dev/null @@ -1,42 +0,0 @@ -mod utils; - -use std::{env, path::PathBuf}; - -use utils::*; - -#[test] -fn test_each_format() { - test_compress_decompress_with_empty_dir("tar"); - test_compress_decompress_with_empty_dir("zip"); -} - -fn test_compress_decompress_with_empty_dir(format: &str) { - // System temporary directory depends on the platform, for linux it's /tmp - let system_tmp = env::temp_dir(); - - // Create a temporary testing folder that will be deleted on scope drop - let testing_dir = - tempfile::Builder::new().prefix("ouch-testing").tempdir_in(system_tmp).expect("Could not create testing_dir"); - - let testing_dir_path = testing_dir.path(); - - let empty_dir_path: PathBuf = create_empty_dir(testing_dir_path, "dummy_empty_dir_name"); - - let mut file_paths: Vec = vec![empty_dir_path]; - - let compressed_archive_path: PathBuf = compress_files(testing_dir_path, &file_paths, format); - - let mut extracted_paths = extract_files(&compressed_archive_path); - - // // DEBUG UTIL: - // // Uncomment line below to freeze the code and see compressed and extracted files in - // // the temporary directory before their auto-destruction. - // std::thread::sleep(std::time::Duration::from_secs(10)); - - // no need to sort a unitary value vector but i will keep this - // for retrocompatibility, for now. - file_paths.sort(); - extracted_paths.sort(); - - assert_correct_paths(&file_paths, &extracted_paths, format); -} diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 0000000..03146a9 --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1,114 @@ +#[macro_use] +mod utils; + +use std::{iter::once, path::PathBuf}; + +use fs_err as fs; +use parse_display::Display; +use proptest::sample::size_range; +use rand::{rngs::SmallRng, RngCore, SeedableRng}; +use tempfile::tempdir; +use test_strategy::{proptest, Arbitrary}; + +use crate::utils::create_file_random; + +#[derive(Arbitrary, Debug, Display)] +#[display(style = "snake_case")] +enum DirectoryExtension { + Tar, + Tbz, + Tgz, + Tlz4, + Tlz, + Tlzma, + Txz, + Zip, +} + +#[derive(Arbitrary, Debug, Display)] +#[display(style = "snake_case")] +enum FileExtension { + Bz, + Bz2, + Gz, + // Lz4, // broken + Lz, + Lzma, + Xz, +} + +#[derive(Arbitrary, Debug, Display)] +#[display("{0}")] +enum Extension { + Directory(DirectoryExtension), + File(FileExtension), +} + +fn merge_extensions(ext: impl ToString, exts: Vec) -> String { + once(ext.to_string()).chain(exts.into_iter().map(|x| x.to_string())).collect::>().join(".") +} + +fn create_random_files(dir: impl Into, depth: u8, rng: &mut SmallRng) { + if depth == 0 { + return; + } + + let dir = &dir.into(); + + for _ in 0..rng.next_u32() % 8 { + create_file_random(&mut tempfile::Builder::new().tempfile_in(dir).unwrap().keep().unwrap().0, rng); + } + + for _ in 0..rng.next_u32() % 4 { + create_random_files(&tempfile::tempdir_in(dir).unwrap().into_path(), depth - 1, rng); + } +} + +#[proptest(cases = 512)] +fn single_empty_file(ext: Extension, #[any(size_range(0..8).lift())] exts: Vec) { + let dir = tempdir().unwrap(); + let dir = dir.path(); + let before = &dir.join("before"); + fs::create_dir(before).unwrap(); + let before_file = &before.join("file"); + let archive = &dir.join(format!("file.{}", merge_extensions(ext, exts))); + let after = &dir.join("after"); + create_file_random(&mut fs::File::create(before_file).unwrap(), &mut SmallRng::from_entropy()); + ouch!("c", before_file, archive); + ouch!("d", archive, "-d", after); + assert!(!dir_diff::is_different(before, after).unwrap()); +} + +#[proptest(cases = 512)] +fn single_file(ext: Extension, #[any(size_range(0..8).lift())] exts: Vec) { + let dir = tempdir().unwrap(); + let dir = dir.path(); + let before = &dir.join("before"); + fs::create_dir(before).unwrap(); + let before_file = &before.join("file"); + let archive = &dir.join(format!("file.{}", merge_extensions(ext, exts))); + let after = &dir.join("after"); + fs::write(before_file, []).unwrap(); + ouch!("c", before_file, archive); + ouch!("d", archive, "-d", after); + assert!(!dir_diff::is_different(before, after).unwrap()); +} + +#[proptest(cases = 512)] +fn multiple_files( + ext: DirectoryExtension, + #[any(size_range(0..8).lift())] exts: Vec, + #[strategy(0u8..4)] depth: u8, +) { + let dir = tempdir().unwrap(); + let dir = dir.path(); + let before = &dir.join("before"); + let before_dir = &before.join("dir"); + fs::create_dir_all(before_dir).unwrap(); + let archive = &dir.join(format!("archive.{}", merge_extensions(ext, exts))); + let after = &dir.join("after"); + create_random_files(before_dir, depth, &mut SmallRng::from_entropy()); + ouch!("c", before_dir, archive); + ouch!("d", archive, "-d", after); + assert!(!dir_diff::is_different(before, after).unwrap()); +} diff --git a/tests/mime.rs b/tests/mime.rs new file mode 100644 index 0000000..c88bcb9 --- /dev/null +++ b/tests/mime.rs @@ -0,0 +1,55 @@ +#[macro_use] +mod utils; + +use rand::{rngs::SmallRng, SeedableRng}; +use tempfile::NamedTempFile; + +use crate::utils::create_file_random; + +#[test] +/// Makes sure that the files ouch produces are what they claim to be, checking their +/// types through MIME sniffing. +fn sanity_check_through_mime() { + let temp_dir = tempfile::tempdir().expect("to build a temporary directory"); + let temp_dir_path = temp_dir.path(); + + let test_file = &mut NamedTempFile::new_in(temp_dir_path).expect("to be able to build a temporary file"); + create_file_random(test_file, &mut SmallRng::from_entropy()); + + let formats = [ + "tar", "zip", "tar.gz", "tgz", "tbz", "tbz2", "txz", "tlz", "tlzma", "tzst", "tar.bz", "tar.bz2", "tar.lzma", + "tar.xz", "tar.zst", + ]; + + let expected_mimes = [ + "application/x-tar", + "application/zip", + "application/gzip", + "application/gzip", + "application/x-bzip2", + "application/x-bzip2", + "application/x-xz", + "application/x-xz", + "application/x-xz", + "application/zstd", + "application/x-bzip2", + "application/x-bzip2", + "application/x-xz", + "application/x-xz", + "application/zstd", + ]; + + assert_eq!(formats.len(), expected_mimes.len()); + + for (format, expected_mime) in formats.iter().zip(expected_mimes.iter()) { + let path_to_compress = test_file.path(); + + let compressed_file_path = &format!("{}.{}", path_to_compress.display(), format); + ouch!("c", path_to_compress, compressed_file_path); + + let sniffed = + infer::get_from_path(compressed_file_path).expect("the file to be read").expect("the MIME to be found"); + + assert_eq!(&sniffed.mime_type(), expected_mime); + } +} diff --git a/tests/utils.rs b/tests/utils.rs index ec3ae7a..fbccd4d 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -1,68 +1,19 @@ -//! Files in common between one or more integration tests +use std::io::Write; -#![allow(dead_code)] +use rand::RngCore; -use std::path::{Path, PathBuf}; - -use fs_err as fs; -use ouch::{commands::run, Opts, QuestionPolicy, Subcommand}; - -pub fn create_empty_dir(at: &Path, filename: &str) -> PathBuf { - let dirname = Path::new(filename); - let full_path = at.join(dirname); - - fs::create_dir(&full_path).expect("Failed to create an empty directory"); - - full_path -} - -pub fn compress_files(at: &Path, paths_to_compress: &[PathBuf], format: &str) -> PathBuf { - let archive_path = String::from("archive.") + format; - let archive_path = at.join(archive_path); - - let command = Opts { - yes: false, - no: false, - cmd: Subcommand::Compress { files: paths_to_compress.to_vec(), output: archive_path.clone() }, - }; - run(command, QuestionPolicy::Ask).expect("Failed to compress test dummy files"); - - archive_path -} - -pub fn extract_files(archive_path: &Path) -> Vec { - // We will extract in the same folder as the archive - // If the archive is at: - // /tmp/ouch-testing-tar.Rbq4DusBrtF8/archive.tar - // Then the extraction_output_folder will be: - // /tmp/ouch-testing-tar.Rbq4DusBrtF8/extraction_results/ - let mut extraction_output_folder = archive_path.to_path_buf(); - // Remove the name of the extracted archive - assert!(extraction_output_folder.pop()); - // Add the suffix "results" - extraction_output_folder.push("extraction_results"); - - let command = Opts { - yes: false, - no: false, - cmd: Subcommand::Decompress { - files: vec![archive_path.to_owned()], - output_dir: Some(extraction_output_folder.clone()), - }, - }; - run(command, QuestionPolicy::Ask).expect("Failed to extract"); - - fs::read_dir(extraction_output_folder).unwrap().map(Result::unwrap).map(|entry| entry.path()).collect() -} - -pub fn assert_correct_paths(original: &[PathBuf], extracted: &[PathBuf], format: &str) { - assert_eq!( - original.len(), - extracted.len(), - "Number of compressed files does not match number of decompressed when testing archive format '{:?}'.", - format - ); - for (original, extracted) in original.iter().zip(extracted) { - assert_eq!(original.file_name(), extracted.file_name()); +#[macro_export] +macro_rules! ouch { + ($($e:expr),*) => { + ::assert_cmd::Command::cargo_bin("ouch") + .expect("Failed to find ouch executable") + $(.arg($e))* + .unwrap(); } } + +pub fn create_file_random(file: &mut impl Write, rng: &mut impl RngCore) { + let data = &mut Vec::with_capacity((rng.next_u32() % 8192) as usize); + rng.fill_bytes(data); + file.write_all(data).unwrap(); +}