From e1a1af9c8a5716ccb7a8f9da239a4f8cd61d5394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Fri, 15 Sep 2023 22:37:59 -0300 Subject: [PATCH 1/5] give a less ambiguous warning for sniff failure Ouch check for file signatures to be sure that the file indeed has the correct format as inferred by extension, when that fails, we output a message explaining that Ouch wasn't able to confirm the format, however, previous message was confusing with the other extension detection ones --- src/check.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/check.rs b/src/check.rs index 902f79241..62d97c523 100644 --- a/src/check.rs +++ b/src/check.rs @@ -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(())) } From dc25ede7ff0a0fd85712bcb3a1be1e4de870bb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Fri, 15 Sep 2023 22:42:23 -0300 Subject: [PATCH 2/5] add ui snapshot tests these tests are used to assert on Ouch's output for error reports and progress logging --- Cargo.lock | 141 ++++++++++++++++-- Cargo.toml | 1 + ...i_test_err_compress_missing_extension.snap | 14 ++ ...test_err_decompress_missing_extension.snap | 14 ++ .../ui__ui_test_err_missing_files-2.snap | 7 + .../ui__ui_test_err_missing_files-3.snap | 7 + .../ui__ui_test_err_missing_files.snap | 7 + .../snapshots/ui__ui_test_ok_compress-2.snap | 6 + tests/snapshots/ui__ui_test_ok_compress.snap | 7 + .../snapshots/ui__ui_test_ok_decompress.snap | 8 + .../ui__ui_test_usage_help_flag-2.snap | 25 ++++ .../ui__ui_test_usage_help_flag.snap | 48 ++++++ tests/ui.rs | 100 +++++++++++++ tests/utils.rs | 25 +++- 14 files changed, 394 insertions(+), 16 deletions(-) create mode 100644 tests/snapshots/ui__ui_test_err_compress_missing_extension.snap create mode 100644 tests/snapshots/ui__ui_test_err_decompress_missing_extension.snap create mode 100644 tests/snapshots/ui__ui_test_err_missing_files-2.snap create mode 100644 tests/snapshots/ui__ui_test_err_missing_files-3.snap create mode 100644 tests/snapshots/ui__ui_test_err_missing_files.snap create mode 100644 tests/snapshots/ui__ui_test_ok_compress-2.snap create mode 100644 tests/snapshots/ui__ui_test_ok_compress.snap create mode 100644 tests/snapshots/ui__ui_test_ok_decompress.snap create mode 100644 tests/snapshots/ui__ui_test_usage_help_flag-2.snap create mode 100644 tests/snapshots/ui__ui_test_usage_help_flag.snap create mode 100644 tests/ui.rs diff --git a/Cargo.lock b/Cargo.lock index e6333b8c3..f82241f1c 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", @@ -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/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.snap b/tests/snapshots/ui__ui_test_err_decompress_missing_extension.snap new file mode 100644 index 000000000..b5ae3307d --- /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 without extensions + - Files without supported extensions: /a + - Decompression formats are detected automatically by the file extension + +hint: Provide a file with a supported extension: +hint: ouch decompress example.tar.gz +hint: +hint: Or overwrite this option with 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..120808ae9 --- /dev/null +++ b/tests/ui.rs @@ -0,0 +1,100 @@ +// Snapshot tests for Ouch's output. + +#[macro_use] +mod utils; + +use std::{io, path::Path, process::Output}; + +use insta::assert_display_snapshot 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 path = format!("{}/", path.display()); + + text.replace(&path, "/") +} + +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").unwrap(); + + ui!(run_ouch("ouch decompress a", 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"))); +} 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(); From b45cc1ea64d53cc8da177c53c46b106dfb6d4796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Fri, 15 Sep 2023 22:59:40 -0300 Subject: [PATCH 3/5] add UI tests guide in CONTRIBUTING.md --- CONTRIBUTING.md | 30 +++++++++++++++++++++++++----- tests/ui.rs | 5 ++++- 2 files changed, 29 insertions(+), 6 deletions(-) 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/tests/ui.rs b/tests/ui.rs index 120808ae9..68c642e98 100644 --- a/tests/ui.rs +++ b/tests/ui.rs @@ -1,4 +1,7 @@ -// Snapshot tests for Ouch's output. +/// 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; From 6963cf21f36de0719c182480a3b52fd41fa973bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Fri, 15 Sep 2023 23:16:00 -0300 Subject: [PATCH 4/5] ui tests: fix for MacOS and skip for Windows --- Cargo.lock | 18 +++++++++--------- tests/ui.rs | 23 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f82241f1c..fd28f5290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,7 +793,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "regex-syntax 0.7.4", + "regex-syntax 0.7.5", "structmeta", "syn", ] @@ -974,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]] @@ -1003,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" diff --git a/tests/ui.rs b/tests/ui.rs index 68c642e98..efbf8c15d 100644 --- a/tests/ui.rs +++ b/tests/ui.rs @@ -8,8 +8,12 @@ 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)> { @@ -37,9 +41,16 @@ fn run_ouch(argv: &str, dir: &Path) -> String { // remove random tempdir paths from snapshots to make them deterministic fn redact_paths(text: &str, path: &Path) -> String { - let path = format!("{}/", path.display()); + let redacted = ""; - text.replace(&path, "/") + 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 { @@ -101,3 +112,11 @@ 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 + }}; +} From 7a631be678ad7f8d135d49f57c3326abe7f92a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Sat, 16 Sep 2023 13:44:45 -0300 Subject: [PATCH 5/5] Improve hints when decompressing with no extension refactored `check_missing_formats_when_decompressing` to be aware of missing extensions and unsupported extensions in order to give a more detailed error message --- src/check.rs | 55 +++++++++++++------ src/extension.rs | 15 +++-- ...st_err_decompress_missing_extension-2.snap | 12 ++++ ...st_err_decompress_missing_extension-3.snap | 14 +++++ ...test_err_decompress_missing_extension.snap | 12 ++-- tests/ui.rs | 4 +- 6 files changed, 81 insertions(+), 31 deletions(-) create mode 100644 tests/snapshots/ui__ui_test_err_decompress_missing_extension-2.snap create mode 100644 tests/snapshots/ui__ui_test_err_decompress_missing_extension-3.snap diff --git a/src/check.rs b/src/check.rs index 62d97c523..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, @@ -127,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_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 index b5ae3307d..9850d48d3 100644 --- a/tests/snapshots/ui__ui_test_err_decompress_missing_extension.snap +++ b/tests/snapshots/ui__ui_test_err_decompress_missing_extension.snap @@ -2,13 +2,13 @@ source: tests/ui.rs expression: "run_ouch(\"ouch decompress a\", dir)" --- -[ERROR] Cannot decompress files without extensions - - Files without supported extensions: /a - - Decompression formats are detected automatically by the file extension +[ERROR] Cannot decompress files + - Files with missing extensions: /a + - Decompression formats are detected automatically from file extension -hint: Provide a file with a supported extension: -hint: ouch decompress example.tar.gz +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: Or overwrite this option with the '--format' flag: +hint: Alternatively, you can pass an extension to the '--format' flag: hint: ouch decompress /a --format tar.gz diff --git a/tests/ui.rs b/tests/ui.rs index efbf8c15d..c72f05224 100644 --- a/tests/ui.rs +++ b/tests/ui.rs @@ -71,9 +71,11 @@ fn ui_test_err_compress_missing_extension() { fn ui_test_err_decompress_missing_extension() { let (_dropper, dir) = testdir().unwrap(); - run_in(dir, "touch", "a").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]