diff --git a/Cargo.lock b/Cargo.lock index 7923803a..ad3edaed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,21 +81,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "assert_cmd" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9" -dependencies = [ - "anstyle", - "bstr", - "libc", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -206,16 +191,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "diff" -version = "0.1.13" +name = "ctor" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +dependencies = [ + "ctor-proc-macro", + "dtor", +] [[package]] -name = "difflib" -version = "0.4.0" +name = "ctor-proc-macro" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "displaydoc" @@ -228,6 +232,33 @@ dependencies = [ "syn", ] +[[package]] +name = "dns-lookup" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e39034cee21a2f5bbb66ba0e3689819c4bb5d00382a282006e802a7ffa6c41d" +dependencies = [ + "cfg-if", + "libc", + "socket2", + "windows-sys 0.60.2", +] + +[[package]] +name = "dtor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "dunce" version = "1.0.4" @@ -277,31 +308,20 @@ name = "findutils" version = "0.8.0" dependencies = [ "argmax", - "assert_cmd", "chrono", "clap", + "ctor", "faccess", "filetime", "nix 0.31.2", "onig", - "predicates", - "pretty_assertions", "regex", - "serial_test", "tempfile", "uucore", + "uutests", "walkdir", ] -[[package]] -name = "float-cmp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" -dependencies = [ - "num-traits", -] - [[package]] name = "fluent" version = "0.17.0" @@ -347,42 +367,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "getrandom" version = "0.3.1" @@ -450,6 +434,53 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.0", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "js-sys" version = "0.3.68" @@ -482,24 +513,11 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" -[[package]] -name = "lock_api" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" -version = "0.4.14" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" @@ -532,10 +550,10 @@ dependencies = [ ] [[package]] -name = "normalize-line-endings" -version = "0.3.0" +name = "num-conv" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-traits" @@ -546,6 +564,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.21.0" @@ -583,41 +610,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkg-config" version = "0.3.24" @@ -625,33 +617,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" [[package]] -name = "predicates" -version = "3.1.4" +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ - "anstyle", - "difflib", - "float-cmp", - "normalize-line-endings", - "predicates-core", - "regex", + "portable-atomic", ] [[package]] -name = "predicates-core" -version = "1.0.6" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "predicates-tree" -version = "1.0.1" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aee95d988ee893cb35c06b148c80ed2cd52c8eea927f50ba7a0be1a786aeab73" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "predicates-core", - "treeline", + "zerocopy", ] [[package]] @@ -682,6 +674,35 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.5.3" @@ -720,6 +741,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rlimit" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f35ee2729c56bb610f6dba436bf78135f728b7373bdffae2ec815b2d3eb98cc3" +dependencies = [ + "libc", +] + [[package]] name = "rustc-hash" version = "2.1.1" @@ -748,27 +778,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scc" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" -dependencies = [ - "sdd", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sdd" -version = "3.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" - [[package]] name = "self_cell" version = "1.2.0" @@ -782,25 +791,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" [[package]] -name = "serial_test" -version = "3.4.0" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "futures-executor", - "futures-util", - "log", - "once_cell", - "parking_lot", - "scc", - "serial_test_derive", + "serde_derive", ] [[package]] -name = "serial_test_derive" -version = "3.4.0" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -813,18 +816,22 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "slab" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" - [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.0", +] + [[package]] name = "strsim" version = "0.11.1" @@ -885,6 +892,39 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.1" @@ -895,12 +935,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "treeline" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" - [[package]] name = "type-map" version = "0.5.1" @@ -954,15 +988,18 @@ checksum = "a8038531f506a34ab4612b93f97d5f40759768cd34a83fd2af041b84fcbde474" dependencies = [ "bstr", "clap", + "dns-lookup", "dunce", "fluent", "fluent-bundle", "fluent-syntax", + "jiff", "libc", "nix 0.30.1", "os_display", "rustc-hash", "thiserror", + "time", "unic-langid", "uucore_procs", "wild", @@ -981,12 +1018,21 @@ dependencies = [ ] [[package]] -name = "wait-timeout" -version = "0.2.0" +name = "uutests" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "25b8a1598ddd20230a5df59d642e2e5741b0b90038bde8684c984515f947fda5" dependencies = [ + "ctor", "libc", + "nix 0.30.1", + "pretty_assertions", + "rand", + "regex", + "rlimit", + "tempfile", + "uucore", + "xattr", ] [[package]] @@ -1288,12 +1334,42 @@ dependencies = [ "bitflags 2.4.1", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index 4ef1cd6c..1273c52c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,13 +21,11 @@ uucore = { version = "0.7.0", features = ["entries", "fs", "fsext", "mode"] } walkdir = "2.5" [dev-dependencies] -assert_cmd = "2" +ctor = "0.6" filetime = "0.2" nix = { version = "0.31", features = ["fs"] } -predicates = "3" -pretty_assertions = "1.4.1" -serial_test = "3.4" tempfile = "3" +uutests = "0.7.0" [[bin]] name = "find" diff --git a/tests/common/mod.rs b/tests/common/mod.rs index aa8de375..4ccf3852 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -8,3 +8,11 @@ // in one test but not another can cause a dead code warning. #[allow(dead_code)] pub mod test_helpers; + +// Set UUTESTS_BINARY_PATH before any tests run. The value is only used by +// TestScenario::new() to satisfy its internal setup; tests that need a +// specific binary pass it explicitly via TestScenario::cmd(BINARY_PATH). +#[ctor::ctor] +fn init() { + std::env::set_var("UUTESTS_BINARY_PATH", env!("CARGO_BIN_EXE_find")); +} diff --git a/tests/find_cmd_tests.rs b/tests/find_cmd_tests.rs deleted file mode 100644 index 59638b90..00000000 --- a/tests/find_cmd_tests.rs +++ /dev/null @@ -1,1205 +0,0 @@ -// Copyright 2021 Chad Williamson -// -// Use of this source code is governed by an MIT-style license that can be -// found in the LICENSE file or at https://opensource.org/licenses/MIT. - -// This file contains integration tests for the find command. -// -// Note: the `serial` macro is used on tests that make assumptions about the -// working directory, since we have at least one test that needs to change it. - -use assert_cmd::cargo_bin_cmd; -use predicates::prelude::*; -use regex::Regex; -use serial_test::serial; -use std::fs::{self, File}; -use std::io::{Read, Write}; -use std::{env, io::ErrorKind}; -use tempfile::Builder; - -#[cfg(unix)] -use std::os::unix::fs::symlink; - -#[cfg(windows)] -use std::os::windows::fs::{symlink_dir, symlink_file}; - -use common::test_helpers::fix_up_slashes; - -mod common; - -// Variants of fix_up_slashes that properly escape the forward slashes for being -// in a regex. -#[cfg(windows)] -fn fix_up_regex_slashes(re: &str) -> String { - re.replace("/", "\\\\") -} - -#[cfg(not(windows))] -fn fix_up_regex_slashes(re: &str) -> String { - re.to_owned() -} - -#[serial(working_dir)] -#[test] -fn no_args() { - cargo_bin_cmd!("find") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("test_data")); -} - -#[serial(working_dir)] -#[test] -fn two_matchers_both_match() { - cargo_bin_cmd!("find") - .args(["-type", "d", "-name", "test_data"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("test_data")); -} - -#[serial(working_dir)] -#[test] -fn two_matchers_one_matches() { - cargo_bin_cmd!("find") - .args(["-type", "f", "-name", "test_data"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::is_empty()); -} - -#[test] -fn multiple_matcher_success() { - cargo_bin_cmd!("find") - .args(["-type", "f,d,l", "-name", "abbbc"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("abbbc")); - - cargo_bin_cmd!("find") - .args(["-xtype", "f,d,l", "-name", "abbbc"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("abbbc")); -} - -#[test] -fn multiple_matcher_failure() { - cargo_bin_cmd!("find") - .args(["-type", "fd", "-name", "abbb"]) - .assert() - .failure() - .stderr(predicate::str::contains("Must separate multiple arguments")) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["-type", "f,", "-name", "abbb"]) - .assert() - .failure() - .stderr(predicate::str::contains("list is ending on: ','")) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["-type", "f,f", "-name", "abbb"]) - .assert() - .failure() - .stderr(predicate::str::contains("Duplicate file type")) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["-type", "", "-name", "abbb"]) - .assert() - .failure() - .stderr(predicate::str::contains( - "should contain at least one letter", - )) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["-type", "x,y", "-name", "abbb"]) - .assert() - .failure() - .stderr(predicate::str::contains("Unrecognised type argument")) - .stdout(predicate::str::is_empty()); - // x-type tests below - cargo_bin_cmd!("find") - .args(["-xtype", "fd", "-name", "abbb"]) - .assert() - .failure() - .stderr(predicate::str::contains("Must separate multiple arguments")) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["-xtype", "f,", "-name", "abbb"]) - .assert() - .failure() - .stderr(predicate::str::contains("list is ending on: ','")) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["-xtype", "f,f", "-name", "abbb"]) - .assert() - .failure() - .stderr(predicate::str::contains("Duplicate file type")) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["-xtype", "", "-name", "abbb"]) - .assert() - .failure() - .stderr(predicate::str::contains( - "should contain at least one letter", - )) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .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() { - cargo_bin_cmd!("find") - .args(["-files0-from", "./test_data/simple/abbbc"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::is_empty()); -} - -#[serial(working_dir)] -#[test] -fn files0_file_basic_success() { - cargo_bin_cmd!("find") - .args(["-files0-from", "./test_data/simple/abbbc"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::is_empty()); - - #[cfg(unix)] - { - let temp_dir = Builder::new().prefix("find_cmd_").tempdir().unwrap(); - let test_file = temp_dir.path().join("test_files0"); - let mut file = File::create(&test_file).expect("created test file"); - file.write_all(b"./test_data/\0./test_data/simple/\0") - .expect("file write error"); - - cargo_bin_cmd!("find") - .args(["-files0-from", &test_file.display().to_string()]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("/test_data/")); - } -} - -#[serial(working_dir)] -#[test] -fn files0_empty_pipe() { - cargo_bin_cmd!("find") - .args(["-files0-from", "-"]) - .write_stdin(b"") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::is_empty()); -} - -#[serial(working_dir)] -#[test] -fn files0_pipe_basic() { - cargo_bin_cmd!("find") - .write_stdin(b"./test_data/simple\0./test_data/links") - .args(["-files0-from", "-"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("./test_data/")); -} - -#[serial(working_dir)] -#[test] -fn files0_pipe_double_nul() { - cargo_bin_cmd!("find") - .write_stdin(b"./test_data/simple\0\0./test_data/links") - .args(["-files0-from", "-"]) - .assert() - .success() - .stderr(predicate::str::contains("invalid zero-length file name")) - .stdout(predicate::str::contains("./test_data/")); -} - -#[serial(working_dir)] -#[test] -fn files0_no_file() { - #[cfg(unix)] - { - cargo_bin_cmd!("find") - .args(["-files0-from", "xyz.nonexistentFile"]) - .assert() - .failure() - .stderr(predicate::str::contains("No such file or directory")) - .stdout(predicate::str::is_empty()); - } - #[cfg(windows)] - { - cargo_bin_cmd!("find") - .args(["-files0-from", "xyz.nonexistantFile"]) - .assert() - .failure() - .stderr(predicate::str::contains( - "The system cannot find the file specified.", - )) - .stdout(predicate::str::is_empty()); - } -} - -#[serial(working_dir)] -#[test] -fn files0_basic() { - cargo_bin_cmd!("find") - .arg("-files0-from") - .assert() - .failure() - .stderr(predicate::str::contains("missing argument to -files0-from")) - .stdout(predicate::str::is_empty()); -} - -#[test] -fn matcher_with_side_effects_at_end() { - let temp_dir = Builder::new().prefix("find_cmd_").tempdir().unwrap(); - - let temp_dir_path = temp_dir.path().to_string_lossy(); - let test_file = temp_dir.path().join("test"); - File::create(&test_file).expect("created test file"); - - cargo_bin_cmd!("find") - .args([&temp_dir_path, "-name", "test", "-delete"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::is_empty()); - - assert!(!test_file.exists(), "test file should be deleted"); - assert!(temp_dir.path().exists(), "temp dir should NOT be deleted"); -} - -#[test] -fn matcher_with_side_effects_in_front() { - let temp_dir = Builder::new().prefix("find_cmd_").tempdir().unwrap(); - - let temp_dir_path = temp_dir.path().to_string_lossy(); - let test_file = temp_dir.path().join("test"); - File::create(&test_file).expect("created test file"); - - cargo_bin_cmd!("find") - .args([&temp_dir_path, "-delete", "-name", "test"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::is_empty()); - - assert!(!test_file.exists(), "test file should be deleted"); - assert!(!temp_dir.path().exists(), "temp dir should also be deleted"); -} - -// This could be covered by a unit test in principle... in practice, changing -// the working dir can't be done safely in unit tests unless `--test-threads=1` -// or `serial` goes everywhere, and it doesn't seem possible to get an -// appropriate `walkdir::DirEntry` for "." without actually changing dirs -// (or risking deletion of the repo itself). -#[serial(working_dir)] -#[test] -fn delete_on_dot_dir() { - let temp_dir = Builder::new().prefix("example").tempdir().unwrap(); - let original_dir = env::current_dir().unwrap(); - env::set_current_dir(temp_dir.path()).expect("working dir changed"); - - // "." should be matched (confirmed by the print), but not deleted. - cargo_bin_cmd!("find") - .args([".", "-delete", "-print"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff(".\n")); - - env::set_current_dir(original_dir).expect("restored original working dir"); - - assert!(temp_dir.path().exists(), "temp dir should still exist"); -} - -#[test] -fn regex_types() { - let temp_dir = Builder::new().prefix("find_cmd_").tempdir().unwrap(); - - let temp_dir_path = temp_dir.path().to_string_lossy(); - let test_file = temp_dir.path().join("teeest"); - File::create(test_file).expect("created test file"); - - cargo_bin_cmd!("find") - .args([&temp_dir_path, "-regex", &fix_up_regex_slashes(".*/tE+st")]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args([&temp_dir_path, "-iregex", &fix_up_regex_slashes(".*/tE+st")]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("teeest")); - - cargo_bin_cmd!("find") - .args([ - &temp_dir_path, - "-regextype", - "posix-basic", - "-regex", - &fix_up_regex_slashes(r".*/te\{1,3\}st"), - ]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("teeest")); - - cargo_bin_cmd!("find") - .args([ - &temp_dir_path, - "-regextype", - "posix-extended", - "-regex", - &fix_up_regex_slashes(".*/te{1,3}st"), - ]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("teeest")); - - cargo_bin_cmd!("find") - .args([ - &temp_dir_path, - "-regextype", - "ed", - "-regex", - &fix_up_regex_slashes(r".*/te\{1,3\}st"), - ]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("teeest")); - - cargo_bin_cmd!("find") - .args([ - &temp_dir_path, - "-regextype", - "sed", - "-regex", - &fix_up_regex_slashes(r".*/te\{1,3\}st"), - ]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("teeest")); -} - -#[test] -fn empty_files() { - let temp_dir = Builder::new().prefix("find_cmd_").tempdir().unwrap(); - let temp_dir_path = temp_dir.path().to_string_lossy(); - - cargo_bin_cmd!("find") - .args([&temp_dir_path, "-empty"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(fix_up_slashes(&format!("{temp_dir_path}\n"))); - - let test_file_path = temp_dir.path().join("test"); - let mut test_file = File::create(&test_file_path).unwrap(); - - cargo_bin_cmd!("find") - .args([&temp_dir_path, "-empty"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(fix_up_slashes(&format!( - "{}\n", - test_file_path.to_string_lossy() - ))); - - let subdir_path = temp_dir.path().join("subdir"); - std::fs::create_dir(&subdir_path).unwrap(); - - cargo_bin_cmd!("find") - .args([&temp_dir_path, "-empty", "-sorted"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(fix_up_slashes(&format!( - "{}\n{}\n", - subdir_path.to_string_lossy(), - test_file_path.to_string_lossy() - ))); - - write!(test_file, "x").unwrap(); - test_file.sync_all().unwrap(); - - cargo_bin_cmd!("find") - .args([&temp_dir_path, "-empty", "-sorted"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(fix_up_slashes(&format!( - "{}\n", - subdir_path.to_string_lossy(), - ))); -} - -#[serial(working_dir)] -#[test] -fn find_printf() { - #[cfg(unix)] - { - if let Err(e) = symlink("abbbc", "test_data/links/link-f") { - assert!( - e.kind() == ErrorKind::AlreadyExists, - "Failed to create sym link: {e:?}" - ); - } - if let Err(e) = symlink("subdir", "test_data/links/link-d") { - assert!( - e.kind() == ErrorKind::AlreadyExists, - "Failed to create sym link: {e:?}" - ); - } - if let Err(e) = symlink("missing", "test_data/links/link-missing") { - assert!( - e.kind() == ErrorKind::AlreadyExists, - "Failed to create sym link: {e:?}" - ); - } - if let Err(e) = symlink("abbbc/x", "test_data/links/link-notdir") { - assert!( - e.kind() == ErrorKind::AlreadyExists, - "Failed to create sym link: {e:?}" - ); - } - if let Err(e) = symlink("link-loop", "test_data/links/link-loop") { - assert!( - e.kind() == ErrorKind::AlreadyExists, - "Failed to create sym link: {e:?}" - ); - } - } - #[cfg(windows)] - { - if let Err(e) = symlink_file("abbbc", "test_data/links/link-f") { - assert!( - e.kind() == ErrorKind::AlreadyExists, - "Failed to create sym link: {:?}", - e - ); - } - if let Err(e) = symlink_dir("subdir", "test_data/links/link-d") { - assert!( - e.kind() == ErrorKind::AlreadyExists, - "Failed to create sym link: {:?}", - e - ); - } - if let Err(e) = symlink_file("missing", "test_data/links/link-missing") { - assert!( - e.kind() == ErrorKind::AlreadyExists, - "Failed to create sym link: {:?}", - e - ); - } - if let Err(e) = symlink_file("abbbc/x", "test_data/links/link-notdir") { - assert!( - e.kind() == ErrorKind::AlreadyExists, - "Failed to create sym link: {:?}", - e - ); - } - } - - cargo_bin_cmd!("find") - .args([ - &fix_up_slashes("./test_data/simple"), - "-sorted", - "-printf", - "%f %d %h %H %p %P %y\n", - ]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff(fix_up_slashes( - "simple 0 ./test_data ./test_data/simple \ - ./test_data/simple d\n\ - abbbc 1 ./test_data/simple ./test_data/simple \ - ./test_data/simple/abbbc abbbc f\n\ - subdir 1 ./test_data/simple ./test_data/simple \ - ./test_data/simple/subdir subdir d\n\ - ABBBC 2 ./test_data/simple/subdir ./test_data/simple \ - ./test_data/simple/subdir/ABBBC subdir/ABBBC f\n", - ))); - - fs::create_dir_all("a").expect("Failed to create directory 'a'"); - let output = cargo_bin_cmd!("find") - .args(["a", "-printf", "%A+"]) - .assert() - .success() - .get_output() - .stdout - .clone(); - - let output_str = String::from_utf8(output).expect("Invalid UTF-8 in output"); - - println!("Actual output: '{}'", output_str.trim()); - - let re = Regex::new(r"^\d{4}-\d{2}-\d{2}\+\d{2}:\d{2}:\d{2}\.\d{9}0$") - .expect("Failed to compile regex"); - - assert!( - re.is_match(output_str.trim()), - "Output did not match expected timestamp format" - ); - - cargo_bin_cmd!("find") - .args([ - &fix_up_slashes("./test_data/links"), - "-sorted", - "-type", - "l", - "-printf", - "%f %l %y %Y\n", - ]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff( - [ - "link-d subdir l d\n", - "link-f abbbc l f\n", - #[cfg(unix)] - "link-loop link-loop l L\n", - "link-missing missing l N\n", - // We can't detect ENOTDIR on non-unix platforms yet. - #[cfg(not(unix))] - "link-notdir abbbc/x l ?\n", - #[cfg(unix)] - "link-notdir abbbc/x l N\n", - ] - .join(""), - )); -} - -#[cfg(unix)] -#[serial(working_dir)] -#[test] -fn find_perm() { - cargo_bin_cmd!("find") - .args(["-perm", "+rwx"]) - .assert() - .success(); - - cargo_bin_cmd!("find") - .args(["-perm", "u+rwX"]) - .assert() - .success(); - - cargo_bin_cmd!("find") - .args(["-perm", "u=g"]) - .assert() - .success(); -} - -#[cfg(unix)] -#[serial(working_dir)] -#[test] -fn find_inum() { - use std::fs::metadata; - use std::os::unix::fs::MetadataExt; - - let inum = metadata("test_data/simple/abbbc") - .expect("metadata for abbbc") - .ino() - .to_string(); - - cargo_bin_cmd!("find") - .args(["test_data", "-inum", &inum]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("abbbc")); -} - -#[cfg(not(unix))] -#[test] -fn find_inum() { - cargo_bin_cmd!("find") - .args(["test_data", "-inum", "1"]) - .assert() - .failure() - .stderr(predicate::str::contains("not available on this platform")) - .stdout(predicate::str::is_empty()); -} - -#[cfg(unix)] -#[serial(working_dir)] -#[test] -fn find_links() { - cargo_bin_cmd!("find") - .args(["test_data", "-links", "1"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("abbbc")); -} - -#[cfg(not(unix))] -#[test] -fn find_links() { - cargo_bin_cmd!("find") - .args(["test_data", "-links", "1"]) - .assert() - .failure() - .stderr(predicate::str::contains("not available on this platform")) - .stdout(predicate::str::is_empty()); -} - -#[serial(working_dir)] -#[test] -fn find_mount_xdev() { - // Make sure that -mount/-xdev doesn't prune unexpectedly. - // TODO: Test with a mount point in the search. - - cargo_bin_cmd!("find") - .args(["test_data", "-mount"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("abbbc")); - - cargo_bin_cmd!("find") - .args(["test_data", "-xdev"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("abbbc")); -} - -#[serial(working_dir)] -#[test] -fn find_accessible() { - cargo_bin_cmd!("find") - .args(["test_data", "-readable"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("abbbc")); - - cargo_bin_cmd!("find") - .args(["test_data", "-writable"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("abbbc")); - - #[cfg(unix)] - cargo_bin_cmd!("find") - .args(["test_data", "-executable"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("abbbc").not()); -} - -#[serial(working_dir)] -#[test] -fn find_time() { - let args = ["1", "+1", "-1"]; - let exception_args = ["1%2", "1%2%3", "1a2", "1%2a", "abc", "-", "+", "%"]; - - let tests = [ - "-atime", - #[cfg(unix)] - "-ctime", - "-mtime", - ]; - tests.iter().for_each(|flag| { - args.iter().for_each(|arg| { - cargo_bin_cmd!("find") - .args(["./test_data/simple", flag, arg]) - .assert() - .success() - .stderr(predicate::str::is_empty()); - }); - - exception_args.iter().for_each(|arg| { - cargo_bin_cmd!("find") - .args([".", flag, arg]) - .assert() - .failure() - .stdout(predicate::str::is_empty()); - }); - }); -} - -#[test] -fn expression_empty_parentheses() { - cargo_bin_cmd!("find") - .args(["-true", "(", ")"]) - .assert() - .failure() - .stderr(predicate::str::contains( - "empty parentheses are not allowed", - )) - .stdout(predicate::str::is_empty()); -} - -#[test] -#[cfg(unix)] -#[serial(working_dir)] -fn find_with_user_predicate() { - // Considering the different test environments, - // the test code can only use a specific default user to perform the test, - // such as the root user on Linux. - cargo_bin_cmd!("find") - .args(["test_data", "-user", "root"]) - .assert() - .success() - .stderr(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["test_data", "-user", ""]) - .assert() - .failure() - .stderr(predicate::str::contains("empty")) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["test_data", "-user", " "]) - .assert() - .failure() - .stderr(predicate::str::contains( - "invalid user name or UID argument to -user", - )) - .stdout(predicate::str::is_empty()); -} - -#[test] -#[serial(working_dir)] -fn find_with_nouser_predicate() { - cargo_bin_cmd!("find") - .args(["test_data", "-nouser"]) - .assert() - .success() - .stdout(predicate::str::is_empty()) - .stderr(predicate::str::is_empty()); -} - -#[test] -#[cfg(unix)] -#[serial(working_dir)] -fn find_with_uid_predicate() { - use std::os::unix::fs::MetadataExt; - use std::path::Path; - - let path = Path::new("./test_data"); - let uid = path.metadata().unwrap().uid(); - - cargo_bin_cmd!("find") - .args(["test_data", "-uid", &uid.to_string()]) - .assert() - .success() - .stderr(predicate::str::is_empty()); -} - -#[test] -#[serial(working_dir)] -fn find_with_group_predicate() { - // Considering the different test environments, - // the test code can only use a specific default user group for the test, - // such as the root user group on Linux. - #[cfg(target_os = "linux")] - cargo_bin_cmd!("find") - .args(["test_data", "-group", "root"]) - .assert() - .success() - .stderr(predicate::str::is_empty()); - - #[cfg(target_os = "macos")] - cargo_bin_cmd!("find") - .args(["test_data", "-group", "staff"]) - .assert() - .success() - .stderr(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["test_data", "-group", ""]) - .assert() - .failure() - .stderr(predicate::str::contains("empty")) - .stdout(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args(["test_data", "-group", " "]) - .assert() - .failure() - .stderr(predicate::str::contains( - "invalid group name or GID argument to -group:", - )) - .stdout(predicate::str::is_empty()); -} - -#[test] -#[serial(working_dir)] -fn find_with_nogroup_predicate() { - cargo_bin_cmd!("find") - .args(["test_data", "-nogroup"]) - .assert() - .success() - .stdout(predicate::str::is_empty()) - .stderr(predicate::str::is_empty()); -} - -#[test] -#[cfg(unix)] -#[serial(working_dir)] -fn find_with_gid_predicate() { - use std::os::unix::fs::MetadataExt; - use std::path::Path; - - let path = Path::new("./test_data"); - let gid = path.metadata().unwrap().gid(); - - cargo_bin_cmd!("find") - .args(["test_data", "-gid", &gid.to_string()]) - .assert() - .success() - .stderr(predicate::str::is_empty()); -} - -#[test] -#[serial(working_dir)] -fn find_newer_xy() { - let options = [ - "a", - #[cfg(not(target_os = "linux"))] - "B", - #[cfg(unix)] - "c", - "m", - ]; - - for x in options { - for y in options { - let arg = &format!("-newer{x}{y}"); - cargo_bin_cmd!("find") - .args([ - "./test_data/simple/subdir", - arg, - "./test_data/simple/subdir/ABBBC", - ]) - .assert() - .success() - .stderr(predicate::str::is_empty()); - } - } - - let args = [ - "-newerat", - #[cfg(not(target_os = "linux"))] - "-newerBt", - #[cfg(unix)] - "-newerct", - "-newermt", - ]; - let times = ["jan 01, 2000", "jan 01, 2000 00:00:00"]; - - for arg in args { - for time in times { - cargo_bin_cmd!("find") - .args(["./test_data/simple/subdir", arg, time]) - .assert() - .success() - .stderr(predicate::str::is_empty()); - } - } -} - -#[test] -#[serial(working_dir)] -fn find_age_range() { - let args = ["-amin", "-cmin", "-mmin"]; - let times = ["-60", "-120", "-240", "+60", "+120", "+240"]; - let time_strings = [ - "\"-60\"", "\"-120\"", "\"-240\"", "\"-60\"", "\"-120\"", "\"-240\"", - ]; - - for arg in args { - for time in times { - cargo_bin_cmd!("find") - .args(["test_data/simple", arg, time]) - .assert() - .success() - .code(0); - } - } - - for arg in args { - for time_string in time_strings { - cargo_bin_cmd!("find") - .args(["test_data/simple", arg, time_string]) - .assert() - .failure() - .code(1) - .stderr(predicate::str::contains( - "find: Expected a decimal integer (with optional + or - prefix) argument to", - )) - .stdout(predicate::str::is_empty()); - } - } -} - -#[test] -#[cfg(unix)] -#[serial(working_dir)] -fn find_fs() { - use findutils::find::matchers::fs::get_file_system_type; - use std::cell::RefCell; - use std::path::Path; - - let path = Path::new("./test_data/simple/subdir"); - let empty_cache = RefCell::new(None); - let target_fs_type = get_file_system_type(path, &empty_cache).unwrap(); - - // match fs type - cargo_bin_cmd!("find") - .args(["./test_data/simple/subdir", "-fstype", &target_fs_type]) - .assert() - .success() - .stdout(predicate::str::contains("./test_data/simple/subdir")) - .stderr(predicate::str::is_empty()); - - // not match fs type - cargo_bin_cmd!("find") - .args([ - "./test_data/simple/subdir", - "-fstype", - format!("{} foo", target_fs_type).as_str(), - ]) - .assert() - .success() - .stdout(predicate::str::is_empty()) - .stderr(predicate::str::is_empty()); - - // not contain fstype text. - cargo_bin_cmd!("find") - .args(["./test_data/simple/subdir", "-fstype"]) - .assert() - .failure() - .stdout(predicate::str::is_empty()); - - // void fstype - cargo_bin_cmd!("find") - .args(["./test_data/simple/subdir", "-fstype", " "]) - .assert() - .success() - .stdout(predicate::str::is_empty()) - .stderr(predicate::str::is_empty()); - - let path = Path::new("./test_data/links"); - let empty_cache = RefCell::new(None); - let target_fs_type = get_file_system_type(path, &empty_cache).unwrap(); - - // working with broken links - cargo_bin_cmd!("find") - .args(["./test_data/links", "-fstype", &target_fs_type]) - .assert() - .success() - .stdout(predicate::str::contains("./test_data/links/link-missing")) - .stderr(predicate::str::is_empty()); -} - -#[test] -#[serial(working_dir)] -fn find_samefile() { - use std::fs; - - // remove file if hard link file exist. - // But you can't delete a file that doesn't exist, - // so ignore the error returned here. - let _ = fs::remove_file("test_data/links/hard_link"); - fs::hard_link("test_data/links/abbbc", "test_data/links/hard_link").unwrap(); - - cargo_bin_cmd!("find") - .args([ - "./test_data/links/abbbc", - "-samefile", - "./test_data/links/hard_link", - ]) - .assert() - .success() - .stdout(predicate::str::contains("./test_data/links/abbbc")) - .stderr(predicate::str::is_empty()); - - // test . path - cargo_bin_cmd!("find") - .args([".", "-samefile", "."]) - .assert() - .success() - .stdout(predicate::str::contains(".")) - .stderr(predicate::str::is_empty()); - - cargo_bin_cmd!("find") - .args([".", "-samefile", "./test_data/links/abbbc"]) - .assert() - .success() - .stdout(predicate::str::contains(fix_up_slashes( - "./test_data/links/abbbc", - ))) - .stderr(predicate::str::is_empty()); - - // test not exist file - cargo_bin_cmd!("find") - .args([".", "-samefile", "./test_data/links/not-exist-file"]) - .assert() - .failure() - .stdout(predicate::str::is_empty()) - .stderr(predicate::str::contains("not-exist-file")); - - fs::remove_file("test_data/links/hard_link").unwrap(); -} - -#[test] -#[serial(working_dir)] -fn find_noleaf() { - cargo_bin_cmd!("find") - .args(["test_data/simple/subdir", "-noleaf"]) - .assert() - .success() - .stdout(predicate::str::contains("test_data/simple/subdir")) - .stderr(predicate::str::is_empty()); -} - -#[test] -#[serial(working_dir)] -fn find_daystart() { - cargo_bin_cmd!("find") - .args(["./test_data/simple/subdir", "-daystart", "-mtime", "0"]) - .assert() - .success() - .stderr(predicate::str::is_empty()); - - // twice -daystart should be matched - cargo_bin_cmd!("find") - .args([ - "./test_data/simple/subdir", - "-daystart", - "-daystart", - "-mtime", - "1", - ]) - .assert() - .success() - .stderr(predicate::str::is_empty()); -} - -#[test] -#[serial(working_dir)] -fn find_fprinter() { - let printer = ["fprint", "fprint0"]; - - for p in &printer { - let _ = fs::remove_file(format!("test_data/find_{p}")); - - cargo_bin_cmd!("find") - .args([ - "test_data/simple", - format!("-{p}").as_str(), - format!("test_data/find_{p}").as_str(), - ]) - .assert() - .success() - .stdout(predicate::str::is_empty()) - .stderr(predicate::str::is_empty()); - - // read test_data/find_fprint - let mut f = File::open(format!("test_data/find_{p}")).unwrap(); - let mut contents = String::new(); - f.read_to_string(&mut contents).unwrap(); - assert!(contents.contains("test_data/simple")); - - let _ = fs::remove_file(format!("test_data/find_{p}")); - } -} - -#[test] -#[serial(working_dir)] -fn find_follow() { - cargo_bin_cmd!("find") - .args(["test_data/links/link-f", "-follow"]) - .assert() - .success() - .stdout(predicate::str::contains("test_data/links/link-f")) - .stderr(predicate::str::is_empty()); -} - -#[test] -#[serial(working_dir)] -fn find_fprintf() { - let _ = fs::remove_file("test_data/find_fprintf"); - - cargo_bin_cmd!("find") - .args([ - "test_data/simple", - "-fprintf", - "test_data/find_fprintf", - "%h %H %p %P", - ]) - .assert() - .success() - .stdout(predicate::str::is_empty()) - .stderr(predicate::str::is_empty()); - - // read test_data/find_fprintf - let mut f = File::open("test_data/find_fprintf").unwrap(); - let mut contents = String::new(); - f.read_to_string(&mut contents).unwrap(); - assert!(contents.contains("test_data/simple")); - - let _ = fs::remove_file("test_data/find_fprintf"); -} - -#[test] -#[serial(working_dir)] -fn find_ls() { - cargo_bin_cmd!("find") - .args(["./test_data/simple/subdir", "-ls"]) - .assert() - .success() - .stderr(predicate::str::is_empty()); -} - -#[test] -#[cfg(unix)] -fn find_slashes() { - cargo_bin_cmd!("find") - .args(["///", "-maxdepth", "0", "-name", "/"]) - .assert() - .success() - .stderr(predicate::str::is_empty()); -} diff --git a/tests/test_find.rs b/tests/test_find.rs new file mode 100644 index 00000000..97d69e92 --- /dev/null +++ b/tests/test_find.rs @@ -0,0 +1,1003 @@ +// Copyright 2021 Chad Williamson +// +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file or at https://opensource.org/licenses/MIT. + +// Integration tests for the find command using the uutests framework. + +use regex::Regex; +use std::fs::{self, File}; +use std::io::ErrorKind; +use std::io::{Read, Write}; +use std::path::Path; +use tempfile::Builder; +use uutests::util::TestScenario; + +#[cfg(unix)] +use std::os::unix::fs::symlink; + +#[cfg(windows)] +use std::os::windows::fs::{symlink_dir, symlink_file}; + +use common::test_helpers::fix_up_slashes; + +mod common; + +/// Returns a UCommand for `find` with the working directory set to the +/// repository root, so that tests using relative `test_data/` paths work. +fn ucmd() -> uutests::util::UCommand { + let ts = TestScenario::new("find"); + let mut cmd = ts.cmd(env!("CARGO_BIN_EXE_find")); + cmd.current_dir(env!("CARGO_MANIFEST_DIR")); + cmd +} + +// Variants of fix_up_slashes that properly escape the forward slashes for +// use in a regex. +#[cfg(windows)] +fn fix_up_regex_slashes(re: &str) -> String { + re.replace("/", "\\\\") +} + +#[cfg(not(windows))] +fn fix_up_regex_slashes(re: &str) -> String { + re.to_owned() +} + +#[test] +fn no_args() { + ucmd().succeeds().no_stderr().stdout_contains("test_data"); +} + +#[test] +fn two_matchers_both_match() { + ucmd() + .args(&["-type", "d", "-name", "test_data"]) + .succeeds() + .no_stderr() + .stdout_contains("test_data"); +} + +#[test] +fn two_matchers_one_matches() { + ucmd() + .args(&["-type", "f", "-name", "test_data"]) + .succeeds() + .no_output(); +} + +#[test] +fn multiple_matcher_success() { + ucmd() + .args(&["-type", "f,d,l", "-name", "abbbc"]) + .succeeds() + .no_stderr() + .stdout_contains("abbbc"); + + ucmd() + .args(&["-xtype", "f,d,l", "-name", "abbbc"]) + .succeeds() + .no_stderr() + .stdout_contains("abbbc"); +} + +#[test] +fn multiple_matcher_failure() { + ucmd() + .args(&["-type", "fd", "-name", "abbb"]) + .fails() + .stderr_contains("Must separate multiple arguments") + .no_stdout(); + + ucmd() + .args(&["-type", "f,", "-name", "abbb"]) + .fails() + .stderr_contains("list is ending on: ','") + .no_stdout(); + + ucmd() + .args(&["-type", "f,f", "-name", "abbb"]) + .fails() + .stderr_contains("Duplicate file type") + .no_stdout(); + + ucmd() + .args(&["-type", "", "-name", "abbb"]) + .fails() + .stderr_contains("should contain at least one letter") + .no_stdout(); + + ucmd() + .args(&["-type", "x,y", "-name", "abbb"]) + .fails() + .stderr_contains("Unrecognised type argument") + .no_stdout(); + + // x-type tests below + ucmd() + .args(&["-xtype", "fd", "-name", "abbb"]) + .fails() + .stderr_contains("Must separate multiple arguments") + .no_stdout(); + + ucmd() + .args(&["-xtype", "f,", "-name", "abbb"]) + .fails() + .stderr_contains("list is ending on: ','") + .no_stdout(); + + ucmd() + .args(&["-xtype", "f,f", "-name", "abbb"]) + .fails() + .stderr_contains("Duplicate file type") + .no_stdout(); + + ucmd() + .args(&["-xtype", "", "-name", "abbb"]) + .fails() + .stderr_contains("should contain at least one letter") + .no_stdout(); + + ucmd() + .args(&["-xtype", "x,y", "-name", "abbb"]) + .fails() + .stderr_contains("Unrecognised type argument") + .no_stdout(); +} + +#[test] +fn files0_empty_file() { + ucmd() + .args(&["-files0-from", "./test_data/simple/abbbc"]) + .succeeds() + .no_output(); +} + +#[test] +fn files0_file_basic_success() { + ucmd() + .args(&["-files0-from", "./test_data/simple/abbbc"]) + .succeeds() + .no_output(); + + #[cfg(unix)] + { + let temp_dir = Builder::new().prefix("find_cmd_").tempdir().unwrap(); + let test_file = temp_dir.path().join("test_files0"); + let mut file = File::create(&test_file).expect("created test file"); + file.write_all(b"./test_data/\0./test_data/simple/\0") + .expect("file write error"); + + ucmd() + .args(&["-files0-from", &test_file.display().to_string()]) + .succeeds() + .no_stderr() + .stdout_contains("/test_data/"); + } +} + +#[test] +fn files0_empty_pipe() { + ucmd() + .args(&["-files0-from", "-"]) + .pipe_in(b"" as &[u8]) + .succeeds() + .no_output(); +} + +#[test] +fn files0_pipe_basic() { + ucmd() + .pipe_in(b"./test_data/simple\0./test_data/links" as &[u8]) + .args(&["-files0-from", "-"]) + .succeeds() + .no_stderr() + .stdout_contains("./test_data/"); +} + +#[test] +fn files0_pipe_double_nul() { + ucmd() + .pipe_in(b"./test_data/simple\0\0./test_data/links" as &[u8]) + .args(&["-files0-from", "-"]) + .succeeds() + .stderr_contains("invalid zero-length file name") + .stdout_contains("./test_data/"); +} + +#[test] +fn files0_no_file() { + #[cfg(unix)] + { + ucmd() + .args(&["-files0-from", "xyz.nonexistentFile"]) + .fails() + .stderr_contains("No such file or directory") + .no_stdout(); + } + #[cfg(windows)] + { + ucmd() + .args(&["-files0-from", "xyz.nonexistantFile"]) + .fails() + .stderr_contains("The system cannot find the file specified.") + .no_stdout(); + } +} + +#[test] +fn files0_basic() { + ucmd() + .arg("-files0-from") + .fails() + .stderr_contains("missing argument to -files0-from") + .no_stdout(); +} + +#[test] +fn matcher_with_side_effects_at_end() { + let temp_dir = Builder::new().prefix("find_cmd_").tempdir().unwrap(); + + let temp_dir_path = temp_dir.path().to_str().unwrap(); + let test_file = temp_dir.path().join("test"); + File::create(&test_file).expect("created test file"); + + ucmd() + .args(&[temp_dir_path, "-name", "test", "-delete"]) + .succeeds() + .no_output(); + + assert!(!test_file.exists(), "test file should be deleted"); + assert!(temp_dir.path().exists(), "temp dir should NOT be deleted"); +} + +#[test] +fn matcher_with_side_effects_in_front() { + let temp_dir = Builder::new().prefix("find_cmd_").tempdir().unwrap(); + + let temp_dir_path = temp_dir.path().to_str().unwrap(); + let test_file = temp_dir.path().join("test"); + File::create(&test_file).expect("created test file"); + + ucmd() + .args(&[temp_dir_path, "-delete", "-name", "test"]) + .succeeds() + .no_output(); + + assert!(!test_file.exists(), "test file should be deleted"); + assert!(!temp_dir.path().exists(), "temp dir should also be deleted"); +} + +// This could be covered by a unit test in principle... in practice, changing +// the working dir can't be done safely in unit tests unless `--test-threads=1` +// or `serial` goes everywhere, and it doesn't seem possible to get an +// appropriate `walkdir::DirEntry` for "." without actually changing dirs +// (or risking deletion of the repo itself). +#[test] +fn delete_on_dot_dir() { + let temp_dir = Builder::new().prefix("example").tempdir().unwrap(); + + // "." should be matched (confirmed by the print), but not deleted. + ucmd() + .current_dir(temp_dir.path()) + .args(&[".", "-delete", "-print"]) + .succeeds() + .no_stderr() + .stdout_only(".\n"); + + assert!(temp_dir.path().exists(), "temp dir should still exist"); +} + +#[test] +fn regex_types() { + let temp_dir = Builder::new().prefix("find_cmd_").tempdir().unwrap(); + + let temp_dir_path = temp_dir.path().to_str().unwrap(); + let test_file = temp_dir.path().join("teeest"); + File::create(test_file).expect("created test file"); + + ucmd() + .arg(temp_dir_path) + .arg("-regex") + .arg(fix_up_regex_slashes(".*/tE+st")) + .succeeds() + .no_output(); + + ucmd() + .arg(temp_dir_path) + .arg("-iregex") + .arg(fix_up_regex_slashes(".*/tE+st")) + .succeeds() + .no_stderr() + .stdout_contains("teeest"); + + ucmd() + .arg(temp_dir_path) + .args(&["-regextype", "posix-basic", "-regex"]) + .arg(fix_up_regex_slashes(r".*/te\{1,3\}st")) + .succeeds() + .no_stderr() + .stdout_contains("teeest"); + + ucmd() + .arg(temp_dir_path) + .args(&["-regextype", "posix-extended", "-regex"]) + .arg(fix_up_regex_slashes(".*/te{1,3}st")) + .succeeds() + .no_stderr() + .stdout_contains("teeest"); + + ucmd() + .arg(temp_dir_path) + .args(&["-regextype", "ed", "-regex"]) + .arg(fix_up_regex_slashes(r".*/te\{1,3\}st")) + .succeeds() + .no_stderr() + .stdout_contains("teeest"); + + ucmd() + .arg(temp_dir_path) + .args(&["-regextype", "sed", "-regex"]) + .arg(fix_up_regex_slashes(r".*/te\{1,3\}st")) + .succeeds() + .no_stderr() + .stdout_contains("teeest"); +} + +#[test] +fn empty_files() { + let temp_dir = Builder::new().prefix("find_cmd_").tempdir().unwrap(); + let temp_dir_path = temp_dir.path().to_str().unwrap(); + + ucmd() + .args(&[temp_dir_path, "-empty"]) + .succeeds() + .no_stderr() + .stdout_only(fix_up_slashes(&format!("{temp_dir_path}\n"))); + + let test_file_path = temp_dir.path().join("test"); + let mut test_file = File::create(&test_file_path).unwrap(); + + ucmd() + .args(&[temp_dir_path, "-empty"]) + .succeeds() + .no_stderr() + .stdout_only(fix_up_slashes(&format!( + "{}\n", + test_file_path.to_string_lossy() + ))); + + let subdir_path = temp_dir.path().join("subdir"); + std::fs::create_dir(&subdir_path).unwrap(); + + ucmd() + .args(&[temp_dir_path, "-empty", "-sorted"]) + .succeeds() + .no_stderr() + .stdout_only(fix_up_slashes(&format!( + "{}\n{}\n", + subdir_path.to_string_lossy(), + test_file_path.to_string_lossy() + ))); + + write!(test_file, "x").unwrap(); + test_file.sync_all().unwrap(); + + ucmd() + .args(&[temp_dir_path, "-empty", "-sorted"]) + .succeeds() + .no_stderr() + .stdout_only(fix_up_slashes(&format!( + "{}\n", + subdir_path.to_string_lossy(), + ))); +} + +#[test] +fn find_printf() { + #[cfg(unix)] + { + if let Err(e) = symlink("abbbc", "test_data/links/link-f") { + assert!( + e.kind() == ErrorKind::AlreadyExists, + "Failed to create sym link: {e:?}" + ); + } + if let Err(e) = symlink("subdir", "test_data/links/link-d") { + assert!( + e.kind() == ErrorKind::AlreadyExists, + "Failed to create sym link: {e:?}" + ); + } + if let Err(e) = symlink("missing", "test_data/links/link-missing") { + assert!( + e.kind() == ErrorKind::AlreadyExists, + "Failed to create sym link: {e:?}" + ); + } + if let Err(e) = symlink("abbbc/x", "test_data/links/link-notdir") { + assert!( + e.kind() == ErrorKind::AlreadyExists, + "Failed to create sym link: {e:?}" + ); + } + if let Err(e) = symlink("link-loop", "test_data/links/link-loop") { + assert!( + e.kind() == ErrorKind::AlreadyExists, + "Failed to create sym link: {e:?}" + ); + } + } + #[cfg(windows)] + { + if let Err(e) = symlink_file("abbbc", "test_data/links/link-f") { + assert!( + e.kind() == ErrorKind::AlreadyExists, + "Failed to create sym link: {:?}", + e + ); + } + if let Err(e) = symlink_dir("subdir", "test_data/links/link-d") { + assert!( + e.kind() == ErrorKind::AlreadyExists, + "Failed to create sym link: {:?}", + e + ); + } + if let Err(e) = symlink_file("missing", "test_data/links/link-missing") { + assert!( + e.kind() == ErrorKind::AlreadyExists, + "Failed to create sym link: {:?}", + e + ); + } + if let Err(e) = symlink_file("abbbc/x", "test_data/links/link-notdir") { + assert!( + e.kind() == ErrorKind::AlreadyExists, + "Failed to create sym link: {:?}", + e + ); + } + } + + ucmd() + .arg(fix_up_slashes("./test_data/simple")) + .args(&["-sorted", "-printf", "%f %d %h %H %p %P %y\n"]) + .succeeds() + .no_stderr() + .stdout_only(fix_up_slashes( + "simple 0 ./test_data ./test_data/simple \ + ./test_data/simple d\n\ + abbbc 1 ./test_data/simple ./test_data/simple \ + ./test_data/simple/abbbc abbbc f\n\ + subdir 1 ./test_data/simple ./test_data/simple \ + ./test_data/simple/subdir subdir d\n\ + ABBBC 2 ./test_data/simple/subdir ./test_data/simple \ + ./test_data/simple/subdir/ABBBC subdir/ABBBC f\n", + )); + + fs::create_dir_all("a").expect("Failed to create directory 'a'"); + let result = ucmd().args(&["a", "-printf", "%A+"]).succeeds(); + + let output_str = result.stdout_str(); + let re = Regex::new(r"^\d{4}-\d{2}-\d{2}\+\d{2}:\d{2}:\d{2}\.\d{9}0$") + .expect("Failed to compile regex"); + assert!( + re.is_match(output_str.trim()), + "Output did not match expected timestamp format" + ); + + ucmd() + .arg(fix_up_slashes("./test_data/links")) + .args(&["-sorted", "-type", "l", "-printf", "%f %l %y %Y\n"]) + .succeeds() + .no_stderr() + .stdout_only( + [ + "link-d subdir l d\n", + "link-f abbbc l f\n", + #[cfg(unix)] + "link-loop link-loop l L\n", + "link-missing missing l N\n", + // We can't detect ENOTDIR on non-unix platforms yet. + #[cfg(not(unix))] + "link-notdir abbbc/x l ?\n", + #[cfg(unix)] + "link-notdir abbbc/x l N\n", + ] + .join(""), + ); +} + +#[cfg(unix)] +#[test] +fn find_perm() { + ucmd().args(&["-perm", "+rwx"]).succeeds(); + ucmd().args(&["-perm", "u+rwX"]).succeeds(); + ucmd().args(&["-perm", "u=g"]).succeeds(); +} + +#[cfg(unix)] +#[test] +fn find_inum() { + use std::os::unix::fs::MetadataExt; + + let inum = fs::metadata("test_data/simple/abbbc") + .expect("metadata for abbbc") + .ino() + .to_string(); + + ucmd() + .args(&["test_data", "-inum", &inum]) + .succeeds() + .no_stderr() + .stdout_contains("abbbc"); +} + +#[cfg(not(unix))] +#[test] +fn find_inum() { + ucmd() + .args(&["test_data", "-inum", "1"]) + .fails() + .stderr_contains("not available on this platform") + .no_stdout(); +} + +#[cfg(unix)] +#[test] +fn find_links() { + ucmd() + .args(&["test_data", "-links", "1"]) + .succeeds() + .no_stderr() + .stdout_contains("abbbc"); +} + +#[cfg(not(unix))] +#[test] +fn find_links() { + ucmd() + .args(&["test_data", "-links", "1"]) + .fails() + .stderr_contains("not available on this platform") + .no_stdout(); +} + +#[test] +fn find_mount_xdev() { + // Make sure that -mount/-xdev doesn't prune unexpectedly. + // TODO: Test with a mount point in the search. + ucmd() + .args(&["test_data", "-mount"]) + .succeeds() + .no_stderr() + .stdout_contains("abbbc"); + + ucmd() + .args(&["test_data", "-xdev"]) + .succeeds() + .no_stderr() + .stdout_contains("abbbc"); +} + +#[test] +fn find_accessible() { + ucmd() + .args(&["test_data", "-readable"]) + .succeeds() + .no_stderr() + .stdout_contains("abbbc"); + + ucmd() + .args(&["test_data", "-writable"]) + .succeeds() + .no_stderr() + .stdout_contains("abbbc"); + + #[cfg(unix)] + ucmd() + .args(&["test_data", "-executable"]) + .succeeds() + .no_stderr() + .stdout_does_not_contain("abbbc"); +} + +#[test] +fn find_time() { + let args = ["1", "+1", "-1"]; + let exception_args = ["1%2", "1%2%3", "1a2", "1%2a", "abc", "-", "+", "%"]; + + let tests = [ + "-atime", + #[cfg(unix)] + "-ctime", + "-mtime", + ]; + tests.iter().for_each(|flag| { + args.iter().for_each(|arg| { + ucmd() + .args(&["./test_data/simple", flag, arg]) + .succeeds() + .no_stderr(); + }); + + exception_args.iter().for_each(|arg| { + ucmd().args(&[".", flag, arg]).fails().no_stdout(); + }); + }); +} + +#[test] +fn expression_empty_parentheses() { + ucmd() + .args(&["-true", "(", ")"]) + .fails() + .stderr_contains("empty parentheses are not allowed") + .no_stdout(); +} + +#[test] +#[cfg(unix)] +fn find_with_user_predicate() { + // Considering the different test environments, + // the test code can only use a specific default user to perform the test, + // such as the root user on Linux. + ucmd() + .args(&["test_data", "-user", "root"]) + .succeeds() + .no_stderr(); + + ucmd() + .args(&["test_data", "-user", ""]) + .fails() + .stderr_contains("empty") + .no_stdout(); + + ucmd() + .args(&["test_data", "-user", " "]) + .fails() + .stderr_contains("invalid user name or UID argument to -user") + .no_stdout(); +} + +#[test] +fn find_with_nouser_predicate() { + ucmd() + .args(&["test_data", "-nouser"]) + .succeeds() + .no_stdout() + .no_stderr(); +} + +#[test] +#[cfg(unix)] +fn find_with_uid_predicate() { + use std::os::unix::fs::MetadataExt; + + let uid = Path::new("./test_data") + .metadata() + .unwrap() + .uid() + .to_string(); + + ucmd() + .args(&["test_data", "-uid", &uid]) + .succeeds() + .no_stderr(); +} + +#[test] +fn find_with_group_predicate() { + // Considering the different test environments, + // the test code can only use a specific default user group for the test, + // such as the root user group on Linux. + #[cfg(target_os = "linux")] + ucmd() + .args(&["test_data", "-group", "root"]) + .succeeds() + .no_stderr(); + + #[cfg(target_os = "macos")] + ucmd() + .args(&["test_data", "-group", "staff"]) + .succeeds() + .no_stderr(); + + ucmd() + .args(&["test_data", "-group", ""]) + .fails() + .stderr_contains("empty") + .no_stdout(); + + ucmd() + .args(&["test_data", "-group", " "]) + .fails() + .stderr_contains("invalid group name or GID argument to -group:") + .no_stdout(); +} + +#[test] +fn find_with_nogroup_predicate() { + ucmd() + .args(&["test_data", "-nogroup"]) + .succeeds() + .no_stdout() + .no_stderr(); +} + +#[test] +#[cfg(unix)] +fn find_with_gid_predicate() { + use std::os::unix::fs::MetadataExt; + + let gid = Path::new("./test_data") + .metadata() + .unwrap() + .gid() + .to_string(); + + ucmd() + .args(&["test_data", "-gid", &gid]) + .succeeds() + .no_stderr(); +} + +#[test] +fn find_newer_xy() { + let options = [ + "a", + #[cfg(not(target_os = "linux"))] + "B", + #[cfg(unix)] + "c", + "m", + ]; + + for x in options { + for y in options { + let arg = &format!("-newer{x}{y}"); + ucmd() + .args(&[ + "./test_data/simple/subdir", + arg, + "./test_data/simple/subdir/ABBBC", + ]) + .succeeds() + .no_stderr(); + } + } + + let args = [ + "-newerat", + #[cfg(not(target_os = "linux"))] + "-newerBt", + #[cfg(unix)] + "-newerct", + "-newermt", + ]; + let times = ["jan 01, 2000", "jan 01, 2000 00:00:00"]; + + for arg in args { + for time in times { + ucmd() + .args(&["./test_data/simple/subdir", arg, time]) + .succeeds() + .no_stderr(); + } + } +} + +#[test] +fn find_age_range() { + let args = ["-amin", "-cmin", "-mmin"]; + let times = ["-60", "-120", "-240", "+60", "+120", "+240"]; + let time_strings = [ + "\"-60\"", "\"-120\"", "\"-240\"", "\"-60\"", "\"-120\"", "\"-240\"", + ]; + + for arg in args { + for time in times { + ucmd().args(&["test_data/simple", arg, time]).succeeds(); + } + } + + for arg in args { + for time_string in time_strings { + ucmd() + .args(&["test_data/simple", arg, time_string]) + .fails() + .stderr_contains( + "find: Expected a decimal integer (with optional + or - prefix) argument to", + ) + .no_stdout(); + } + } +} + +#[test] +#[cfg(unix)] +fn find_fs() { + use findutils::find::matchers::fs::get_file_system_type; + use std::cell::RefCell; + + let path = Path::new("./test_data/simple/subdir"); + let empty_cache = RefCell::new(None); + let target_fs_type = get_file_system_type(path, &empty_cache).unwrap(); + + // match fs type + ucmd() + .args(&["./test_data/simple/subdir", "-fstype", &target_fs_type]) + .succeeds() + .stdout_contains("./test_data/simple/subdir") + .no_stderr(); + + // not match fs type + ucmd() + .args(&[ + "./test_data/simple/subdir", + "-fstype", + format!("{} foo", target_fs_type).as_str(), + ]) + .succeeds() + .no_output(); + + // not contain fstype text. + ucmd() + .args(&["./test_data/simple/subdir", "-fstype"]) + .fails() + .no_stdout(); + + // void fstype + ucmd() + .args(&["./test_data/simple/subdir", "-fstype", " "]) + .succeeds() + .no_output(); + + let path = Path::new("./test_data/links"); + let empty_cache = RefCell::new(None); + let target_fs_type = get_file_system_type(path, &empty_cache).unwrap(); + + // working with broken links + ucmd() + .args(&["./test_data/links", "-fstype", &target_fs_type]) + .succeeds() + .stdout_contains("./test_data/links/link-missing") + .no_stderr(); +} + +#[test] +fn find_samefile() { + let temp_dir = Builder::new().prefix("find_samefile_").tempdir().unwrap(); + let temp_dir_path = temp_dir.path().to_str().unwrap(); + let test_file = temp_dir.path().join("abbbc"); + let hard_link = temp_dir.path().join("hard_link"); + fs::copy("test_data/links/abbbc", &test_file).unwrap(); + fs::hard_link(&test_file, &hard_link).unwrap(); + + let test_file_str = test_file.to_str().unwrap(); + let hard_link_str = hard_link.to_str().unwrap(); + let not_exist = temp_dir.path().join("not-exist-file"); + let not_exist_str = not_exist.to_str().unwrap(); + + ucmd() + .args(&[test_file_str, "-samefile", hard_link_str]) + .succeeds() + .stdout_contains(test_file_str) + .no_stderr(); + + // test . path + ucmd() + .args(&[temp_dir_path, "-samefile", temp_dir_path]) + .succeeds() + .stdout_contains(temp_dir_path) + .no_stderr(); + + ucmd() + .args(&[temp_dir_path, "-samefile", test_file_str]) + .succeeds() + .stdout_contains(fix_up_slashes(test_file_str)) + .no_stderr(); + + // test not exist file + ucmd() + .args(&[temp_dir_path, "-samefile", not_exist_str]) + .fails() + .no_stdout() + .stderr_contains("not-exist-file"); +} + +#[test] +fn find_noleaf() { + ucmd() + .args(&["test_data/simple/subdir", "-noleaf"]) + .succeeds() + .stdout_contains("test_data/simple/subdir") + .no_stderr(); +} + +#[test] +fn find_daystart() { + ucmd() + .args(&["./test_data/simple/subdir", "-daystart", "-mtime", "0"]) + .succeeds() + .no_stderr(); + + // twice -daystart should be matched + ucmd() + .args(&[ + "./test_data/simple/subdir", + "-daystart", + "-daystart", + "-mtime", + "1", + ]) + .succeeds() + .no_stderr(); +} + +#[test] +fn find_fprinter() { + let temp_dir = Builder::new().prefix("find_fprinter_").tempdir().unwrap(); + let printer = ["fprint", "fprint0"]; + + for p in &printer { + let out_file = temp_dir.path().join(format!("find_{p}")); + let out_file_str = out_file.to_str().unwrap(); + + ucmd() + .args(&["test_data/simple", format!("-{p}").as_str(), out_file_str]) + .succeeds() + .no_output(); + + // read test_data/find_fprint + let mut f = File::open(&out_file).unwrap(); + let mut contents = String::new(); + f.read_to_string(&mut contents).unwrap(); + assert!(contents.contains("test_data/simple")); + } +} + +#[test] +fn find_follow() { + ucmd() + .args(&["test_data/links/link-f", "-follow"]) + .succeeds() + .stdout_contains("test_data/links/link-f") + .no_stderr(); +} + +#[test] +fn find_fprintf() { + let temp_dir = Builder::new().prefix("find_fprintf_").tempdir().unwrap(); + let out_file = temp_dir.path().join("find_fprintf"); + let out_file_str = out_file.to_str().unwrap(); + + ucmd() + .args(&["test_data/simple", "-fprintf", out_file_str, "%h %H %p %P"]) + .succeeds() + .no_output(); + + // read test_data/find_fprintf + let mut f = File::open(&out_file).unwrap(); + let mut contents = String::new(); + f.read_to_string(&mut contents).unwrap(); + assert!(contents.contains("test_data/simple")); +} + +#[test] +fn find_ls() { + ucmd() + .args(&["./test_data/simple/subdir", "-ls"]) + .succeeds() + .no_stderr(); +} + +#[test] +#[cfg(unix)] +fn find_slashes() { + ucmd() + .args(&["///", "-maxdepth", "0", "-name", "/"]) + .succeeds() + .no_stderr(); +} diff --git a/tests/test_xargs.rs b/tests/test_xargs.rs new file mode 100644 index 00000000..ac9f63f4 --- /dev/null +++ b/tests/test_xargs.rs @@ -0,0 +1,497 @@ +// Copyright 2021 Collabora, Ltd. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +// Integration tests for the xargs command using the uutests framework. +// These are integration tests (rather than unit tests) so that the +// testing-commandline binary is guaranteed to be built first. +use uutests::util::TestScenario; + +use common::test_helpers::path_to_testing_commandline; + +mod common; + +fn ucmd() -> uutests::util::UCommand { + TestScenario::new("xargs").cmd(env!("CARGO_BIN_EXE_xargs")) +} + +#[test] +fn xargs_basics() { + ucmd() + .pipe_in("abc\ndef g\\hi 'i j \"k'") + .succeeds() + .stdout_only("abc def ghi i j \"k\n"); +} + +#[test] +fn xargs_null() { + ucmd() + .args(&["-0n1"]) + .pipe_in("ab c\0d\tef\0") + .succeeds() + .stdout_only("ab c\nd\tef\n"); +} + +#[test] +fn xargs_delim() { + ucmd() + .args(&["-d1"]) + .pipe_in("ab1cd1ef") + .succeeds() + .stdout_only("ab cd ef\n"); + + ucmd() + .args(&["-d\\t", "-n1"]) + .pipe_in("a\nb\td e\tfg") + .succeeds() + .stdout_only("a\nb\nd e\nfg\n"); + + ucmd() + .args(&["-dabc"]) + .fails_with_code(1) + .stderr_contains("invalid") + .no_stdout(); +} + +#[test] +fn xargs_null_conflict() { + ucmd() + .args(&["-d\t", "-0n1"]) + .pipe_in("ab c\0d\tef\0") + .succeeds() + .stdout_only("ab c\nd\tef\n"); +} + +#[test] +fn xargs_if_empty() { + // Should echo at least once still. + ucmd().succeeds().no_stderr().stdout_only("\n"); + + // Should never echo. + ucmd().args(&["--no-run-if-empty"]).succeeds().no_output(); +} + +#[test] +fn xargs_max_args() { + ucmd() + .args(&["-n2"]) + .pipe_in("ab cd ef\ngh i") + .succeeds() + .stdout_only("ab cd\nef gh\ni\n"); +} + +#[test] +fn xargs_max_lines() { + for arg in ["-L2", "--max-lines=2", "-l2"] { + ucmd() + .arg(arg) + .pipe_in("ab cd\nef\ngh i\n\njkl\n") + .succeeds() + .stdout_only("ab cd ef\ngh i jkl\n"); + } + ucmd() + .arg("-l") + .pipe_in("ab cd\nef\ngh i\n\njkl\n") + .succeeds() + .stdout_only("ab cd\nef\ngh i\njkl\n"); +} + +#[test] +fn xargs_max_args_lines_conflict() { + // -n2 is last, so it should be given priority. + ucmd() + .args(&["-L2", "-n2"]) + .pipe_in("ab cd ef\ngh i") + .succeeds() + .stderr_contains("WARNING") + .stdout_is("ab cd\nef gh\ni\n"); + + // -n2 is last, so it should be given priority. + ucmd() + .args(&["-I=_", "-n2", "echo", "_"]) + .pipe_in("ab cd ef\ngh i\njkl") + .succeeds() + .stderr_contains("WARNING") + .stdout_is("_ ab cd\n_ ef gh\n_ i jkl\n"); + + // -L2 is last, so it should be given priority. + ucmd() + .args(&["-n2", "-L2"]) + .pipe_in("ab cd\nef\ngh i\n\njkl\n") + .succeeds() + .stderr_contains("WARNING") + .stdout_is("ab cd ef\ngh i jkl\n"); + + // -L2 is last, so it should be given priority. + ucmd() + .args(&["-I=_", "-L2", "echo", "_"]) + .pipe_in("ab cd\nef\ngh i\n\njkl\n") + .succeeds() + .stderr_contains("WARNING") + .stdout_is("_ ab cd ef\n_ gh i jkl\n"); + + for redundant_arg in ["-L2", "-n2"] { + // -I={} is last, so it should be given priority. + ucmd() + .args(&[redundant_arg, "-I={}", "echo", "{} bar"]) + .pipe_in("ab cd ef\ngh i\njkl") + .succeeds() + .stderr_contains("WARNING") + .stdout_is("ab cd ef bar\ngh i bar\njkl bar\n"); + } +} + +#[test] +fn xargs_max_chars() { + for arg in ["-s11", "--max-chars=11"] { + ucmd() + .arg(arg) + .pipe_in("ab cd efg") + .succeeds() + .stdout_only("ab cd\nefg\n"); + } + + // Behavior should be the same with -x, which only takes effect with -L or + // -n. + ucmd() + .args(&["-xs11"]) + .pipe_in("ab cd efg") + .succeeds() + .stdout_only("ab cd\nefg\n"); + + ucmd() + .args(&["-s10"]) + .pipe_in("abcdefghijkl ab") + .fails_with_code(1) + .stderr_contains("Error:") + .no_stdout(); +} + +#[test] +fn xargs_exit_on_large() { + ucmd() + .args(&["-xs11", "-n2"]) + .pipe_in("ab cd efg h i") + .succeeds() + .stdout_only("ab cd\nefg h\ni\n"); + + ucmd() + .args(&["-xs11", "-n2"]) + .pipe_in("abcdefg hijklmn") + .fails_with_code(1) + .stderr_contains("Error:") + .no_stdout(); +} + +#[test] +fn xargs_exec() { + let result = ucmd() + .args(&[ + "-n2", + &path_to_testing_commandline(), + "-", + "--print_stdin", + "--no_print_cwd", + ]) + .pipe_in("a b c\nd") + .succeeds(); + result.no_stderr(); + assert_eq!( + result.stdout_str(), + "stdin=\nargs=\n--print_stdin\n--no_print_cwd\na\nb\n\ + stdin=\nargs=\n--print_stdin\n--no_print_cwd\nc\nd\n", + ); +} + +#[test] +fn xargs_exec_stdin_open() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + std::fs::write(temp_file.path(), b"a b c").unwrap(); + + let result = ucmd() + .args(&[ + "-a", + &temp_file.path().to_string_lossy(), + &path_to_testing_commandline(), + "-", + "--print_stdin", + "--no_print_cwd", + ]) + .pipe_in("test") + .succeeds(); + result.no_stderr(); + assert_eq!( + result.stdout_str(), + "stdin=test\nargs=\n--print_stdin\n--no_print_cwd\na\nb\nc\n", + ); +} + +#[test] +fn xargs_exec_failure() { + let result = ucmd() + .args(&[ + "-n1", + &path_to_testing_commandline(), + "-", + "--no_print_cwd", + "--exit_with_failure", + ]) + .pipe_in("a b") + .run(); + result.code_is(123); + result.no_stderr(); + assert_eq!( + result.stdout_str(), + "args=\n--no_print_cwd\n--exit_with_failure\na\n\ + args=\n--no_print_cwd\n--exit_with_failure\nb\n", + ); +} + +#[test] +fn xargs_exec_urgent_failure() { + let result = ucmd() + .args(&[ + "-n1", + &path_to_testing_commandline(), + "-", + "--no_print_cwd", + "--exit_with_urgent_failure", + ]) + .pipe_in("a b") + .run(); + result.code_is(124); + assert!( + !result.stderr_str().is_empty(), + "stderr should not be empty" + ); + assert_eq!( + result.stdout_str(), + "args=\n--no_print_cwd\n--exit_with_urgent_failure\na\n" + ); +} + +#[test] +#[cfg(unix)] +fn xargs_exec_with_signal() { + let result = ucmd() + .args(&[ + "-n1", + &path_to_testing_commandline(), + "-", + "--no_print_cwd", + "--exit_with_signal", + ]) + .pipe_in("a b") + .run(); + result.code_is(125); + assert!( + !result.stderr_str().is_empty(), + "stderr should not be empty" + ); + assert_eq!( + result.stdout_str(), + "args=\n--no_print_cwd\n--exit_with_signal\na\n" + ); +} + +#[test] +fn xargs_exec_not_found() { + ucmd() + .args(&["this-file-does-not-exist"]) + .fails_with_code(127) + .stderr_contains("Error:") + .no_stdout(); +} + +#[test] +fn xargs_exec_verbose() { + ucmd() + .args(&[ + "-n2", + "--verbose", + &path_to_testing_commandline(), + "-", + "--print_stdin", + "--no_print_cwd", + ]) + .pipe_in("a b c\nd") + .succeeds() + .stderr_contains("testing-commandline") + .stdout_is( + "stdin=\nargs=\n--print_stdin\n--no_print_cwd\na\nb\n\ + stdin=\nargs=\n--print_stdin\n--no_print_cwd\nc\nd\n", + ); +} + +#[test] +fn xargs_unterminated_quote() { + ucmd() + .args(&[ + "-n2", + &path_to_testing_commandline(), + "-", + "--print_stdin", + "--no_print_cwd", + ]) + .pipe_in("a \"b c\nd") + .fails_with_code(1) + .stderr_contains("Error: Unterminated quote:") + .no_stdout(); +} + +#[test] +fn xargs_zero_lines() { + ucmd() + .args(&[ + "-L0", + &path_to_testing_commandline(), + "-", + "--print_stdin", + "--no_print_cwd", + ]) + // Empty stdin: -L0 is rejected before reading input, so writing + // actual content races with xargs exiting and causes a broken pipe. + .pipe_in("") + .fails_with_code(1) + .stderr_contains("Value must be > 0, not: 0") + .no_stdout(); +} + +#[test] +fn xargs_replace() { + ucmd() + .args(&["-i={}", "echo", "{} bar"]) + .pipe_in("foo") + .succeeds() + .stdout_contains("foo bar"); + + ucmd() + .args(&["-i=_", "echo", "_ bar"]) + .pipe_in("foo") + .succeeds() + .stdout_contains("foo bar"); + + ucmd() + .args(&["--replace=_", "echo", "_ _ bar"]) + .pipe_in("foo") + .succeeds() + .stdout_contains("foo foo bar"); + + ucmd() + .args(&["-i=_", "echo", "_ _ bar"]) + .pipe_in("foo") + .succeeds() + .stdout_contains("foo foo bar"); + + ucmd() + .args(&["-i", "echo", "{} {} bar"]) + .pipe_in("foo") + .succeeds() + .stdout_contains("foo foo bar"); + + ucmd() + .args(&["-I={}", "echo", "{} bar {}"]) + .pipe_in("foo") + .succeeds() + .stdout_contains("foo bar foo"); + + // Combine the two options to see which one wins + ucmd() + .args(&["-I=_", "-i", "echo", "{} bar {}"]) + .pipe_in("foo") + .succeeds() + .stdout_contains("foo bar foo"); + + // other order + ucmd() + .args(&["-i", "-I=_", "echo", "{} bar {}"]) + .pipe_in("foo") + .succeeds() + .stdout_contains("{} bar {}"); + + ucmd() + .args(&["-i", "-I", "_", "echo", "{} bar _"]) + .pipe_in("foo") + .succeeds() + .stdout_contains("{} bar foo"); + + // Expected to fail + ucmd() + .args(&["-I", "echo", "_ _ bar"]) + .pipe_in("foo") + .fails() + .stderr_contains("Error: Command not found"); +} + +#[test] +fn xargs_replace_multiple_lines() { + ucmd() + .args(&["-I", "_", "echo", "[_]"]) + .pipe_in("ab c\nd ef\ng") + .succeeds() + .stdout_only("[ab c]\n[d ef]\n[g]\n"); + + ucmd() + .args(&["-I", "{}", "echo", "{} {} foo"]) + .pipe_in("bar\nbaz") + .succeeds() + .stdout_only("bar bar foo\nbaz baz foo\n"); + + ucmd() + .args(&["-I", "non-exist", "echo"]) + .pipe_in("abc\ndef\ng") + .succeeds() + .stdout_only("\n\n\n"); +} + +#[test] +fn xargs_help() { + for option_style in ["-h", "--help"] { + ucmd() + .args(&[option_style]) + .succeeds() + .no_stderr() + .stdout_contains("--help"); + } +} + +// Do not regress to: +// +// ❯ xargs --version +// Error: xargs 0.7.0 +// +// Same for help above. +#[test] +fn xargs_version() { + for option_style in ["-V", "--version"] { + let output = ucmd().args(&[option_style]).succeeds(); + let result = output.no_stderr(); + assert!( + result.stdout_str().starts_with("xargs "), + "expected stdout to start with 'xargs ', got: {:?}", + result.stdout_str() + ); + } +} + +#[test] +fn xargs_eof() { + for option_style in [vec!["-ecd"], vec!["-E", "cd"], vec!["--eof", "cd"]] { + ucmd() + .args(option_style.as_slice()) + .pipe_in("ab cd ef") + .succeeds() + .stdout_only("ab\n"); + } +} + +#[test] +fn xargs_eof_with_delimiter() { + ucmd() + .args(&["-0", "-Ecd"]) + .pipe_in("ab\0cd\0ef") + .succeeds() + .stdout_only("ab cd ef\n"); +} diff --git a/tests/xargs_tests.rs b/tests/xargs_tests.rs deleted file mode 100644 index b5fd4826..00000000 --- a/tests/xargs_tests.rs +++ /dev/null @@ -1,585 +0,0 @@ -// Copyright 2021 Collabora, Ltd. -// -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -/// ! This file contains integration tests for xargs, separate from the unit -/// ! tests so that testing-commandline can be built first. -use std::io::{Seek, SeekFrom, Write}; - -use assert_cmd::cargo::cargo_bin_cmd; -use predicates::prelude::*; - -use common::test_helpers::path_to_testing_commandline; -use pretty_assertions::assert_eq; - -mod common; - -#[test] -fn xargs_basics() { - cargo_bin_cmd!("xargs") - .write_stdin("abc\ndef g\\hi 'i j \"k'") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("abc def ghi i j \"k\n")); -} - -#[test] -fn xargs_null() { - cargo_bin_cmd!("xargs") - .args(["-0n1"]) - .write_stdin("ab c\0d\tef\0") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab c\nd\tef\n")); -} - -#[test] -fn xargs_delim() { - cargo_bin_cmd!("xargs") - .args(["-d1"]) - .write_stdin("ab1cd1ef") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab cd ef\n")); - - cargo_bin_cmd!("xargs") - .args(["-d\\t", "-n1"]) - .write_stdin("a\nb\td e\tfg") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("a\nb\nd e\nfg\n")); - - cargo_bin_cmd!("xargs") - .args(["-dabc"]) - .assert() - .failure() - .code(1) - .stderr(predicate::str::contains("invalid")) - .stdout(predicate::str::is_empty()); -} - -#[test] -fn xargs_null_conflict() { - cargo_bin_cmd!("xargs") - .args(["-d\t", "-0n1"]) - .write_stdin("ab c\0d\tef\0") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab c\nd\tef\n")); -} - -#[test] -fn xargs_if_empty() { - cargo_bin_cmd!("xargs") - .assert() - .success() - .stderr(predicate::str::is_empty()) - // Should echo at least once still. - .stdout(predicate::eq("\n")); - - cargo_bin_cmd!("xargs") - .args(["--no-run-if-empty"]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - // Should never echo. - .stdout(predicate::str::is_empty()); -} - -#[test] -fn xargs_max_args() { - cargo_bin_cmd!("xargs") - .args(["-n2"]) - .write_stdin("ab cd ef\ngh i") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab cd\nef gh\ni\n")); -} - -#[test] -fn xargs_max_lines() { - for arg in ["-L2", "--max-lines=2", "-l2"] { - cargo_bin_cmd!("xargs") - .arg(arg) - .write_stdin("ab cd\nef\ngh i\n\njkl\n") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab cd ef\ngh i jkl\n")); - } - cargo_bin_cmd!("xargs") - .arg("-l") - .write_stdin("ab cd\nef\ngh i\n\njkl\n") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab cd\nef\ngh i\njkl\n")); -} - -#[test] -fn xargs_max_args_lines_conflict() { - cargo_bin_cmd!("xargs") - // -n2 is last, so it should be given priority. - .args(["-L2", "-n2"]) - .write_stdin("ab cd ef\ngh i") - .assert() - .success() - .stderr(predicate::str::contains("WARNING")) - .stdout(predicate::str::diff("ab cd\nef gh\ni\n")); - - cargo_bin_cmd!("xargs") - // -n2 is last, so it should be given priority. - .args(["-I=_", "-n2", "echo", "_"]) - .write_stdin("ab cd ef\ngh i\njkl") - .assert() - .success() - .stderr(predicate::str::contains("WARNING")) - .stdout(predicate::str::diff("_ ab cd\n_ ef gh\n_ i jkl\n")); - - cargo_bin_cmd!("xargs") - // -L2 is last, so it should be given priority. - .args(["-n2", "-L2"]) - .write_stdin("ab cd\nef\ngh i\n\njkl\n") - .assert() - .success() - .stderr(predicate::str::contains("WARNING")) - .stdout(predicate::str::diff("ab cd ef\ngh i jkl\n")); - - cargo_bin_cmd!("xargs") - // -L2 is last, so it should be given priority. - .args(["-I=_", "-L2", "echo", "_"]) - .write_stdin("ab cd\nef\ngh i\n\njkl\n") - .assert() - .success() - .stderr(predicate::str::contains("WARNING")) - .stdout(predicate::str::diff("_ ab cd ef\n_ gh i jkl\n")); - - for redundant_arg in ["-L2", "-n2"] { - cargo_bin_cmd!("xargs") - // -I={} is last, so it should be given priority. - .args([redundant_arg, "-I={}", "echo", "{} bar"]) - .write_stdin("ab cd ef\ngh i\njkl") - .assert() - .success() - .stderr(predicate::str::contains("WARNING")) - .stdout(predicate::str::diff("ab cd ef bar\ngh i bar\njkl bar\n")); - } -} - -#[test] -fn xargs_max_chars() { - for arg in ["-s11", "--max-chars=11"] { - cargo_bin_cmd!("xargs") - .arg(arg) - .write_stdin("ab cd efg") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab cd\nefg\n")); - } - - // Behavior should be the same with -x, which only takes effect with -L or - // -n. - cargo_bin_cmd!("xargs") - .args(["-xs11"]) - .write_stdin("ab cd efg") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab cd\nefg\n")); - - cargo_bin_cmd!("xargs") - .args(["-s10"]) - .write_stdin("abcdefghijkl ab") - .assert() - .failure() - .code(1) - .stderr(predicate::str::contains("Error:")) - .stdout(predicate::str::is_empty()); -} - -#[test] -fn xargs_exit_on_large() { - cargo_bin_cmd!("xargs") - .args(["-xs11", "-n2"]) - .write_stdin("ab cd efg h i") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab cd\nefg h\ni\n")); - - cargo_bin_cmd!("xargs") - .args(["-xs11", "-n2"]) - .write_stdin("abcdefg hijklmn") - .assert() - .failure() - .code(1) - .stderr(predicate::str::contains("Error:")) - .stdout(predicate::str::is_empty()); -} - -#[test] -fn xargs_exec() { - let result = cargo_bin_cmd!("xargs") - .args([ - "-n2", - &path_to_testing_commandline(), - "-", - "--print_stdin", - "--no_print_cwd", - ]) - .write_stdin("a b c\nd") - .output(); - assert!(result.is_ok(), "xargs failed: {result:?}"); - let result = result.unwrap(); - assert_eq!(result.status.code(), Some(0)); - - assert!(result.stderr.is_empty(), "stderr: {result:?}"); - - let stdout_string = String::from_utf8(result.stdout).expect("Found invalid UTF-8"); - - assert_eq!( - stdout_string, - "stdin=\nargs=\n--print_stdin\n--no_print_cwd\na\nb\n\ - stdin=\nargs=\n--print_stdin\n--no_print_cwd\nc\nd\n", - ); -} - -#[test] -fn xargs_exec_stdin_open() { - let mut temp_file = tempfile::NamedTempFile::new().unwrap(); - - write!(temp_file, "a b c").unwrap(); - temp_file.seek(SeekFrom::Start(0)).unwrap(); - - let result = cargo_bin_cmd!("xargs") - .args([ - "-a", - &temp_file.path().to_string_lossy(), - &path_to_testing_commandline(), - "-", - "--print_stdin", - "--no_print_cwd", - ]) - .write_stdin("test") - .output(); - - assert!(result.is_ok(), "xargs failed: {result:?}"); - let result = result.unwrap(); - assert_eq!(result.status.code(), Some(0)); - - assert!(result.stderr.is_empty(), "stderr: {result:?}"); - - let stdout_string = String::from_utf8(result.stdout).expect("Found invalid UTF-8"); - - assert_eq!( - stdout_string, - "stdin=test\nargs=\n--print_stdin\n--no_print_cwd\na\nb\nc\n", - ); -} - -#[test] -fn xargs_exec_failure() { - let result = cargo_bin_cmd!("xargs") - .args([ - "-n1", - &path_to_testing_commandline(), - "-", - "--no_print_cwd", - "--exit_with_failure", - ]) - .write_stdin("a b") - .output(); - - assert!(result.is_ok(), "xargs failed: {result:?}"); - let result = result.unwrap(); - assert_eq!(result.status.code(), Some(123)); - - assert!(result.stderr.is_empty(), "stderr: {result:?}"); - - let stdout_string = String::from_utf8(result.stdout).expect("Found invalid UTF-8"); - - assert_eq!( - stdout_string, - "args=\n--no_print_cwd\n--exit_with_failure\na\n\ - args=\n--no_print_cwd\n--exit_with_failure\nb\n", - ); -} - -#[test] -fn xargs_exec_urgent_failure() { - let result = cargo_bin_cmd!("xargs") - .args([ - "-n1", - &path_to_testing_commandline(), - "-", - "--no_print_cwd", - "--exit_with_urgent_failure", - ]) - .write_stdin("a b") - .output(); - - assert!(result.is_ok(), "xargs failed: {result:?}"); - let result = result.unwrap(); - assert_eq!(result.status.code(), Some(124)); - - assert!(!result.stderr.is_empty(), "stderr: {result:?}"); - - let stdout_string = String::from_utf8(result.stdout).expect("Found invalid UTF-8"); - - assert_eq!( - stdout_string, - "args=\n--no_print_cwd\n--exit_with_urgent_failure\na\n" - ); -} - -#[test] -#[cfg(unix)] -fn xargs_exec_with_signal() { - let result = cargo_bin_cmd!("xargs") - .args([ - "-n1", - &path_to_testing_commandline(), - "-", - "--no_print_cwd", - "--exit_with_signal", - ]) - .write_stdin("a b") - .output(); - - assert!(result.is_ok(), "xargs failed: {result:?}"); - let result = result.unwrap(); - assert_eq!(result.status.code(), Some(125)); - assert!(!result.stderr.is_empty(), "stderr: {result:?}"); - - let stdout_string = String::from_utf8(result.stdout).expect("Found invalid UTF-8"); - - assert_eq!( - stdout_string, - "args=\n--no_print_cwd\n--exit_with_signal\na\n" - ); -} - -#[test] -fn xargs_exec_not_found() { - cargo_bin_cmd!("xargs") - .args(["this-file-does-not-exist"]) - .assert() - .failure() - .code(127) - .stderr(predicate::str::contains("Error:")) - .stdout(predicate::str::is_empty()); -} - -#[test] -fn xargs_exec_verbose() { - cargo_bin_cmd!("xargs") - .args([ - "-n2", - "--verbose", - &path_to_testing_commandline(), - "-", - "--print_stdin", - "--no_print_cwd", - ]) - .write_stdin("a b c\nd") - .assert() - .success() - .stderr(predicate::str::contains("testing-commandline")) - .stdout(predicate::str::diff( - "stdin=\nargs=\n--print_stdin\n--no_print_cwd\na\nb\n\ - stdin=\nargs=\n--print_stdin\n--no_print_cwd\nc\nd\n", - )); -} - -#[test] -fn xargs_unterminated_quote() { - cargo_bin_cmd!("xargs") - .args([ - "-n2", - &path_to_testing_commandline(), - "-", - "--print_stdin", - "--no_print_cwd", - ]) - .write_stdin("a \"b c\nd") - .assert() - .failure() - .code(1) - .stderr(predicate::str::contains("Error: Unterminated quote:")) - .stdout(predicate::str::is_empty()); -} - -#[test] -fn xargs_zero_lines() { - cargo_bin_cmd!("xargs") - .args([ - "-L0", - &path_to_testing_commandline(), - "-", - "--print_stdin", - "--no_print_cwd", - ]) - .write_stdin("a \"b c\nd") - .assert() - .failure() - .code(1) - .stderr(predicate::str::contains("Value must be > 0, not: 0")) - .stdout(predicate::str::is_empty()); -} - -#[test] -fn xargs_replace() { - cargo_bin_cmd!("xargs") - .args(["-i={}", "echo", "{} bar"]) - .write_stdin("foo") - .assert() - .stdout(predicate::str::contains("foo bar")); - - cargo_bin_cmd!("xargs") - .args(["-i=_", "echo", "_ bar"]) - .write_stdin("foo") - .assert() - .stdout(predicate::str::contains("foo bar")); - - cargo_bin_cmd!("xargs") - .args(["--replace=_", "echo", "_ _ bar"]) - .write_stdin("foo") - .assert() - .stdout(predicate::str::contains("foo foo bar")); - - cargo_bin_cmd!("xargs") - .args(["-i=_", "echo", "_ _ bar"]) - .write_stdin("foo") - .assert() - .stdout(predicate::str::contains("foo foo bar")); - - cargo_bin_cmd!("xargs") - .args(["-i", "echo", "{} {} bar"]) - .write_stdin("foo") - .assert() - .stdout(predicate::str::contains("foo foo bar")); - - cargo_bin_cmd!("xargs") - .args(["-I={}", "echo", "{} bar {}"]) - .write_stdin("foo") - .assert() - .stdout(predicate::str::contains("foo bar foo")); - - // Combine the two options to see which one wins - cargo_bin_cmd!("xargs") - .args(["-I=_", "-i", "echo", "{} bar {}"]) - .write_stdin("foo") - .assert() - .stdout(predicate::str::contains("foo bar foo")); - - // other order - cargo_bin_cmd!("xargs") - .args(["-i", "-I=_", "echo", "{} bar {}"]) - .write_stdin("foo") - .assert() - .stdout(predicate::str::contains("{} bar {}")); - - cargo_bin_cmd!("xargs") - .args(["-i", "-I", "_", "echo", "{} bar _"]) - .write_stdin("foo") - .assert() - .stdout(predicate::str::contains("{} bar foo")); - - // Expected to fail - cargo_bin_cmd!("xargs") - .args(["-I", "echo", "_ _ bar"]) - .write_stdin("foo") - .assert() - .failure() - .stderr(predicate::str::contains("Error: Command not found")); -} - -#[test] -fn xargs_replace_multiple_lines() { - cargo_bin_cmd!("xargs") - .args(["-I", "_", "echo", "[_]"]) - .write_stdin("ab c\nd ef\ng") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("[ab c]\n[d ef]\n[g]\n")); - - cargo_bin_cmd!("xargs") - .args(["-I", "{}", "echo", "{} {} foo"]) - .write_stdin("bar\nbaz") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("bar bar foo\nbaz baz foo\n")); - - cargo_bin_cmd!("xargs") - .args(["-I", "non-exist", "echo"]) - .write_stdin("abc\ndef\ng") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("\n\n\n")); -} - -#[test] -fn xargs_help() { - for option_style in ["-h", "--help"] { - cargo_bin_cmd!("xargs") - .args([option_style]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::contains("--help")); - } -} - -// Do not regress to: -// -// ❯ xargs --version -// Error: xargs 0.7.0 -// -// Same for help above -#[test] -fn xargs_version() { - for option_style in ["-V", "--version"] { - cargo_bin_cmd!("xargs") - .args([option_style]) - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::starts_with("xargs ")); - } -} - -#[test] -fn xargs_eof() { - for option_style in [vec!["-ecd"], vec!["-E", "cd"], vec!["--eof", "cd"]] { - cargo_bin_cmd!("xargs") - .args(option_style.as_slice()) - .write_stdin("ab cd ef") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab\n")); - } -} - -#[test] -fn xargs_eof_with_delimiter() { - cargo_bin_cmd!("xargs") - .args(["-0", "-Ecd"]) - .write_stdin("ab\0cd\0ef") - .assert() - .success() - .stderr(predicate::str::is_empty()) - .stdout(predicate::str::diff("ab cd ef\n")); -}