diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc9d1e79..8d66ecf0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ on: jobs: # Run 'dist plan' (or host) to determine what tasks we need to do plan: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-latest" outputs: val: ${{ steps.plan.outputs.manifest }} tag: ${{ !github.event.pull_request && github.ref_name || '' }} @@ -129,7 +129,7 @@ jobs: run: ${{ matrix.install_dist.run }} # Get the dist-manifest - name: Fetch local artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: artifacts-* path: target/distrib/ @@ -168,7 +168,7 @@ jobs: needs: - plan - build-local-artifacts - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-latest" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json @@ -177,14 +177,14 @@ jobs: with: submodules: recursive - name: Install cached dist - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: cargo-dist-cache path: ~/.cargo/bin/ - run: chmod +x ~/.cargo/bin/dist # Get all the local artifacts for the global tasks to use (for e.g. checksums) - name: Fetch local artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: artifacts-* path: target/distrib/ @@ -218,7 +218,7 @@ jobs: if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-latest" outputs: val: ${{ steps.host.outputs.manifest }} steps: @@ -226,14 +226,14 @@ jobs: with: submodules: recursive - name: Install cached dist - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: cargo-dist-cache path: ~/.cargo/bin/ - run: chmod +x ~/.cargo/bin/dist # Fetch artifacts from scratch-storage - name: Fetch artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: artifacts-* path: target/distrib/ @@ -253,7 +253,7 @@ jobs: path: dist-manifest.json # Create a GitHub Release while uploading all files to it - name: "Download GitHub Artifacts" - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: artifacts-* path: artifacts @@ -282,7 +282,7 @@ jobs: # still allowing individual publish jobs to skip themselves (for prereleases). # "host" however must run to completion, no skipping allowed! if: ${{ always() && needs.host.result == 'success' }} - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-latest" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 063d7d02..955e0f49 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -180,7 +180,7 @@ To ensure easy collaboration, we have guidelines for using Git and GitHub. ### Commit messages You can read this section in the Git book to learn how to write good commit -messages: https://git-scm.com/book/ch5-2.html. +messages: https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project. In addition, here are a few examples for a summary line when committing to uutils: diff --git a/Cargo.lock b/Cargo.lock index 0122f473..406bd2bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,20 +78,20 @@ dependencies = [ [[package]] name = "argmax" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7e3ef5e3a7f2c5e5a49d90ad087c03d38258e75155daac64deb62c50972c66" +checksum = "0144c58b55af0133ec3963ce5e4d07aad866e3bbcfdcddbf4590dbd7ad6ff557" dependencies = [ - "lazy_static", "libc", - "nix 0.24.3", + "nix 0.30.1", + "once_cell", ] [[package]] name = "assert_cmd" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" dependencies = [ "anstyle", "bstr", @@ -161,9 +161,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -196,18 +196,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.36" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.36" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" dependencies = [ "anstream", "anstyle", @@ -307,7 +307,7 @@ dependencies = [ "clap", "faccess", "filetime", - "nix 0.29.0", + "nix 0.30.1", "onig", "predicates", "pretty_assertions", @@ -461,17 +461,11 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" -version = "0.2.169" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libredox" @@ -523,20 +517,21 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "nix" -version = "0.24.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "cfg-if", + "cfg_aliases", "libc", ] [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -573,11 +568,11 @@ checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" [[package]] name = "onig" -version = "6.4.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "libc", "once_cell", "onig_sys", @@ -585,9 +580,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.8.1" +version = "69.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" dependencies = [ "cc", "pkg-config", @@ -931,9 +926,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", "getrandom", diff --git a/Cargo.toml b/Cargo.toml index 54ce18d3..5e6814e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,12 @@ description = "Rust implementation of GNU findutils" authors = ["uutils developers"] [dependencies] -chrono = "0.4.40" +chrono = "0.4.41" clap = "4.5" faccess = "0.2.4" walkdir = "2.5" regex = "1.11" -onig = { version = "6.4", default-features = false } +onig = { version = "6.5", default-features = false } uucore = { version = "0.0.30", features = ["entries", "fs", "fsext", "mode"] } nix = { version = "0.29", features = ["fs", "user"] } argmax = "0.3.1" @@ -25,7 +25,7 @@ ctor = "0.4.1" [dev-dependencies] assert_cmd = "2" filetime = "0.2" -nix = { version = "0.29", features = ["fs"] } +nix = { version = "0.30", features = ["fs"] } predicates = "3" serial_test = "3.2" tempfile = "3" diff --git a/README.md b/README.md index feb0ee79..7036908d 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ bash util/build-gnu.sh tests/misc/help-version.sh ## Comparing with GNU -![Evolution over time - GNU testsuite](https://github.com/uutils/findutils-tracking/blob/main/gnu-results.png?raw=true) -![Evolution over time - BFS testsuite](https://github.com/uutils/findutils-tracking/blob/main/bfs-results.png?raw=true) +![Evolution over time - GNU testsuite](https://github.com/uutils/findutils-tracking/blob/main/gnu-results.svg?raw=true) +![Evolution over time - BFS testsuite](https://github.com/uutils/findutils-tracking/blob/main/bfs-results.svg?raw=true) ## Build/run with BFS @@ -35,3 +35,4 @@ bash util/build-bfs.sh posix/basic ``` For more details, see https://github.com/uutils/findutils-tracking/ + \ No newline at end of file diff --git a/dist-workspace.toml b/dist-workspace.toml index 03a34b41..9ae104b2 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -17,3 +17,8 @@ pr-run-mode = "plan" install-path = "CARGO_HOME" # Whether to install an updater program install-updater = false +# Ignore out-of-date contents +allow-dirty = ["ci"] + +[dist.github-custom-runners] +x86_64-unknown-linux-gnu = "ubuntu-latest" diff --git a/src/find/matchers/entry.rs b/src/find/matchers/entry.rs index ab4b2b4c..fb28f2a0 100644 --- a/src/find/matchers/entry.rs +++ b/src/find/matchers/entry.rs @@ -24,7 +24,7 @@ enum Entry { } /// File types. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub enum FileType { Unknown, Fifo, diff --git a/src/find/matchers/fs.rs b/src/find/matchers/fs.rs index ee53c367..051604c3 100644 --- a/src/find/matchers/fs.rs +++ b/src/find/matchers/fs.rs @@ -53,7 +53,8 @@ pub fn get_file_system_type(path: &Path, cache: &RefCell>) -> URes let fs_list = uucore::fsext::read_fs_list()?; let result = fs_list .into_iter() - .find(|fs| fs.dev_id == dev_id) + .filter(|fs| fs.dev_id == dev_id) + .next_back() .map_or_else(String::new, |fs| fs.fs_type); // cache the latest query if not a match before diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index 05ad37ae..ca2ed15e 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -720,7 +720,9 @@ fn build_matcher_tree( i += 1; let matcher = UserMatcher::from_user_name(user) .or_else(|| Some(UserMatcher::from_uid(user.parse::().ok()?))) - .ok_or_else(|| format!("{user} is not the name of a known user"))?; + .ok_or_else(|| { + format!("invalid user name or UID argument to -user: '{user}'") + })?; Some(matcher.into_box()) } "-nouser" => Some(NoUserMatcher {}.into_box()), @@ -749,7 +751,9 @@ fn build_matcher_tree( i += 1; let matcher = GroupMatcher::from_group_name(group) .or_else(|| Some(GroupMatcher::from_gid(group.parse::().ok()?))) - .ok_or_else(|| format!("{group} is not the name of an existing group"))?; + .ok_or_else(|| { + format!("invalid group name or GID argument to -group: '{group}'") + })?; Some(matcher.into_box()) } "-nogroup" => Some(NoGroupMatcher {}.into_box()), diff --git a/src/find/matchers/printf.rs b/src/find/matchers/printf.rs index b6722152..aa7b0662 100644 --- a/src/find/matchers/printf.rs +++ b/src/find/matchers/printf.rs @@ -446,7 +446,8 @@ fn format_directive<'entry>( uucore::fsext::read_fs_list().expect("Could not find the filesystem info"); fs_list .into_iter() - .find(|fs| fs.dev_id == dev_id) + .filter(|fs| fs.dev_id == dev_id) + .next_back() .map_or_else(String::new, |fs| fs.fs_type) .into() } diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 95696c91..a1af4889 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -4,16 +4,16 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use std::error::Error; - use super::{FileType, Follow, Matcher, MatcherIO, WalkEntry}; +use std::collections::HashSet; +use std::error::Error; /// This matcher checks the type of the file. pub struct TypeMatcher { - file_type: FileType, + file_type: HashSet, } -fn parse(type_string: &str) -> Result> { +fn parse(type_string: &str, mode: &str) -> Result> { let file_type = match type_string { "f" => FileType::Regular, "d" => FileType::Directory, @@ -24,8 +24,20 @@ fn parse(type_string: &str) -> Result> { "s" => FileType::Socket, // D: door (Solaris) "D" => { + #[cfg(not(target_os = "solaris"))] + { + return Err(From::from(format!("{mode} D is not supported because Solaris doors are not supported on the platform find was compiled on."))); + } + #[cfg(target_os = "solaris")] + { + return Err(From::from(format!( + "Type argument {type_string} not supported yet" + ))); + } + } + "" => { return Err(From::from(format!( - "Type argument {type_string} not supported yet" + "Arguments to {mode} should contain at least one letter" ))) } _ => { @@ -39,29 +51,32 @@ fn parse(type_string: &str) -> Result> { impl TypeMatcher { pub fn new(type_string: &str) -> Result> { - let file_type = parse(type_string)?; - Ok(Self { file_type }) + let main_file_type = type_creator(type_string, "-type")?; + Ok(Self { + file_type: main_file_type, + }) } } impl Matcher for TypeMatcher { fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { - file_info.file_type() == self.file_type + self.file_type.contains(&file_info.file_type()) } } /// Like [TypeMatcher], but toggles whether symlinks are followed. pub struct XtypeMatcher { - file_type: FileType, + file_type: HashSet, } impl XtypeMatcher { pub fn new(type_string: &str) -> Result> { - let file_type = parse(type_string)?; - Ok(Self { file_type }) + let main_file_type = type_creator(type_string, "-xtype")?; + Ok(Self { + file_type: main_file_type, + }) } } - impl Matcher for XtypeMatcher { fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { let follow = if file_info.follow() { @@ -72,16 +87,45 @@ impl Matcher for XtypeMatcher { let file_type = follow .metadata(file_info) - .map(|m| m.file_type()) - .map(FileType::from); - - match file_type { - Ok(file_type) if file_type == self.file_type => true, - // Since GNU find 4.10, ELOOP will match -xtype l - Err(e) if self.file_type.is_symlink() && e.is_loop() => true, - _ => false, + .map(|m| m.file_type().into()) + .or_else(|e| { + if e.is_loop() { + Ok(FileType::Symlink) + } else { + Err(e) + } + }) + .unwrap_or(FileType::Unknown); + + self.file_type.contains(&file_type) + } +} + +fn type_creator(type_string: &str, mode: &str) -> Result, Box> { + let mut file_types = std::collections::HashSet::new(); + + if type_string.contains(',') { + for part in type_string.split(',') { + if part.is_empty() { + return Err(From::from(format!("find: Last file type in list argument to {mode} is missing, i.e., list is ending on: ','"))); + } + let file_type = parse(part, mode)?; + if !file_types.insert(file_type) { + return Err(From::from(format!( + "Duplicate file type '{part}' in the argument list to {mode}" + ))); + } } + } else { + if type_string.len() > 1 { + return Err(From::from(format!( + "Must separate multiple arguments to {mode} using: ','" + ))); + } + file_types.insert(parse(type_string, mode)?); } + + Ok(file_types) } #[cfg(test)] @@ -238,4 +282,58 @@ mod tests { let deps = FakeDependencies::new(); assert!(matcher.matches(&entry, &mut deps.new_matcher_io())); } + + #[test] + fn chained_arguments_type() { + assert!(TypeMatcher::new("").is_err()); + assert!(TypeMatcher::new("f,f").is_err()); + assert!(TypeMatcher::new("f,").is_err()); + assert!(TypeMatcher::new("x,y").is_err()); + assert!(TypeMatcher::new("fd").is_err()); + + assert!(XtypeMatcher::new("").is_err()); + assert!(XtypeMatcher::new("f,f").is_err()); + assert!(XtypeMatcher::new("f,").is_err()); + assert!(XtypeMatcher::new("x,y").is_err()); + assert!(XtypeMatcher::new("fd").is_err()); + } + + #[test] + fn type_matcher_multiple_valid_types() { + let deps = FakeDependencies::new(); + let file = get_dir_entry_for("test_data/simple", "abbbc"); + let dir = get_dir_entry_for("test_data", "simple"); + let symlink = get_dir_entry_for("test_data/links", "link-f"); + + let matcher = TypeMatcher::new("f,d").unwrap(); + assert!(matcher.matches(&file, &mut deps.new_matcher_io())); + assert!(matcher.matches(&dir, &mut deps.new_matcher_io())); + assert!(!matcher.matches(&symlink, &mut deps.new_matcher_io())); + + let matcher = TypeMatcher::new("l,d").unwrap(); + assert!(!matcher.matches(&file, &mut deps.new_matcher_io())); + assert!(matcher.matches(&dir, &mut deps.new_matcher_io())); + assert!(matcher.matches(&symlink, &mut deps.new_matcher_io())); + } + + #[cfg(unix)] + #[test] + fn xtype_matcher_mixed_types_with_symlinks() { + let deps = FakeDependencies::new(); + + // Regular file through symlink + let entry = get_dir_entry_follow("test_data/links", "link-f", Follow::Always); + let matcher = XtypeMatcher::new("f,l").unwrap(); + assert!(matcher.matches(&entry, &mut deps.new_matcher_io())); + + // Broken symlink + let broken_entry = get_dir_entry_for("test_data/links", "link-missing"); + assert!(matcher.matches(&broken_entry, &mut deps.new_matcher_io())); + + //looping symlink + let matcher2 = XtypeMatcher::new("l").unwrap(); + let looping_entry = get_dir_entry_for("test_data/links", "link-loop"); + assert!(matcher.matches(&looping_entry, &mut deps.new_matcher_io())); + assert!(matcher2.matches(&looping_entry, &mut deps.new_matcher_io())); + } } diff --git a/src/find/mod.rs b/src/find/mod.rs index f3008faf..b9c9cd3a 100644 --- a/src/find/mod.rs +++ b/src/find/mod.rs @@ -318,7 +318,7 @@ pub fn find_main(args: &[&str], deps: &dyn Dependencies) -> i32 { match do_find(&args[1..], deps) { Ok(ret) => ret, Err(e) => { - writeln!(&mut stderr(), "Error: {e}").unwrap(); + writeln!(&mut stderr(), "find: {e}").unwrap(); 1 } } diff --git a/tests/find_cmd_tests.rs b/tests/find_cmd_tests.rs index 7f7a53d6..53c68949 100644 --- a/tests/find_cmd_tests.rs +++ b/tests/find_cmd_tests.rs @@ -75,6 +75,112 @@ fn two_matchers_one_matches() { .stdout(predicate::str::is_empty()); } +#[test] +fn multiple_matcher_success() { + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "f,d,l", "-name", "abbbc"]) + .assert() + .success() + .stderr(predicate::str::is_empty()) + .stdout(predicate::str::contains("abbbc")); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "f,d,l", "-name", "abbbc"]) + .assert() + .success() + .stderr(predicate::str::is_empty()) + .stdout(predicate::str::contains("abbbc")); +} + +#[test] +fn multiple_matcher_failure() { + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "fd", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Must separate multiple arguments")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "f,", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("list is ending on: ','")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "f,f", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Duplicate file type")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains( + "should contain at least one letter", + )) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "x,y", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Unrecognised type argument")) + .stdout(predicate::str::is_empty()); + // x-type tests below + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "fd", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Must separate multiple arguments")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "f,", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("list is ending on: ','")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "f,f", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Duplicate file type")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains( + "should contain at least one letter", + )) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "x,y", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Unrecognised type argument")) + .stdout(predicate::str::is_empty()); +} + #[serial(working_dir)] #[test] fn files0_empty_file() { @@ -744,7 +850,9 @@ fn find_with_user_predicate() { .args(["test_data", "-user", " "]) .assert() .failure() - .stderr(predicate::str::contains("is not the name of a known user")) + .stderr(predicate::str::contains( + "invalid user name or UID argument to -user", + )) .stdout(predicate::str::is_empty()); } @@ -814,7 +922,7 @@ fn find_with_group_predicate() { .assert() .failure() .stderr(predicate::str::contains( - "is not the name of an existing group", + "invalid group name or GID argument to -group:", )) .stdout(predicate::str::is_empty()); } @@ -928,7 +1036,7 @@ fn find_age_range() { .failure() .code(1) .stderr(predicate::str::contains( - "Error: Expected a decimal integer (with optional + or - prefix) argument to", + "find: Expected a decimal integer (with optional + or - prefix) argument to", )) .stdout(predicate::str::is_empty()); }