From 50fe62344729fb2194cd81159c605a10d3d6651a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 28 Mar 2025 09:22:03 +0100 Subject: [PATCH] Create the uutest crate + adjust the code + move some of the tests into the program test --- .../workspace.wordlist.txt | 2 + Cargo.lock | 63 ++++++++++- Cargo.toml | 3 + tests/test_util_name.rs | 107 ++++++++++-------- tests/tests.rs | 15 ++- tests/uutests/Cargo.toml | 45 ++++++++ .../{common/mod.rs => uutests/src/lib/lib.rs} | 0 tests/{common => uutests/src/lib}/macros.rs | 0 tests/uutests/src/lib/mod.rs | 8 ++ tests/{common => uutests/src/lib}/random.rs | 0 tests/{common => uutests/src/lib}/util.rs | 32 ++++-- 11 files changed, 211 insertions(+), 64 deletions(-) create mode 100644 tests/uutests/Cargo.toml rename tests/{common/mod.rs => uutests/src/lib/lib.rs} (100%) rename tests/{common => uutests/src/lib}/macros.rs (100%) create mode 100644 tests/uutests/src/lib/mod.rs rename tests/{common => uutests/src/lib}/random.rs (100%) rename tests/{common => uutests/src/lib}/util.rs (99%) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 45373d95c..43f56dfc2 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -325,6 +325,7 @@ libc libstdbuf musl tmpd +uchild ucmd ucommand utmpx @@ -333,6 +334,7 @@ uucore_procs uudoc uumain uutil +uutests uutils # * function names diff --git a/Cargo.lock b/Cargo.lock index b0f54656c..086aace1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,6 +448,7 @@ dependencies = [ "clap", "clap_complete", "clap_mangen", + "ctor", "filetime", "glob", "hex-literal", @@ -574,6 +575,7 @@ dependencies = [ "uu_yes", "uucore", "uuhelp_parser", + "uutests", "walkdir", "xattr", "zip", @@ -724,6 +726,22 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e9666f4a9a948d4f1dff0c08a4512b0f7c86414b23960104c243c10d79f4c3" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" + [[package]] name = "ctrlc" version = "3.4.5" @@ -817,6 +835,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dtor" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222ef136a1c687d4aa0395c175f2c4586e379924c352fd02f7870cf7de783c23" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" + [[package]] name = "dunce" version = "1.0.5" @@ -848,7 +881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1259,7 +1292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1989,7 +2022,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2002,7 +2035,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.3", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2246,7 +2279,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3531,6 +3564,24 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +[[package]] +name = "uutests" +version = "0.0.30" +dependencies = [ + "ctor", + "glob", + "libc", + "nix", + "pretty_assertions", + "rand 0.9.0", + "regex", + "rlimit", + "tempfile", + "time", + "uucore", + "xattr", +] + [[package]] name = "uutils_term_grid" version = "0.6.0" @@ -3670,7 +3721,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a0fd3f19d..226bd28d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -366,6 +366,7 @@ uucore = { version = "0.0.30", package = "uucore", path = "src/uucore" } uucore_procs = { version = "0.0.30", package = "uucore_procs", path = "src/uucore_procs" } uu_ls = { version = "0.0.30", path = "src/uu/ls" } uu_base32 = { version = "0.0.30", path = "src/uu/base32" } +uutests = { version = "0.0.30", package = "uutests", path = "tests/uutests/" } [dependencies] clap = { workspace = true } @@ -505,6 +506,7 @@ sha1 = { workspace = true, features = ["std"] } tempfile = { workspace = true } time = { workspace = true, features = ["local-offset"] } unindent = "0.2.3" +uutests = { workspace = true } uucore = { workspace = true, features = [ "mode", "entries", @@ -515,6 +517,7 @@ uucore = { workspace = true, features = [ walkdir = { workspace = true } hex-literal = "1.0.0" rstest = { workspace = true } +ctor = "0.4.1" [target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies] procfs = { version = "0.17", default-features = false } diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index dd8cd9359..789077b5c 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -2,15 +2,26 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -#![allow(unused_imports)] -mod common; - -use common::util::TestScenario; +use uutests::util::TestScenario; #[cfg(unix)] use std::os::unix::fs::symlink as symlink_file; -#[cfg(windows)] -use std::os::windows::fs::symlink_file; + +use std::env; +pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_coreutils"); + +// Set the environment variable for any tests + +// Use the ctor attribute to run this function before any tests +#[ctor::ctor] +fn init() { + // No need for unsafe here + unsafe { + std::env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); + } + // Print for debugging + eprintln!("Setting UUTESTS_BINARY_PATH={}", TESTS_BINARY); +} #[test] #[cfg(feature = "ls")] @@ -18,6 +29,10 @@ fn execution_phrase_double() { use std::process::Command; let scenario = TestScenario::new("ls"); + if !scenario.bin_path.exists() { + println!("Skipping test: Binary not found at {:?}", scenario.bin_path); + return; + } let output = Command::new(&scenario.bin_path) .arg("ls") .arg("--some-invalid-arg") @@ -30,25 +45,6 @@ fn execution_phrase_double() { ); } -#[test] -#[cfg(feature = "ls")] -#[cfg(any(unix, windows))] -fn execution_phrase_single() { - use std::process::Command; - - let scenario = TestScenario::new("ls"); - symlink_file(&scenario.bin_path, scenario.fixtures.plus("uu-ls")).unwrap(); - let output = Command::new(scenario.fixtures.plus("uu-ls")) - .arg("--some-invalid-arg") - .output() - .unwrap(); - dbg!(String::from_utf8(output.stderr.clone()).unwrap()); - assert!(String::from_utf8(output.stderr).unwrap().contains(&format!( - "Usage: {}", - scenario.fixtures.plus("uu-ls").display() - ))); -} - #[test] #[cfg(feature = "sort")] fn util_name_double() { @@ -58,6 +54,10 @@ fn util_name_double() { }; let scenario = TestScenario::new("sort"); + if !scenario.bin_path.exists() { + println!("Skipping test: Binary not found at {:?}", scenario.bin_path); + return; + } let mut child = Command::new(&scenario.bin_path) .arg("sort") .stdin(Stdio::piped()) @@ -72,7 +72,7 @@ fn util_name_double() { #[test] #[cfg(feature = "sort")] -#[cfg(any(unix, windows))] +#[cfg(unix)] fn util_name_single() { use std::{ io::Write, @@ -80,6 +80,11 @@ fn util_name_single() { }; let scenario = TestScenario::new("sort"); + if !scenario.bin_path.exists() { + println!("Skipping test: Binary not found at {:?}", scenario.bin_path); + return; + } + symlink_file(&scenario.bin_path, scenario.fixtures.plus("uu-sort")).unwrap(); let mut child = Command::new(scenario.fixtures.plus("uu-sort")) .stdin(Stdio::piped()) @@ -96,14 +101,15 @@ fn util_name_single() { } #[test] -#[cfg(any(unix, windows))] +#[cfg(unix)] fn util_invalid_name_help() { - use std::{ - io::Write, - process::{Command, Stdio}, - }; + use std::process::{Command, Stdio}; let scenario = TestScenario::new("invalid_name"); + if !scenario.bin_path.exists() { + println!("Skipping test: Binary not found at {:?}", scenario.bin_path); + return; + } symlink_file(&scenario.bin_path, scenario.fixtures.plus("invalid_name")).unwrap(); let child = Command::new(scenario.fixtures.plus("invalid_name")) .arg("--help") @@ -132,14 +138,17 @@ fn util_non_utf8_name_help() { // Make sure we don't crash even if the util name is invalid UTF-8. use std::{ ffi::OsStr, - io::Write, os::unix::ffi::OsStrExt, - path::Path, process::{Command, Stdio}, }; let scenario = TestScenario::new("invalid_name"); let non_utf8_path = scenario.fixtures.plus(OsStr::from_bytes(b"\xff")); + if !scenario.bin_path.exists() { + println!("Skipping test: Binary not found at {:?}", scenario.bin_path); + return; + } + symlink_file(&scenario.bin_path, &non_utf8_path).unwrap(); let child = Command::new(&non_utf8_path) .arg("--help") @@ -160,15 +169,17 @@ fn util_non_utf8_name_help() { } #[test] -#[cfg(any(unix, windows))] +#[cfg(unix)] fn util_invalid_name_invalid_command() { - use std::{ - io::Write, - process::{Command, Stdio}, - }; + use std::process::{Command, Stdio}; let scenario = TestScenario::new("invalid_name"); symlink_file(&scenario.bin_path, scenario.fixtures.plus("invalid_name")).unwrap(); + if !scenario.bin_path.exists() { + println!("Skipping test: Binary not found at {:?}", scenario.bin_path); + return; + } + let child = Command::new(scenario.fixtures.plus("invalid_name")) .arg("definitely_invalid") .stdin(Stdio::piped()) @@ -188,12 +199,14 @@ fn util_invalid_name_invalid_command() { #[test] #[cfg(feature = "true")] fn util_completion() { - use std::{ - io::Write, - process::{Command, Stdio}, - }; + use std::process::{Command, Stdio}; let scenario = TestScenario::new("completion"); + if !scenario.bin_path.exists() { + println!("Skipping test: Binary not found at {:?}", scenario.bin_path); + return; + } + let child = Command::new(&scenario.bin_path) .arg("completion") .arg("true") @@ -216,12 +229,14 @@ fn util_completion() { #[test] #[cfg(feature = "true")] fn util_manpage() { - use std::{ - io::Write, - process::{Command, Stdio}, - }; + use std::process::{Command, Stdio}; let scenario = TestScenario::new("completion"); + if !scenario.bin_path.exists() { + println!("Skipping test: Binary not found at {:?}", scenario.bin_path); + return; + } + let child = Command::new(&scenario.bin_path) .arg("manpage") .arg("true") diff --git a/tests/tests.rs b/tests/tests.rs index 1fb5735eb..a4eaaacff 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,8 +2,19 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -#[macro_use] -mod common; + +// Then override the macro with your constant +use std::env; + +pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_coreutils"); + +// Use the ctor attribute to run this function before any tests +#[ctor::ctor] +fn init() { + unsafe { + std::env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); + } +} #[cfg(feature = "arch")] #[path = "by-util/test_arch.rs"] diff --git a/tests/uutests/Cargo.toml b/tests/uutests/Cargo.toml new file mode 100644 index 000000000..2ede02148 --- /dev/null +++ b/tests/uutests/Cargo.toml @@ -0,0 +1,45 @@ +# spell-checker:ignore (features) zerocopy serde + +[package] +name = "uutests" +version = "0.0.30" +authors = ["uutils developers"] +license = "MIT" +description = "uutils ~ 'core' uutils test library (cross-platform)" + +homepage = "https://github.com/uutils/coreutils" +repository = "https://github.com/uutils/coreutils/tree/main/src/tests/common" +# readme = "README.md" +keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] +categories = ["command-line-utilities"] +edition = "2024" + +[package.metadata.docs.rs] +all-features = true + +[lib] +path = "src/lib/lib.rs" + +[dependencies] +glob = { workspace = true } +libc = { workspace = true } +pretty_assertions = "1.4.0" +rand = { workspace = true } +regex = { workspace = true } +tempfile = { workspace = true } +time = { workspace = true, features = ["local-offset"] } +uucore = { workspace = true, features = [ + "mode", + "entries", + "process", + "signals", + "utmpx", +] } +ctor = "0.4.1" + +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] + +[target.'cfg(unix)'.dependencies] +nix = { workspace = true, features = ["process", "signal", "user", "term"] } +rlimit = "0.10.1" +xattr = { workspace = true } diff --git a/tests/common/mod.rs b/tests/uutests/src/lib/lib.rs similarity index 100% rename from tests/common/mod.rs rename to tests/uutests/src/lib/lib.rs diff --git a/tests/common/macros.rs b/tests/uutests/src/lib/macros.rs similarity index 100% rename from tests/common/macros.rs rename to tests/uutests/src/lib/macros.rs diff --git a/tests/uutests/src/lib/mod.rs b/tests/uutests/src/lib/mod.rs new file mode 100644 index 000000000..05e2b1382 --- /dev/null +++ b/tests/uutests/src/lib/mod.rs @@ -0,0 +1,8 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +#[macro_use] +pub mod macros; +pub mod random; +pub mod util; diff --git a/tests/common/random.rs b/tests/uutests/src/lib/random.rs similarity index 100% rename from tests/common/random.rs rename to tests/uutests/src/lib/random.rs diff --git a/tests/common/util.rs b/tests/uutests/src/lib/util.rs similarity index 99% rename from tests/common/util.rs rename to tests/uutests/src/lib/util.rs index 4b7acaa54..79c5ac96b 100644 --- a/tests/common/util.rs +++ b/tests/uutests/src/lib/util.rs @@ -2,7 +2,6 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. - //spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL canonicalized openpty //spell-checker: ignore (linux) winsize xpixel ypixel setrlimit FSIZE SIGBUS SIGSEGV sigbus tmpfs @@ -22,8 +21,6 @@ use nix::sys; use pretty_assertions::assert_eq; #[cfg(unix)] use rlimit::setrlimit; -#[cfg(feature = "sleep")] -use rstest::rstest; use std::borrow::Cow; use std::collections::VecDeque; #[cfg(not(windows))] @@ -63,7 +60,21 @@ static MULTIPLE_STDIN_MEANINGLESS: &str = "Ucommand is designed around a typical static NO_STDIN_MEANINGLESS: &str = "Setting this flag has no effect if there is no stdin"; static END_OF_TRANSMISSION_SEQUENCE: &[u8] = b"\n\x04"; -pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_coreutils"); +// we can't use +// pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_coreutils"); +// as we are in a library, not a binary +pub fn get_tests_binary() -> String { + std::env::var("CARGO_BIN_EXE_coreutils").unwrap_or_else(|_| { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let debug_or_release = if cfg!(debug_assertions) { + "debug" + } else { + "release" + }; + format!("{manifest_dir}/../../target/{debug_or_release}/coreutils") + }) +} + pub const PATH: &str = env!("PATH"); /// Default environment variables to run the commands with @@ -1178,8 +1189,9 @@ impl TestScenario { T: AsRef, { let tmpd = Rc::new(TempDir::new().unwrap()); + println!("bin: {:?}", get_tests_binary()); let ts = Self { - bin_path: PathBuf::from(TESTS_BINARY), + bin_path: PathBuf::from(get_tests_binary()), util_name: util_name.as_ref().into(), fixtures: AtPath::new(tmpd.as_ref().path()), tmpd, @@ -1343,7 +1355,7 @@ impl UCommand { { let mut ucmd = Self::new(); ucmd.util_name = Some(util_name.as_ref().into()); - ucmd.bin_path(TESTS_BINARY).temp_dir(tmpd); + ucmd.bin_path(&*get_tests_binary()).temp_dir(tmpd); ucmd } @@ -1604,7 +1616,7 @@ impl UCommand { self.args.push_front(util_name.into()); } } else if let Some(util_name) = &self.util_name { - self.bin_path = Some(PathBuf::from(TESTS_BINARY)); + self.bin_path = Some(PathBuf::from(&*get_tests_binary())); self.args.push_front(util_name.into()); // neither `bin_path` nor `util_name` was set so we apply the default to run the arguments // in a platform specific shell @@ -2762,7 +2774,7 @@ const UUTILS_INFO: &str = "uutils-tests-info"; /// Example: /// /// ```no_run -/// use crate::common::util::*; +/// use uutests::util::*; /// const VERSION_MIN_MULTIPLE_USERS: &str = "8.31"; /// /// #[test] @@ -2838,7 +2850,7 @@ fn parse_coreutil_version(version_string: &str) -> f32 { /// Example: /// /// ```no_run -/// use crate::common::util::*; +/// use uutests::util::*; /// #[test] /// fn test_xyz() { /// let ts = TestScenario::new(util_name!()); @@ -2901,7 +2913,7 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result< /// Example: /// /// ```no_run -/// use crate::common::util::*; +/// use uutests::util::*; /// #[test] /// fn test_xyz() { /// let ts = TestScenario::new("whoami");