diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c5394d80..052d19a90 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,12 +8,32 @@ We follow the [Rust Official Code of Conduct](https://www.rust-lang.org/policies Create [an issue](https://github.com/ouch-org/ouch/issues) or go to [Ouch Discussions](https://github.com/ouch-org/ouch/discussions). -# Adding a new feature +# Adding a brand new feature -Before creating a PR with a new feature, please, open an issue to suggest your addition. +Before opening the PR, open an issue to discuss your addition, this increases the chance of your PR being accepted. -This allows us to discuss the problem and solution, increasing the chance of your PR to be accepted. +# PRs -# Don't forget to +- Pass all CI checks. +- After opening the PR, add a [CHANGELOG.md] entry. -- In your PR, add a CHANGELOG.md entry. +# Updating UI tests + +In case you need to update the UI tests. + +- Run tests with `insta` to create the new snapshots: + +```sh +cargo insta review # or +cargo insta review -- ui # useful filter +``` + +- Now, review the diffs you just generated. + +```sh +cargo insta review +``` + +- You can commit them now. + +[CHANGELOG.md]: https://github.com/ouch-org/ouch diff --git a/Cargo.lock b/Cargo.lock index e6333b8c3..fd28f5290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,7 +52,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -62,7 +62,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -266,6 +266,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "core_affinity" version = "0.8.1" @@ -353,6 +365,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "errno" version = "0.3.2" @@ -361,7 +379,7 @@ checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -389,7 +407,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -530,6 +548,19 @@ dependencies = [ "cfb", ] +[[package]] +name = "insta" +version = "1.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "similar", + "yaml-rust", +] + [[package]] name = "is_executable" version = "1.0.1" @@ -720,6 +751,7 @@ dependencies = [ "gzp", "ignore", "infer", + "insta", "is_executable", "libc", "linked-hash-map", @@ -761,7 +793,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "regex-syntax 0.7.4", + "regex-syntax 0.7.5", "structmeta", "syn", ] @@ -942,25 +974,25 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.7.4", + "regex-syntax 0.7.5", ] [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.7.5", ] [[package]] @@ -971,9 +1003,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "roff" @@ -991,7 +1023,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1027,6 +1059,12 @@ version = "1.0.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" +[[package]] +name = "similar" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" + [[package]] name = "snap" version = "1.1.0" @@ -1109,7 +1147,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1327,13 +1365,37 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -1342,51 +1404,93 @@ version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.0" @@ -1411,6 +1515,15 @@ dependencies = [ "lzma-sys", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/Cargo.toml b/Cargo.toml index 66731b4e8..3dca850b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ clap_mangen = "0.2.13" [dev-dependencies] assert_cmd = "2.0.12" infer = "0.15.0" +insta = "1.31.0" parse-display = "0.8.2" proptest = "1.2.0" rand = { version = "0.8.5", default-features = false, features = ["small_rng", "std"] } diff --git a/src/check.rs b/src/check.rs index 902f79241..9e1bdac37 100644 --- a/src/check.rs +++ b/src/check.rs @@ -10,7 +10,7 @@ use std::{ use crate::{ error::FinalError, - extension::{build_archive_file_suggestion, Extension}, + extension::{build_archive_file_suggestion, Extension, PRETTY_SUPPORTED_ALIASES, PRETTY_SUPPORTED_EXTENSIONS}, info, utils::{pretty_format_list_of_paths, try_infer_extension, user_wants_to_continue, EscapedPathDisplay}, warning, QuestionAction, QuestionPolicy, Result, @@ -66,7 +66,11 @@ pub fn check_mime_type( } else { // NOTE: If this actually produces no false positives, we can upgrade it in the future // to a warning and ask the user if he wants to continue decompressing. - info!(accessible, "Could not detect the extension of `{}`", path.display()); + info!( + accessible, + "Failed to confirm the format of `{}` by sniffing the contents, file might be misnamed", + path.display() + ); } Ok(ControlFlow::Continue(())) } @@ -123,32 +127,55 @@ pub fn check_archive_formats_position(formats: &[Extension], output_path: &Path) /// Check if all provided files have formats to decompress. pub fn check_missing_formats_when_decompressing(files: &[PathBuf], formats: &[Vec]) -> Result<()> { - let files_missing_format: Vec = files + let files_with_broken_extension: Vec<&PathBuf> = files .iter() .zip(formats) .filter(|(_, format)| format.is_empty()) - .map(|(input_path, _)| PathBuf::from(input_path)) + .map(|(input_path, _)| input_path) .collect(); - if let Some(path) = files_missing_format.first() { - let error = FinalError::with_title("Cannot decompress files without extensions") - .detail(format!( - "Files without supported extensions: {}", - pretty_format_list_of_paths(&files_missing_format) - )) - .detail("Decompression formats are detected automatically by the file extension") - .hint("Provide a file with a supported extension:") - .hint(" ouch decompress example.tar.gz") + if files_with_broken_extension.is_empty() { + return Ok(()); + } + + let (files_with_unsupported_extensions, files_missing_extension): (Vec<&PathBuf>, Vec<&PathBuf>) = + files_with_broken_extension + .iter() + .partition(|path| path.extension().is_some()); + + let mut error = FinalError::with_title("Cannot decompress files"); + + if !files_with_unsupported_extensions.is_empty() { + error = error.detail(format!( + "Files with unsupported extensions: {}", + pretty_format_list_of_paths(&files_with_unsupported_extensions) + )); + } + + if !files_missing_extension.is_empty() { + error = error.detail(format!( + "Files with missing extensions: {}", + pretty_format_list_of_paths(&files_missing_extension) + )); + } + + error = error + .detail("Decompression formats are detected automatically from file extension") + .hint(format!("Supported extensions are: {}", PRETTY_SUPPORTED_EXTENSIONS)) + .hint(format!("Supported aliases are: {}", PRETTY_SUPPORTED_ALIASES)); + + // If there's exactly one file, give a suggestion to use `--format` + if let &[path] = files_with_broken_extension.as_slice() { + error = error .hint("") - .hint("Or overwrite this option with the '--format' flag:") + .hint("Alternatively, you can pass an extension to the '--format' flag:") .hint(format!( " ouch decompress {} --format tar.gz", EscapedPathDisplay::new(path), )); - - return Err(error.into()); } - Ok(()) + + Err(error.into()) } /// Check if there is a first format when compressing, and returns it. diff --git a/src/extension.rs b/src/extension.rs index 016da7d52..ac79303d7 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -7,6 +7,11 @@ use bstr::ByteSlice; use self::CompressionFormat::*; use crate::{error::Error, warning}; +pub const SUPPORTED_EXTENSIONS: &[&str] = &["tar", "zip", "bz", "bz2", "gz", "lz4", "xz", "lzma", "sz", "zst"]; +pub const SUPPORTED_ALIASES: &[&str] = &["tgz", "tbz", "tlz4", "txz", "tzlma", "tsz", "tzst"]; +pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst"; +pub const PRETTY_SUPPORTED_ALIASES: &str = "tgz, tbz, tlz4, txz, tzlma, tsz, tzst"; + /// A wrapper around `CompressionFormat` that allows combinations like `tgz` #[derive(Debug, Clone, Eq)] #[non_exhaustive] @@ -85,11 +90,6 @@ impl CompressionFormat { } } -pub const SUPPORTED_EXTENSIONS: &[&str] = &[ - "tar", "tgz", "tbz", "tlz4", "txz", "tzlma", "tsz", "tzst", "zip", "bz", "bz2", "gz", "lz4", "xz", "lzma", "sz", - "zst", -]; - fn to_extension(ext: &[u8]) -> Option { Some(Extension::new( match ext { @@ -156,7 +156,7 @@ pub fn separate_known_extensions_from_name(path: &Path) -> (&Path, Vec // If the extension we got is a supported extension, generate the suggestion // at the position we found - if SUPPORTED_EXTENSIONS.contains(&maybe_extension) { + if SUPPORTED_EXTENSIONS.contains(&maybe_extension) || SUPPORTED_ALIASES.contains(&maybe_extension) { let mut path = path.to_string(); path.insert_str(position_to_insert - 1, suggested_extension); @@ -227,7 +227,6 @@ mod tests { #[test] fn test_extensions_from_path() { - use CompressionFormat::*; let path = Path::new("bolovo.tar.gz"); let extensions: Vec = extensions_from_path(path); diff --git a/tests/snapshots/ui__ui_test_err_compress_missing_extension.snap b/tests/snapshots/ui__ui_test_err_compress_missing_extension.snap new file mode 100644 index 000000000..92fb838ab --- /dev/null +++ b/tests/snapshots/ui__ui_test_err_compress_missing_extension.snap @@ -0,0 +1,14 @@ +--- +source: tests/ui.rs +expression: "run_ouch(\"ouch compress input output\", dir)" +--- +[ERROR] Cannot compress to 'output'. + - You shall supply the compression format + +hint: Try adding supported extensions (see --help): +hint: ouch compress ... output.tar.gz +hint: ouch compress ... output.zip +hint: +hint: Alternatively, you can overwrite this option by using the '--format' flag: +hint: ouch compress ... output --format tar.gz + diff --git a/tests/snapshots/ui__ui_test_err_decompress_missing_extension-2.snap b/tests/snapshots/ui__ui_test_err_decompress_missing_extension-2.snap new file mode 100644 index 000000000..5f5cddb36 --- /dev/null +++ b/tests/snapshots/ui__ui_test_err_decompress_missing_extension-2.snap @@ -0,0 +1,12 @@ +--- +source: tests/ui.rs +expression: "run_ouch(\"ouch decompress a b.unknown\", dir)" +--- +[ERROR] Cannot decompress files + - Files with unsupported extensions: /b.unknown + - Files with missing extensions: /a + - Decompression formats are detected automatically from file extension + +hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst +hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst + diff --git a/tests/snapshots/ui__ui_test_err_decompress_missing_extension-3.snap b/tests/snapshots/ui__ui_test_err_decompress_missing_extension-3.snap new file mode 100644 index 000000000..1fd4ab8c9 --- /dev/null +++ b/tests/snapshots/ui__ui_test_err_decompress_missing_extension-3.snap @@ -0,0 +1,14 @@ +--- +source: tests/ui.rs +expression: "run_ouch(\"ouch decompress b.unknown\", dir)" +--- +[ERROR] Cannot decompress files + - Files with unsupported extensions: /b.unknown + - Decompression formats are detected automatically from file extension + +hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst +hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst +hint: +hint: Alternatively, you can pass an extension to the '--format' flag: +hint: ouch decompress /b.unknown --format tar.gz + diff --git a/tests/snapshots/ui__ui_test_err_decompress_missing_extension.snap b/tests/snapshots/ui__ui_test_err_decompress_missing_extension.snap new file mode 100644 index 000000000..9850d48d3 --- /dev/null +++ b/tests/snapshots/ui__ui_test_err_decompress_missing_extension.snap @@ -0,0 +1,14 @@ +--- +source: tests/ui.rs +expression: "run_ouch(\"ouch decompress a\", dir)" +--- +[ERROR] Cannot decompress files + - Files with missing extensions: /a + - Decompression formats are detected automatically from file extension + +hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst +hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst +hint: +hint: Alternatively, you can pass an extension to the '--format' flag: +hint: ouch decompress /a --format tar.gz + diff --git a/tests/snapshots/ui__ui_test_err_missing_files-2.snap b/tests/snapshots/ui__ui_test_err_missing_files-2.snap new file mode 100644 index 000000000..bfa881e6a --- /dev/null +++ b/tests/snapshots/ui__ui_test_err_missing_files-2.snap @@ -0,0 +1,7 @@ +--- +source: tests/ui.rs +expression: "run_ouch(\"ouch decompress a b\", dir)" +--- +[ERROR] failed to canonicalize path `a` + - File not found + diff --git a/tests/snapshots/ui__ui_test_err_missing_files-3.snap b/tests/snapshots/ui__ui_test_err_missing_files-3.snap new file mode 100644 index 000000000..e88f1c3f6 --- /dev/null +++ b/tests/snapshots/ui__ui_test_err_missing_files-3.snap @@ -0,0 +1,7 @@ +--- +source: tests/ui.rs +expression: "run_ouch(\"ouch list a b\", dir)" +--- +[ERROR] failed to canonicalize path `a` + - File not found + diff --git a/tests/snapshots/ui__ui_test_err_missing_files.snap b/tests/snapshots/ui__ui_test_err_missing_files.snap new file mode 100644 index 000000000..549278e00 --- /dev/null +++ b/tests/snapshots/ui__ui_test_err_missing_files.snap @@ -0,0 +1,7 @@ +--- +source: tests/ui.rs +expression: "run_ouch(\"ouch compress a b\", dir)" +--- +[ERROR] failed to canonicalize path `a` + - File not found + diff --git a/tests/snapshots/ui__ui_test_ok_compress-2.snap b/tests/snapshots/ui__ui_test_ok_compress-2.snap new file mode 100644 index 000000000..f6ff7c7de --- /dev/null +++ b/tests/snapshots/ui__ui_test_ok_compress-2.snap @@ -0,0 +1,6 @@ +--- +source: tests/ui.rs +expression: "run_ouch(\"ouch compress input output.gz\", dir)" +--- +[INFO] Successfully compressed 'output.gz'. + diff --git a/tests/snapshots/ui__ui_test_ok_compress.snap b/tests/snapshots/ui__ui_test_ok_compress.snap new file mode 100644 index 000000000..a3cf31fc0 --- /dev/null +++ b/tests/snapshots/ui__ui_test_ok_compress.snap @@ -0,0 +1,7 @@ +--- +source: tests/ui.rs +expression: "run_ouch(\"ouch compress input output.zip\", dir)" +--- +[INFO] Compressing 'input'. +[INFO] Successfully compressed 'output.zip'. + diff --git a/tests/snapshots/ui__ui_test_ok_decompress.snap b/tests/snapshots/ui__ui_test_ok_decompress.snap new file mode 100644 index 000000000..a39f3446b --- /dev/null +++ b/tests/snapshots/ui__ui_test_ok_decompress.snap @@ -0,0 +1,8 @@ +--- +source: tests/ui.rs +expression: "run_ouch(\"ouch decompress output.zst\", dir)" +--- +[INFO] Failed to confirm the format of `output` by sniffing the contents, file might be misnamed +[INFO] Successfully decompressed archive in current directory. +[INFO] Files unpacked: 1 + diff --git a/tests/snapshots/ui__ui_test_usage_help_flag-2.snap b/tests/snapshots/ui__ui_test_usage_help_flag-2.snap new file mode 100644 index 000000000..b0b60d507 --- /dev/null +++ b/tests/snapshots/ui__ui_test_usage_help_flag-2.snap @@ -0,0 +1,25 @@ +--- +source: tests/ui.rs +expression: "output_to_string(ouch!(\"-h\"))" +--- +A command-line utility for easily compressing and decompressing files and directories. + +Usage: ouch [OPTIONS] + +Commands: + compress Compress one or more files into one output file [aliases: c] + decompress Decompresses one or more files, optionally into another folder [aliases: d] + list List contents of an archive [aliases: l, ls] + help Print this message or the help of the given subcommand(s) + +Options: + -y, --yes Skip [Y/n] questions positively + -n, --no Skip [Y/n] questions negatively + -A, --accessible Activate accessibility mode, reducing visual noise [env: ACCESSIBLE=] + -H, --hidden Ignores hidden files + -q, --quiet Silences output + -g, --gitignore Ignores files matched by git's ignore files + -f, --format Specify the format of the archive + -h, --help Print help (see more with '--help') + -V, --version Print version + diff --git a/tests/snapshots/ui__ui_test_usage_help_flag.snap b/tests/snapshots/ui__ui_test_usage_help_flag.snap new file mode 100644 index 000000000..71d60084b --- /dev/null +++ b/tests/snapshots/ui__ui_test_usage_help_flag.snap @@ -0,0 +1,48 @@ +--- +source: tests/ui.rs +expression: "output_to_string(ouch!(\"--help\"))" +--- +A command-line utility for easily compressing and decompressing files and directories. + +Supported formats: tar, zip, gz, xz/lzma, bz/bz2, lz4, sz, zst. + +Repository: https://github.com/ouch-org/ouch + +Usage: ouch [OPTIONS] + +Commands: + compress Compress one or more files into one output file [aliases: c] + decompress Decompresses one or more files, optionally into another folder [aliases: d] + list List contents of an archive [aliases: l, ls] + help Print this message or the help of the given subcommand(s) + +Options: + -y, --yes + Skip [Y/n] questions positively + + -n, --no + Skip [Y/n] questions negatively + + -A, --accessible + Activate accessibility mode, reducing visual noise + + [env: ACCESSIBLE=] + + -H, --hidden + Ignores hidden files + + -q, --quiet + Silences output + + -g, --gitignore + Ignores files matched by git's ignore files + + -f, --format + Specify the format of the archive + + -h, --help + Print help (see a summary with '-h') + + -V, --version + Print version + diff --git a/tests/ui.rs b/tests/ui.rs new file mode 100644 index 000000000..c72f05224 --- /dev/null +++ b/tests/ui.rs @@ -0,0 +1,124 @@ +/// Snapshot tests for Ouch's output. +/// +/// See CONTRIBUTING.md for a brief guide on how to use [`insta`] for these tests. +/// [`insta`]: https://docs.rs/insta + +#[macro_use] +mod utils; + +use std::{io, path::Path, process::Output}; + +#[cfg(not(windows))] +use insta::assert_display_snapshot as ui; + +// Don't run these on Windows +#[cfg(windows)] +use self::ignore as ui; +use crate::utils::run_in; + +fn testdir() -> io::Result<(tempfile::TempDir, &'static Path)> { + let dir = tempfile::tempdir()?; + let path = dir.path().to_path_buf().into_boxed_path(); + Ok((dir, Box::leak(path))) +} + +fn run_ouch(argv: &str, dir: &Path) -> String { + let output = utils::cargo_bin() + .args(argv.split_whitespace().skip(1)) + .current_dir(dir) + .output() + .unwrap_or_else(|err| { + panic!( + "Failed to run command\n\ + argv: {argv}\n\ + path: {dir:?}\n\ + err: {err}" + ) + }); + + redact_paths(&output_to_string(output), dir) +} + +// remove random tempdir paths from snapshots to make them deterministic +fn redact_paths(text: &str, path: &Path) -> String { + let redacted = ""; + + let path = path.display(); + let path = if cfg!(target_os = "macos") { + format!(r"/private{path}") + } else { + path.to_string() + }; + + text.replace(path.as_str(), redacted) +} + +fn output_to_string(output: Output) -> String { + String::from_utf8(output.stdout).unwrap() + std::str::from_utf8(&output.stderr).unwrap() +} + +#[test] +fn ui_test_err_compress_missing_extension() { + let (_dropper, dir) = testdir().unwrap(); + + // prepare + run_in(dir, "touch", "input").unwrap(); + + ui!(run_ouch("ouch compress input output", dir)); +} + +#[test] +fn ui_test_err_decompress_missing_extension() { + let (_dropper, dir) = testdir().unwrap(); + + run_in(dir, "touch", "a b.unknown").unwrap(); + + ui!(run_ouch("ouch decompress a", dir)); + ui!(run_ouch("ouch decompress a b.unknown", dir)); + ui!(run_ouch("ouch decompress b.unknown", dir)); +} + +#[test] +fn ui_test_err_missing_files() { + let (_dropper, dir) = testdir().unwrap(); + + ui!(run_ouch("ouch compress a b", dir)); + ui!(run_ouch("ouch decompress a b", dir)); + ui!(run_ouch("ouch list a b", dir)); +} + +#[test] +fn ui_test_ok_compress() { + let (_dropper, dir) = testdir().unwrap(); + + // prepare + run_in(dir, "touch", "input").unwrap(); + + ui!(run_ouch("ouch compress input output.zip", dir)); + ui!(run_ouch("ouch compress input output.gz", dir)); +} + +#[test] +fn ui_test_ok_decompress() { + let (_dropper, dir) = testdir().unwrap(); + + // prepare + run_in(dir, "touch", "input").unwrap(); + run_ouch("ouch compress input output.zst", dir); + + ui!(run_ouch("ouch decompress output.zst", dir)); +} + +#[test] +fn ui_test_usage_help_flag() { + ui!(output_to_string(ouch!("--help"))); + ui!(output_to_string(ouch!("-h"))); +} + +#[allow(unused)] +#[macro_export] +macro_rules! ignore { + ($expr:expr) => {{ + $expr + }}; +} diff --git a/tests/utils.rs b/tests/utils.rs index d1af39a65..bd8a66f78 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -1,16 +1,27 @@ -use std::{env, io::Write, path::PathBuf}; +// This warning is unavoidable when reusing testing utils. +#![allow(dead_code)] + +use std::{ + env, + ffi::OsStr, + io, + io::Write, + path::{Path, PathBuf}, + process::Output, +}; use assert_cmd::Command; use fs_err as fs; use rand::{Rng, RngCore}; +// Run ouch with the provided arguments, returns `assert_cmd::Output` #[macro_export] macro_rules! ouch { ($($e:expr),*) => { $crate::utils::cargo_bin() $(.arg($e))* .arg("--yes") - .unwrap(); + .unwrap() } } @@ -27,6 +38,16 @@ pub fn cargo_bin() -> Command { .unwrap_or_else(|| Command::cargo_bin("ouch").expect("Failed to find ouch executable")) } +/// Run a command inside of another folder. +/// +/// example: `run_in("/tmp", "touch", "a b c")` +pub fn run_in(folder: impl AsRef, bin: impl AsRef, args: &str) -> io::Result { + Command::new(bin) + .args(args.split_whitespace()) + .current_dir(folder) + .output() +} + // write random content to a file pub fn write_random_content(file: &mut impl Write, rng: &mut impl RngCore) { let mut data = Vec::new();