1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

Create the uutest crate + adjust the code

+ move some of the tests into the program test
This commit is contained in:
Sylvestre Ledru 2025-03-28 09:22:03 +01:00
parent bf337a29af
commit 50fe623447
11 changed files with 211 additions and 64 deletions

View file

@ -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")

View file

@ -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"]

45
tests/uutests/Cargo.toml Normal file
View file

@ -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 }

View file

@ -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;

View file

@ -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<str>,
{
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");