diff --git a/GNUmakefile b/GNUmakefile index 89a4dca80..bb82925ec 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,4 +1,4 @@ -# spell-checker:ignore (misc) testsuite runtest (targets) busytest distclean manpages pkgs ; (vars/env) BINDIR BUILDDIR CARGOFLAGS DESTDIR DOCSDIR INSTALLDIR INSTALLEES MANDIR MULTICALL +# spell-checker:ignore (misc) testsuite runtest findstring (targets) busytest distclean manpages pkgs ; (vars/env) BINDIR BUILDDIR CARGOFLAGS DESTDIR DOCSDIR INSTALLDIR INSTALLEES MANDIR MULTICALL # Config options PROFILE ?= debug @@ -307,10 +307,12 @@ ifeq (${MULTICALL}, y) $(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out coreutils, $(INSTALLEES)), \ ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)$(prog) &&) : + $(if $(findstring test,$(INSTALLEES)), ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)[) cat $(DOCSDIR)/_build/man/coreutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)coreutils.1.gz else $(foreach prog, $(INSTALLEES), \ $(INSTALL) $(BUILDDIR)/$(prog) $(INSTALLDIR_BIN)/$(PROG_PREFIX)$(prog);) + $(if $(findstring test,$(INSTALLEES)), $(INSTALL) $(BUILDDIR)/test $(INSTALLDIR_BIN)/$(PROG_PREFIX)[) endif $(foreach man, $(filter $(INSTALLEES), $(basename $(notdir $(wildcard $(DOCSDIR)/_build/man/*)))), \ cat $(DOCSDIR)/_build/man/$(man).1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)$(man).1.gz &&) : @@ -326,6 +328,7 @@ ifeq (${MULTICALL}, y) endif rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz) rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS)) + rm -f $(INSTALLDIR_BIN)/$(PROG_PREFIX)[ rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(PROG_PREFIX),$(addsuffix .fish,$(PROGS))) diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 356f2e6b1..8d85dc85e 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -5,11 +5,15 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[macro_use] extern crate uucore; use clap::{crate_version, App, Arg}; use std::path::Path; +use uucore::error::{UResult, UUsageError}; use uucore::InvalidEncodingHandling; static ABOUT: &str = "strip last component from file name"; @@ -30,7 +34,8 @@ fn get_long_usage() -> String { ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -77,11 +82,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { print!("{}", separator); } } else { - show_usage_error!("missing operand"); - return 1; + return Err(UUsageError::new(1, "missing operand".to_string())); } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 05167853c..437668947 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -31,6 +31,8 @@ use std::path::Path; use std::path::PathBuf; use std::str::FromStr; use std::time::{Duration, UNIX_EPOCH}; +use std::{error::Error, fmt::Display}; +use uucore::error::{UCustomError, UResult}; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::InvalidEncodingHandling; #[cfg(windows)] @@ -399,8 +401,61 @@ fn get_usage() -> String { ) } +#[derive(Debug)] +enum DuError { + InvalidMaxDepthArg(String), + SummarizeDepthConflict(String), + InvalidTimeStyleArg(String), + InvalidTimeArg(String), +} + +impl Display for DuError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DuError::InvalidMaxDepthArg(s) => write!(f, "invalid maximum depth '{}'", s), + DuError::SummarizeDepthConflict(s) => { + write!(f, "summarizing conflicts with --max-depth={}", s) + } + DuError::InvalidTimeStyleArg(s) => { + write!( + f, + "invalid argument '{}' for 'time style' +Valid arguments are: +- 'full-iso' +- 'long-iso' +- 'iso' +Try '{} --help' for more information.", + s, NAME + ) + } + DuError::InvalidTimeArg(s) => { + write!( + f, + "Invalid argument '{}' for --time. +'birth' and 'creation' arguments are not supported on this platform.", + s + ) + } + } + } +} + +impl Error for DuError {} + +impl UCustomError for DuError { + fn code(&self) -> i32 { + match self { + Self::InvalidMaxDepthArg(_) => 1, + Self::SummarizeDepthConflict(_) => 1, + Self::InvalidTimeStyleArg(_) => 1, + Self::InvalidTimeArg(_) => 1, + } + } +} + +#[uucore_procs::gen_uumain] #[allow(clippy::cognitive_complexity)] -pub fn uumain(args: impl uucore::Args) -> i32 { +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -411,19 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let summarize = matches.is_present(options::SUMMARIZE); - let max_depth_str = matches.value_of(options::MAX_DEPTH); - let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::().ok()); - match (max_depth_str, max_depth) { - (Some(s), _) if summarize => { - show_error!("summarizing conflicts with --max-depth={}", s); - return 1; - } - (Some(s), None) => { - show_error!("invalid maximum depth '{}'", s); - return 1; - } - (Some(_), Some(_)) | (None, _) => { /* valid */ } - } + let max_depth = parse_depth(matches.value_of(options::MAX_DEPTH), summarize)?; let options = Options { all: matches.is_present(options::ALL), @@ -480,27 +523,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } }; - let time_format_str = match matches.value_of("time-style") { - Some(s) => match s { - "full-iso" => "%Y-%m-%d %H:%M:%S.%f %z", - "long-iso" => "%Y-%m-%d %H:%M", - "iso" => "%Y-%m-%d", - _ => { - show_error!( - "invalid argument '{}' for 'time style' -Valid arguments are: -- 'full-iso' -- 'long-iso' -- 'iso' -Try '{} --help' for more information.", - s, - NAME - ); - return 1; - } - }, - None => "%Y-%m-%d %H:%M", - }; + let time_format_str = parse_time_style(matches.value_of("time-style"))?; let line_separator = if matches.is_present(options::NULL) { "\0" @@ -534,18 +557,9 @@ Try '{} --help' for more information.", Some(s) => match s { "ctime" | "status" => stat.modified, "access" | "atime" | "use" => stat.accessed, - "birth" | "creation" => { - if let Some(time) = stat.created { - time - } else { - show_error!( - "Invalid argument '{}' for --time. -'birth' and 'creation' arguments are not supported on this platform.", - s - ); - return 1; - } - } + "birth" | "creation" => stat + .created + .ok_or_else(|| DuError::InvalidTimeArg(s.into()))?, // below should never happen as clap already restricts the values. _ => unreachable!("Invalid field for --time"), }, @@ -590,7 +604,28 @@ Try '{} --help' for more information.", print!("{}", line_separator); } - 0 + Ok(()) +} + +fn parse_time_style(s: Option<&str>) -> UResult<&str> { + match s { + Some(s) => match s { + "full-iso" => Ok("%Y-%m-%d %H:%M:%S.%f %z"), + "long-iso" => Ok("%Y-%m-%d %H:%M"), + "iso" => Ok("%Y-%m-%d"), + _ => Err(DuError::InvalidTimeStyleArg(s.into()).into()), + }, + None => Ok("%Y-%m-%d %H:%M"), + } +} + +fn parse_depth(max_depth_str: Option<&str>, summarize: bool) -> UResult> { + let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::().ok()); + match (max_depth_str, max_depth) { + (Some(s), _) if summarize => Err(DuError::SummarizeDepthConflict(s.into()).into()), + (Some(s), None) => Err(DuError::InvalidMaxDepthArg(s.into()).into()), + (Some(_), Some(_)) | (None, _) => Ok(max_depth), + } } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index e9fc08379..180c4d2e5 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -7,11 +7,15 @@ // spell-checker:ignore (ToDO) gethostid +// Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[macro_use] extern crate uucore; use clap::{crate_version, App}; use libc::c_long; +use uucore::error::UResult; static SYNTAX: &str = "[options]"; @@ -20,10 +24,11 @@ extern "C" { pub fn gethostid() -> c_long; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { uu_app().get_matches_from(args); hostid(); - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index fe477d7b5..14f8b9df2 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -7,6 +7,9 @@ // spell-checker:ignore (ToDO) MAKEWORD addrs hashset +// Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[macro_use] extern crate uucore; @@ -14,6 +17,9 @@ use clap::{crate_version, App, Arg, ArgMatches}; use std::collections::hash_set::HashSet; use std::net::ToSocketAddrs; use std::str; +#[cfg(windows)] +use uucore::error::UUsageError; +use uucore::error::{UResult, USimpleError}; #[cfg(windows)] use winapi::shared::minwindef::MAKEWORD; @@ -28,15 +34,18 @@ static OPT_FQDN: &str = "fqdn"; static OPT_SHORT: &str = "short"; static OPT_HOST: &str = "host"; -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { #![allow(clippy::let_and_return)] #[cfg(windows)] unsafe { #[allow(deprecated)] let mut data = std::mem::uninitialized(); if WSAStartup(MAKEWORD(2, 2), &mut data as *mut _) != 0 { - eprintln!("Failed to start Winsock 2.2"); - return 1; + return Err(UUsageError::new( + 1, + "Failed to start Winsock 2.2".to_string(), + )); } } let result = execute(args); @@ -50,7 +59,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fn get_usage() -> String { format!("{0} [OPTION]... [HOSTNAME]", executable!()) } -fn execute(args: impl uucore::Args) -> i32 { +fn execute(args: impl uucore::Args) -> UResult<()> { let usage = get_usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -58,10 +67,9 @@ fn execute(args: impl uucore::Args) -> i32 { None => display_hostname(&matches), Some(host) => { if let Err(err) = hostname::set(host) { - show_error!("{}", err); - 1 + return Err(USimpleError::new(1, format!("{}", err))); } else { - 0 + Ok(()) } } } @@ -97,7 +105,7 @@ pub fn uu_app() -> App<'static, 'static> { .arg(Arg::with_name(OPT_HOST)) } -fn display_hostname(matches: &ArgMatches) -> i32 { +fn display_hostname(matches: &ArgMatches) -> UResult<()> { let hostname = hostname::get().unwrap().into_string().unwrap(); if matches.is_present(OPT_IP_ADDRESS) { @@ -127,12 +135,10 @@ fn display_hostname(matches: &ArgMatches) -> i32 { println!("{}", &output[0..len - 1]); } - 0 + Ok(()) } Err(f) => { - show_error!("{}", f); - - 1 + return Err(USimpleError::new(1, format!("{}", f))); } } } else { @@ -144,12 +150,12 @@ fn display_hostname(matches: &ArgMatches) -> i32 { } else { println!("{}", &hostname[ci.0 + 1..]); } - return 0; + return Ok(()); } } println!("{}", hostname); - 0 + Ok(()) } } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 1ba5ee0b5..55bcdb77b 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1267,6 +1267,7 @@ pub fn uu_app() -> App<'static, 'static> { .help("sort by a key") .long_help(LONG_HELP_KEYS) .multiple(true) + .number_of_values(1) .takes_value(true), ) .arg( diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index dba840d3c..bed1472e2 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -10,12 +10,82 @@ mod parser; -use clap::{App, AppSettings}; +use clap::{crate_version, App, AppSettings}; use parser::{parse, Symbol}; use std::ffi::{OsStr, OsString}; use std::path::Path; use uucore::executable; +const USAGE: &str = "test EXPRESSION +or: test +or: [ EXPRESSION ] +or: [ ] +or: [ OPTION"; + +// We use after_help so that this comes after the usage string (it would come before if we used about) +const AFTER_HELP: &str = " +Exit with the status determined by EXPRESSION. + +An omitted EXPRESSION defaults to false. Otherwise, +EXPRESSION is true or false and sets exit status. It is one of: + + ( EXPRESSION ) EXPRESSION is true + ! EXPRESSION EXPRESSION is false + EXPRESSION1 -a EXPRESSION2 both EXPRESSION1 and EXPRESSION2 are true + EXPRESSION1 -o EXPRESSION2 either EXPRESSION1 or EXPRESSION2 is true + + -n STRING the length of STRING is nonzero + STRING equivalent to -n STRING + -z STRING the length of STRING is zero + STRING1 = STRING2 the strings are equal + STRING1 != STRING2 the strings are not equal + + INTEGER1 -eq INTEGER2 INTEGER1 is equal to INTEGER2 + INTEGER1 -ge INTEGER2 INTEGER1 is greater than or equal to INTEGER2 + INTEGER1 -gt INTEGER2 INTEGER1 is greater than INTEGER2 + INTEGER1 -le INTEGER2 INTEGER1 is less than or equal to INTEGER2 + INTEGER1 -lt INTEGER2 INTEGER1 is less than INTEGER2 + INTEGER1 -ne INTEGER2 INTEGER1 is not equal to INTEGER2 + + FILE1 -ef FILE2 FILE1 and FILE2 have the same device and inode numbers + FILE1 -nt FILE2 FILE1 is newer (modification date) than FILE2 + FILE1 -ot FILE2 FILE1 is older than FILE2 + + -b FILE FILE exists and is block special + -c FILE FILE exists and is character special + -d FILE FILE exists and is a directory + -e FILE FILE exists + -f FILE FILE exists and is a regular file + -g FILE FILE exists and is set-group-ID + -G FILE FILE exists and is owned by the effective group ID + -h FILE FILE exists and is a symbolic link (same as -L) + -k FILE FILE exists and has its sticky bit set + -L FILE FILE exists and is a symbolic link (same as -h) + -N FILE FILE exists and has been modified since it was last read + -O FILE FILE exists and is owned by the effective user ID + -p FILE FILE exists and is a named pipe + -r FILE FILE exists and read permission is granted + -s FILE FILE exists and has a size greater than zero + -S FILE FILE exists and is a socket + -t FD file descriptor FD is opened on a terminal + -u FILE FILE exists and its set-user-ID bit is set + -w FILE FILE exists and write permission is granted + -x FILE FILE exists and execute (or search) permission is granted + +Except for -h and -L, all FILE-related tests dereference symbolic links. +Beware that parentheses need to be escaped (e.g., by backslashes) for shells. +INTEGER may also be -l STRING, which evaluates to the length of STRING. + +NOTE: Binary -a and -o are inherently ambiguous. Use 'test EXPR1 && test +EXPR2' or 'test EXPR1 || test EXPR2' instead. + +NOTE: [ honors the --help and --version options, but test does not. +test treats each of those as it treats any other nonempty STRING. + +NOTE: your shell may have its own version of test and/or [, which usually supersedes +the version described here. Please refer to your shell's documentation +for details about the options it supports."; + pub fn uu_app() -> App<'static, 'static> { App::new(executable!()) .setting(AppSettings::DisableHelpFlags) @@ -30,8 +100,22 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { .to_string_lossy(); let mut args: Vec<_> = args.collect(); - // If invoked via name '[', matching ']' must be in the last arg - if binary_name == "[" { + if binary_name.ends_with('[') { + // If invoked as [ we should recognize --help and --version (but not -h or -v) + if args.len() == 1 && (args[0] == "--help" || args[0] == "--version") { + // Let clap pretty-print help and version + App::new(binary_name) + .version(crate_version!()) + .usage(USAGE) + .after_help(AFTER_HELP) + // Disable printing of -h and -v as valid alternatives for --help and --version, + // since we don't recognize -h and -v as help/version flags. + .setting(AppSettings::NeedsLongHelp) + .setting(AppSettings::NeedsLongVersion) + .get_matches_from(std::iter::once(program).chain(args.into_iter())); + return 0; + } + // If invoked via name '[', matching ']' must be in the last arg let last = args.pop(); if last != Some(OsString::from("]")) { eprintln!("[: missing ']'"); diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index dda859722..abd50d1b8 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -30,12 +30,16 @@ pub mod options { pub static OS: &str = "operating-system"; } -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "")))] const HOST_OS: &str = "GNU/Linux"; +#[cfg(all(target_os = "linux", not(any(target_env = "gnu", target_env = ""))))] +const HOST_OS: &str = "Linux"; #[cfg(target_os = "windows")] const HOST_OS: &str = "Windows NT"; #[cfg(target_os = "freebsd")] const HOST_OS: &str = "FreeBSD"; +#[cfg(target_os = "netbsd")] +const HOST_OS: &str = "NetBSD"; #[cfg(target_os = "openbsd")] const HOST_OS: &str = "OpenBSD"; #[cfg(target_vendor = "apple")] diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 029f5e516..e63aa4585 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -39,13 +39,13 @@ fn _du_basics(s: &str) { #[test] fn test_du_basics_subdir() { - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); - let result = scene.ucmd().arg(SUB_DIR).succeeds(); + let result = ts.ucmd().arg(SUB_DIR).succeeds(); #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg(SUB_DIR).run(); + let result_reference = unwrap_or_return!(expected_result(&ts, &[SUB_DIR])); if result_reference.succeeded() { assert_eq!(result.stdout_str(), result_reference.stdout_str()); return; @@ -83,15 +83,16 @@ fn _du_basics_subdir(s: &str) { #[test] fn test_du_invalid_size() { let args = &["block-size", "threshold"]; + let ts = TestScenario::new(util_name!()); for s in args { - new_ucmd!() + ts.ucmd() .arg(format!("--{}=1fb4t", s)) .arg("/tmp") .fails() .code_is(1) .stderr_only(format!("du: invalid --{} argument '1fb4t'", s)); #[cfg(not(target_pointer_width = "128"))] - new_ucmd!() + ts.ucmd() .arg(format!("--{}=1Y", s)) .arg("/tmp") .fails() @@ -110,16 +111,16 @@ fn test_du_basics_bad_name() { #[test] fn test_du_soft_link() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; at.symlink_file(SUB_FILE, SUB_LINK); - let result = scene.ucmd().arg(SUB_DIR_LINKS).succeeds(); + let result = ts.ucmd().arg(SUB_DIR_LINKS).succeeds(); #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg(SUB_DIR_LINKS).run(); + let result_reference = unwrap_or_return!(expected_result(&ts, &[SUB_DIR_LINKS])); if result_reference.succeeded() { assert_eq!(result.stdout_str(), result_reference.stdout_str()); return; @@ -157,16 +158,16 @@ fn _du_soft_link(s: &str) { #[test] fn test_du_hard_link() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; at.hard_link(SUB_FILE, SUB_LINK); - let result = scene.ucmd().arg(SUB_DIR_LINKS).succeeds(); + let result = ts.ucmd().arg(SUB_DIR_LINKS).succeeds(); #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg(SUB_DIR_LINKS).run(); + let result_reference = unwrap_or_return!(expected_result(&ts, &[SUB_DIR_LINKS])); if result_reference.succeeded() { assert_eq!(result.stdout_str(), result_reference.stdout_str()); return; @@ -204,13 +205,13 @@ fn _du_hard_link(s: &str) { #[test] fn test_du_d_flag() { - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); - let result = scene.ucmd().arg("-d1").succeeds(); + let result = ts.ucmd().arg("-d1").succeeds(); #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg("-d1").run(); + let result_reference = unwrap_or_return!(expected_result(&ts, &["-d1"])); if result_reference.succeeded() { assert_eq!(result.stdout_str(), result_reference.stdout_str()); return; @@ -247,16 +248,17 @@ fn _du_d_flag(s: &str) { #[test] fn test_du_dereference() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; at.symlink_dir(SUB_DEEPER_DIR, SUB_DIR_LINKS_DEEPER_SYM_DIR); - let result = scene.ucmd().arg("-L").arg(SUB_DIR_LINKS).succeeds(); + let result = ts.ucmd().arg("-L").arg(SUB_DIR_LINKS).succeeds(); #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg("-L").arg(SUB_DIR_LINKS).run(); + let result_reference = unwrap_or_return!(expected_result(&ts, &["-L", SUB_DIR_LINKS])); + if result_reference.succeeded() { assert_eq!(result.stdout_str(), result_reference.stdout_str()); return; @@ -294,12 +296,12 @@ fn _du_dereference(s: &str) { #[test] fn test_du_inodes_basic() { - let scene = TestScenario::new(util_name!()); - let result = scene.ucmd().arg("--inodes").succeeds(); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().arg("--inodes").succeeds(); #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg("--inodes").run(); + let result_reference = unwrap_or_return!(expected_result(&ts, &["--inodes"])); assert_eq!(result.stdout_str(), result_reference.stdout_str()); } @@ -335,20 +337,15 @@ fn _du_inodes_basic(s: &str) { #[test] fn test_du_inodes() { - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); - scene - .ucmd() + ts.ucmd() .arg("--summarize") .arg("--inodes") .succeeds() .stdout_only("11\t.\n"); - let result = scene - .ucmd() - .arg("--separate-dirs") - .arg("--inodes") - .succeeds(); + let result = ts.ucmd().arg("--separate-dirs").arg("--inodes").succeeds(); #[cfg(target_os = "windows")] result.stdout_contains("3\t.\\subdir\\links\n"); @@ -358,7 +355,8 @@ fn test_du_inodes() { #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg("--separate-dirs").arg("--inodes").run(); + let result_reference = + unwrap_or_return!(expected_result(&ts, &["--separate-dirs", "--inodes"])); assert_eq!(result.stdout_str(), result_reference.stdout_str()); } } @@ -375,31 +373,29 @@ fn test_du_h_flag_empty_file() { #[cfg(feature = "touch")] #[test] fn test_du_time() { - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); - scene - .ccmd("touch") + ts.ccmd("touch") .arg("-a") .arg("-t") .arg("201505150000") .arg("date_test") .succeeds(); - scene - .ccmd("touch") + ts.ccmd("touch") .arg("-m") .arg("-t") .arg("201606160000") .arg("date_test") .succeeds(); - let result = scene.ucmd().arg("--time").arg("date_test").succeeds(); + let result = ts.ucmd().arg("--time").arg("date_test").succeeds(); result.stdout_only("0\t2016-06-16 00:00\tdate_test\n"); - let result = scene.ucmd().arg("--time=atime").arg("date_test").succeeds(); + let result = ts.ucmd().arg("--time=atime").arg("date_test").succeeds(); result.stdout_only("0\t2015-05-15 00:00\tdate_test\n"); - let result = scene.ucmd().arg("--time=ctime").arg("date_test").succeeds(); + let result = ts.ucmd().arg("--time=ctime").arg("date_test").succeeds(); result.stdout_only("0\t2016-06-16 00:00\tdate_test\n"); #[cfg(not(target_env = "musl"))] @@ -408,7 +404,7 @@ fn test_du_time() { let re_birth = Regex::new(r"0\t[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}\tdate_test").unwrap(); - let result = scene.ucmd().arg("--time=birth").arg("date_test").succeeds(); + let result = ts.ucmd().arg("--time=birth").arg("date_test").succeeds(); result.stdout_matches(&re_birth); } } @@ -417,21 +413,21 @@ fn test_du_time() { #[cfg(feature = "chmod")] #[test] fn test_du_no_permission() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; at.mkdir_all(SUB_DIR_LINKS); - scene.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).succeeds(); + ts.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).succeeds(); - let result = scene.ucmd().arg(SUB_DIR_LINKS).run(); // TODO: replace with ".fails()" once `du` is fixed + let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); // TODO: replace with ".fails()" once `du` is fixed result.stderr_contains( "du: cannot read directory 'subdir/links': Permission denied (os error 13)", ); #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg(SUB_DIR_LINKS).fails(); + let result_reference = unwrap_or_return!(expected_result(&ts, &[SUB_DIR_LINKS])); if result_reference .stderr_str() .contains("du: cannot read directory 'subdir/links': Permission denied") @@ -455,13 +451,13 @@ fn _du_no_permission(s: &str) { #[test] fn test_du_one_file_system() { - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); - let result = scene.ucmd().arg("-x").arg(SUB_DIR).succeeds(); + let result = ts.ucmd().arg("-x").arg(SUB_DIR).succeeds(); #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg("-x").arg(SUB_DIR).run(); + let result_reference = unwrap_or_return!(expected_result(&ts, &["-x", SUB_DIR])); if result_reference.succeeded() { assert_eq!(result.stdout_str(), result_reference.stdout_str()); return; @@ -472,19 +468,17 @@ fn test_du_one_file_system() { #[test] fn test_du_threshold() { - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); let threshold = if cfg!(windows) { "7K" } else { "10K" }; - scene - .ucmd() + ts.ucmd() .arg(format!("--threshold={}", threshold)) .succeeds() .stdout_contains("links") .stdout_does_not_contain("deeper_dir"); - scene - .ucmd() + ts.ucmd() .arg(format!("--threshold=-{}", threshold)) .succeeds() .stdout_does_not_contain("links") @@ -493,12 +487,12 @@ fn test_du_threshold() { #[test] fn test_du_apparent_size() { - let scene = TestScenario::new(util_name!()); - let result = scene.ucmd().arg("--apparent-size").succeeds(); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().arg("--apparent-size").succeeds(); #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg("--apparent-size").run(); + let result_reference = unwrap_or_return!(expected_result(&ts, &["--apparent-size"])); assert_eq!(result.stdout_str(), result_reference.stdout_str()); } @@ -561,12 +555,12 @@ fn _du_apparent_size(s: &str) { #[test] fn test_du_bytes() { - let scene = TestScenario::new(util_name!()); - let result = scene.ucmd().arg("--bytes").succeeds(); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().arg("--bytes").succeeds(); #[cfg(target_os = "linux")] { - let result_reference = scene.cmd("du").arg("--bytes").run(); + let result_reference = unwrap_or_return!(expected_result(&ts, &["--bytes"])); assert_eq!(result.stdout_str(), result_reference.stdout_str()); } diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index 9bd0cd12a..73837235f 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -1,55 +1,20 @@ +// * 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. + +//spell-checker: ignore coreutil + use crate::common::util::*; -// spell-checker:ignore (ToDO) coreutil - -// These tests run the GNU coreutils `(g)groups` binary in `$PATH` in order to gather reference values. -// If the `(g)groups` in `$PATH` doesn't include a coreutils version string, -// or the version is too low, the test is skipped. - -// The reference version is 8.32. Here 8.30 was chosen because right now there's no -// ubuntu image for github action available with a higher version than 8.30. -const VERSION_MIN: &str = "8.30"; // minimum Version for the reference `groups` in $PATH const VERSION_MIN_MULTIPLE_USERS: &str = "8.31"; // this feature was introduced in GNU's coreutils 8.31 -const UUTILS_WARNING: &str = "uutils-tests-warning"; -const UUTILS_INFO: &str = "uutils-tests-info"; - -macro_rules! unwrap_or_return { - ( $e:expr ) => { - match $e { - Ok(x) => x, - Err(e) => { - println!("{}: test skipped: {}", UUTILS_INFO, e); - return; - } - } - }; -} - -fn whoami() -> String { - // Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. - // - // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" - // whoami: cannot find name for user ID 1001 - // id --name: cannot find name for user ID 1001 - // id --name: cannot find name for group ID 116 - // - // However, when running "id" from within "/bin/bash" it looks fine: - // id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)" - // whoami: "runner" - - // Use environment variable to get current user instead of - // invoking `whoami` and fall back to user "nobody" on error. - std::env::var("USER").unwrap_or_else(|e| { - println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e); - "nobody".to_string() - }) -} #[test] #[cfg(unix)] fn test_groups() { - let result = new_ucmd!().run(); - let exp_result = unwrap_or_return!(expected_result(&[])); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().run(); + let exp_result = unwrap_or_return!(expected_result(&ts, &[])); result .stdout_is(exp_result.stdout_str()) @@ -62,8 +27,9 @@ fn test_groups() { fn test_groups_username() { let test_users = [&whoami()[..]]; - let result = new_ucmd!().args(&test_users).run(); - let exp_result = unwrap_or_return!(expected_result(&test_users)); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().args(&test_users).run(); + let exp_result = unwrap_or_return!(expected_result(&ts, &test_users)); result .stdout_is(exp_result.stdout_str()) @@ -74,103 +40,18 @@ fn test_groups_username() { #[test] #[cfg(unix)] fn test_groups_username_multiple() { - // TODO: [2021-06; jhscheer] refactor this as `let util_name = host_name_for(util_name!())` when that function is added to 'tests/common' - #[cfg(target_os = "linux")] - let util_name = util_name!(); - #[cfg(all(unix, not(target_os = "linux")))] - let util_name = &format!("g{}", util_name!()); - let version_check_string = check_coreutil_version(util_name, VERSION_MIN_MULTIPLE_USERS); - if version_check_string.starts_with(UUTILS_WARNING) { - println!("{}\ntest skipped", version_check_string); - return; - } + unwrap_or_return!(check_coreutil_version( + util_name!(), + VERSION_MIN_MULTIPLE_USERS + )); let test_users = ["root", "man", "postfix", "sshd", &whoami()]; - let result = new_ucmd!().args(&test_users).run(); - let exp_result = unwrap_or_return!(expected_result(&test_users)); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().args(&test_users).run(); + let exp_result = unwrap_or_return!(expected_result(&ts, &test_users)); result .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str()) .code_is(exp_result.code()); } - -fn check_coreutil_version(util_name: &str, version_expected: &str) -> String { - // example: - // $ id --version | head -n 1 - // id (GNU coreutils) 8.32.162-4eda - let scene = TestScenario::new(util_name); - let version_check = scene - .cmd_keepenv(&util_name) - .env("LC_ALL", "C") - .arg("--version") - .run(); - version_check - .stdout_str() - .split('\n') - .collect::>() - .get(0) - .map_or_else( - || format!("{}: unexpected output format for reference coreutil: '{} --version'", UUTILS_WARNING, util_name), - |s| { - if s.contains(&format!("(GNU coreutils) {}", version_expected)) { - s.to_string() - } else if s.contains("(GNU coreutils)") { - let version_found = s.split_whitespace().last().unwrap()[..4].parse::().unwrap_or_default(); - let version_expected = version_expected.parse::().unwrap_or_default(); - if version_found > version_expected { - format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found) - } else { - format!("{}: version for the reference coreutil '{}' does not match; expected: {}, found: {}", UUTILS_WARNING, util_name, version_expected, version_found) } - } else { - format!("{}: no coreutils version string found for reference coreutils '{} --version'", UUTILS_WARNING, util_name) - } - }, - ) -} - -#[allow(clippy::needless_borrow)] -#[cfg(unix)] -fn expected_result(args: &[&str]) -> Result { - // TODO: [2021-06; jhscheer] refactor this as `let util_name = host_name_for(util_name!())` when that function is added to 'tests/common' - #[cfg(target_os = "linux")] - let util_name = util_name!(); - #[cfg(all(unix, not(target_os = "linux")))] - let util_name = &format!("g{}", util_name!()); - - let version_check_string = check_coreutil_version(util_name, VERSION_MIN); - if version_check_string.starts_with(UUTILS_WARNING) { - return Err(version_check_string); - } - println!("{}", version_check_string); - - let scene = TestScenario::new(util_name); - let result = scene - .cmd_keepenv(util_name) - .env("LC_ALL", "C") - .args(args) - .run(); - - let (stdout, stderr): (String, String) = if cfg!(target_os = "linux") { - ( - result.stdout_str().to_string(), - result.stderr_str().to_string(), - ) - } else { - // strip 'g' prefix from results: - let from = util_name.to_string() + ":"; - let to = &from[1..]; - ( - result.stdout_str().replace(&from, to), - result.stderr_str().replace(&from, to), - ) - }; - - Ok(CmdResult::new( - Some(result.tmpd()), - Some(result.code()), - result.succeeded(), - stdout.as_bytes(), - stderr.as_bytes(), - )) -} diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 8442af538..eeb12f28b 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -1,56 +1,20 @@ -use crate::common::util::*; +// * 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. // spell-checker:ignore (ToDO) coreutil -// These tests run the GNU coreutils `(g)id` binary in `$PATH` in order to gather reference values. -// If the `(g)id` in `$PATH` doesn't include a coreutils version string, -// or the version is too low, the test is skipped. +use crate::common::util::*; -// The reference version is 8.32. Here 8.30 was chosen because right now there's no -// ubuntu image for github action available with a higher version than 8.30. -const VERSION_MIN: &str = "8.30"; // minimum Version for the reference `id` in $PATH const VERSION_MIN_MULTIPLE_USERS: &str = "8.31"; // this feature was introduced in GNU's coreutils 8.31 -const UUTILS_WARNING: &str = "uutils-tests-warning"; -const UUTILS_INFO: &str = "uutils-tests-info"; - -#[allow(clippy::needless_return)] -macro_rules! unwrap_or_return { - ( $e:expr ) => { - match $e { - Ok(x) => x, - Err(e) => { - println!("{}: test skipped: {}", UUTILS_INFO, e); - return; - } - } - }; -} - -fn whoami() -> String { - // Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. - // - // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" - // whoami: cannot find name for user ID 1001 - // id --name: cannot find name for user ID 1001 - // id --name: cannot find name for group ID 116 - // - // However, when running "id" from within "/bin/bash" it looks fine: - // id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)" - // whoami: "runner" - - // Use environment variable to get current user instead of - // invoking `whoami` and fall back to user "nobody" on error. - std::env::var("USER").unwrap_or_else(|e| { - println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e); - "nobody".to_string() - }) -} #[test] #[cfg(unix)] fn test_id_no_specified_user() { - let result = new_ucmd!().run(); - let exp_result = unwrap_or_return!(expected_result(&[])); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().run(); + let exp_result = unwrap_or_return!(expected_result(&ts, &[])); let mut _exp_stdout = exp_result.stdout_str().to_string(); result @@ -64,10 +28,9 @@ fn test_id_no_specified_user() { fn test_id_single_user() { let test_users = [&whoami()[..]]; - let scene = TestScenario::new(util_name!()); - let mut exp_result = unwrap_or_return!(expected_result(&test_users)); - scene - .ucmd() + let ts = TestScenario::new(util_name!()); + let mut exp_result = unwrap_or_return!(expected_result(&ts, &test_users)); + ts.ucmd() .args(&test_users) .run() .stdout_is(exp_result.stdout_str()) @@ -78,36 +41,32 @@ fn test_id_single_user() { for &opt in &["--user", "--group", "--groups"] { let mut args = vec![opt]; args.extend_from_slice(&test_users); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--zero"); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--name"); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.pop(); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) @@ -120,8 +79,9 @@ fn test_id_single_user() { #[cfg(unix)] fn test_id_single_user_non_existing() { let args = &["hopefully_non_existing_username"]; - let result = new_ucmd!().args(args).run(); - let exp_result = unwrap_or_return!(expected_result(args)); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().args(args).run(); + let exp_result = unwrap_or_return!(expected_result(&ts, args)); // It is unknown why on macOS (and possibly others?) `id` adds "Invalid argument". // coreutils 8.32: $ LC_ALL=C id foobar @@ -136,11 +96,11 @@ fn test_id_single_user_non_existing() { #[test] #[cfg(unix)] fn test_id_name() { - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); for &opt in &["--user", "--group", "--groups"] { let args = [opt, "--name"]; - let result = scene.ucmd().args(&args).run(); - let exp_result = unwrap_or_return!(expected_result(&args)); + let result = ts.ucmd().args(&args).run(); + let exp_result = unwrap_or_return!(expected_result(&ts, &args)); result .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str()) @@ -155,11 +115,11 @@ fn test_id_name() { #[test] #[cfg(unix)] fn test_id_real() { - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); for &opt in &["--user", "--group", "--groups"] { let args = [opt, "--real"]; - let result = scene.ucmd().args(&args).run(); - let exp_result = unwrap_or_return!(expected_result(&args)); + let result = ts.ucmd().args(&args).run(); + let exp_result = unwrap_or_return!(expected_result(&ts, &args)); result .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str()) @@ -199,23 +159,17 @@ fn test_id_password_style() { #[test] #[cfg(unix)] fn test_id_multiple_users() { - #[cfg(target_os = "linux")] - let util_name = util_name!(); - #[cfg(all(unix, not(target_os = "linux")))] - let util_name = &format!("g{}", util_name!()); - let version_check_string = check_coreutil_version(util_name, VERSION_MIN_MULTIPLE_USERS); - if version_check_string.starts_with(UUTILS_WARNING) { - println!("{}\ntest skipped", version_check_string); - return; - } + unwrap_or_return!(check_coreutil_version( + util_name!(), + VERSION_MIN_MULTIPLE_USERS + )); // Same typical users that GNU test suite is using. let test_users = ["root", "man", "postfix", "sshd", &whoami()]; - let scene = TestScenario::new(util_name!()); - let mut exp_result = unwrap_or_return!(expected_result(&test_users)); - scene - .ucmd() + let ts = TestScenario::new(util_name!()); + let mut exp_result = unwrap_or_return!(expected_result(&ts, &test_users)); + ts.ucmd() .args(&test_users) .run() .stdout_is(exp_result.stdout_str()) @@ -226,36 +180,32 @@ fn test_id_multiple_users() { for &opt in &["--user", "--group", "--groups"] { let mut args = vec![opt]; args.extend_from_slice(&test_users); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--zero"); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--name"); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.pop(); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) @@ -267,15 +217,10 @@ fn test_id_multiple_users() { #[test] #[cfg(unix)] fn test_id_multiple_users_non_existing() { - #[cfg(target_os = "linux")] - let util_name = util_name!(); - #[cfg(all(unix, not(target_os = "linux")))] - let util_name = &format!("g{}", util_name!()); - let version_check_string = check_coreutil_version(util_name, VERSION_MIN_MULTIPLE_USERS); - if version_check_string.starts_with(UUTILS_WARNING) { - println!("{}\ntest skipped", version_check_string); - return; - } + unwrap_or_return!(check_coreutil_version( + util_name!(), + VERSION_MIN_MULTIPLE_USERS + )); let test_users = [ "root", @@ -290,10 +235,9 @@ fn test_id_multiple_users_non_existing() { &whoami(), ]; - let scene = TestScenario::new(util_name!()); - let mut exp_result = unwrap_or_return!(expected_result(&test_users)); - scene - .ucmd() + let ts = TestScenario::new(util_name!()); + let mut exp_result = unwrap_or_return!(expected_result(&ts, &test_users)); + ts.ucmd() .args(&test_users) .run() .stdout_is(exp_result.stdout_str()) @@ -304,36 +248,32 @@ fn test_id_multiple_users_non_existing() { for &opt in &["--user", "--group", "--groups"] { let mut args = vec![opt]; args.extend_from_slice(&test_users); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--zero"); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--name"); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.pop(); - exp_result = unwrap_or_return!(expected_result(&args)); - scene - .ucmd() + exp_result = unwrap_or_return!(expected_result(&ts, &args)); + ts.ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) @@ -345,20 +285,19 @@ fn test_id_multiple_users_non_existing() { #[test] #[cfg(unix)] fn test_id_default_format() { - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); for &opt1 in &["--name", "--real"] { // id: cannot print only names or real IDs in default format let args = [opt1]; - scene - .ucmd() + ts.ucmd() .args(&args) .fails() - .stderr_only(unwrap_or_return!(expected_result(&args)).stderr_str()); + .stderr_only(unwrap_or_return!(expected_result(&ts, &args)).stderr_str()); for &opt2 in &["--user", "--group", "--groups"] { // u/g/G n/r let args = [opt2, opt1]; - let result = scene.ucmd().args(&args).run(); - let exp_result = unwrap_or_return!(expected_result(&args)); + let result = ts.ucmd().args(&args).run(); + let exp_result = unwrap_or_return!(expected_result(&ts, &args)); result .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str()) @@ -368,38 +307,35 @@ fn test_id_default_format() { for &opt2 in &["--user", "--group", "--groups"] { // u/g/G let args = [opt2]; - scene - .ucmd() + ts.ucmd() .args(&args) .succeeds() - .stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str()); + .stdout_only(unwrap_or_return!(expected_result(&ts, &args)).stdout_str()); } } #[test] #[cfg(unix)] fn test_id_zero() { - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); for z_flag in &["-z", "--zero"] { // id: option --zero not permitted in default format - scene - .ucmd() + ts.ucmd() .args(&[z_flag]) .fails() - .stderr_only(unwrap_or_return!(expected_result(&[z_flag])).stderr_str()); + .stderr_only(unwrap_or_return!(expected_result(&ts, &[z_flag])).stderr_str()); for &opt1 in &["--name", "--real"] { // id: cannot print only names or real IDs in default format let args = [opt1, z_flag]; - scene - .ucmd() + ts.ucmd() .args(&args) .fails() - .stderr_only(unwrap_or_return!(expected_result(&args)).stderr_str()); + .stderr_only(unwrap_or_return!(expected_result(&ts, &args)).stderr_str()); for &opt2 in &["--user", "--group", "--groups"] { // u/g/G n/r z let args = [opt2, z_flag, opt1]; - let result = scene.ucmd().args(&args).run(); - let exp_result = unwrap_or_return!(expected_result(&args)); + let result = ts.ucmd().args(&args).run(); + let exp_result = unwrap_or_return!(expected_result(&ts, &args)); result .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str()) @@ -409,11 +345,10 @@ fn test_id_zero() { for &opt2 in &["--user", "--group", "--groups"] { // u/g/G z let args = [opt2, z_flag]; - scene - .ucmd() + ts.ucmd() .args(&args) .succeeds() - .stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str()); + .stdout_only(unwrap_or_return!(expected_result(&ts, &args)).stdout_str()); } } } @@ -423,46 +358,39 @@ fn test_id_zero() { fn test_id_context() { use selinux::{self, KernelSupport}; if selinux::kernel_support() == KernelSupport::Unsupported { - println!( - "{}: test skipped: Kernel has no support for SElinux context", - UUTILS_INFO - ); + println!("test skipped: Kernel has no support for SElinux context",); return; } - let scene = TestScenario::new(util_name!()); + let ts = TestScenario::new(util_name!()); for c_flag in &["-Z", "--context"] { - scene - .ucmd() + ts.ucmd() .args(&[c_flag]) .succeeds() - .stdout_only(unwrap_or_return!(expected_result(&[c_flag])).stdout_str()); + .stdout_only(unwrap_or_return!(expected_result(&ts, &[c_flag])).stdout_str()); for &z_flag in &["-z", "--zero"] { let args = [c_flag, z_flag]; - scene - .ucmd() + ts.ucmd() .args(&args) .succeeds() - .stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str()); + .stdout_only(unwrap_or_return!(expected_result(&ts, &args)).stdout_str()); for &opt1 in &["--name", "--real"] { // id: cannot print only names or real IDs in default format let args = [opt1, c_flag]; - scene - .ucmd() + ts.ucmd() .args(&args) .succeeds() - .stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str()); + .stdout_only(unwrap_or_return!(expected_result(&ts, &args)).stdout_str()); let args = [opt1, c_flag, z_flag]; - scene - .ucmd() + ts.ucmd() .args(&args) .succeeds() - .stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str()); + .stdout_only(unwrap_or_return!(expected_result(&ts, &args)).stdout_str()); for &opt2 in &["--user", "--group", "--groups"] { // u/g/G n/r z Z // for now, we print clap's standard response for "conflicts_with" instead of: // id: cannot print "only" of more than one choice let args = [opt2, c_flag, opt1]; - let _result = scene.ucmd().args(&args).fails(); + let _result = ts.ucmd().args(&args).fails(); // let exp_result = unwrap_or_return!(expected_result(&args)); // result // .stdout_is(exp_result.stdout_str()) @@ -475,7 +403,7 @@ fn test_id_context() { // for now, we print clap's standard response for "conflicts_with" instead of: // id: cannot print "only" of more than one choice let args = [opt2, c_flag]; - let _result = scene.ucmd().args(&args).fails(); + let _result = ts.ucmd().args(&args).fails(); // let exp_result = unwrap_or_return!(expected_result(&args)); // result // .stdout_is(exp_result.stdout_str()) @@ -491,100 +419,18 @@ fn test_id_context() { fn test_id_no_specified_user_posixly() { // gnu/tests/id/no-context.sh - let scene = TestScenario::new(util_name!()); - let result = scene.ucmd().env("POSIXLY_CORRECT", "1").succeeds(); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().env("POSIXLY_CORRECT", "1").succeeds(); assert!(!result.stdout_str().contains("context=")); #[cfg(target_os = "linux")] { use selinux::{self, KernelSupport}; if selinux::kernel_support() == KernelSupport::Unsupported { - println!( - "{}: test skipped: Kernel has no support for SElinux context", - UUTILS_INFO - ); + println!("test skipped: Kernel has no support for SElinux context",); } else { - let result = scene.ucmd().succeeds(); + let result = ts.ucmd().succeeds(); assert!(result.stdout_str().contains("context=")); } } } - -fn check_coreutil_version(util_name: &str, version_expected: &str) -> String { - // example: - // $ id --version | head -n 1 - // id (GNU coreutils) 8.32.162-4eda - let scene = TestScenario::new(util_name); - let version_check = scene - .cmd_keepenv(&util_name) - .env("LC_ALL", "C") - .arg("--version") - .run(); - version_check - .stdout_str() - .split('\n') - .collect::>() - .get(0) - .map_or_else( - || format!("{}: unexpected output format for reference coreutil: '{} --version'", UUTILS_WARNING, util_name), - |s| { - if s.contains(&format!("(GNU coreutils) {}", version_expected)) { - s.to_string() - } else if s.contains("(GNU coreutils)") { - let version_found = s.split_whitespace().last().unwrap()[..4].parse::().unwrap_or_default(); - let version_expected = version_expected.parse::().unwrap_or_default(); - if version_found > version_expected { - format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found) - } else { - format!("{}: version for the reference coreutil '{}' does not match; expected: {}, found: {}", UUTILS_WARNING, util_name, version_expected, version_found) } - } else { - format!("{}: no coreutils version string found for reference coreutils '{} --version'", UUTILS_WARNING, util_name) - } - }, - ) -} - -#[allow(clippy::needless_borrow)] -#[cfg(unix)] -fn expected_result(args: &[&str]) -> Result { - #[cfg(target_os = "linux")] - let util_name = util_name!(); - #[cfg(all(unix, not(target_os = "linux")))] - let util_name = &format!("g{}", util_name!()); - - let version_check_string = check_coreutil_version(util_name, VERSION_MIN); - if version_check_string.starts_with(UUTILS_WARNING) { - return Err(version_check_string); - } - println!("{}", version_check_string); - - let scene = TestScenario::new(util_name); - let result = scene - .cmd_keepenv(util_name) - .env("LC_ALL", "C") - .args(args) - .run(); - - let (stdout, stderr): (String, String) = if cfg!(target_os = "linux") { - ( - result.stdout_str().to_string(), - result.stderr_str().to_string(), - ) - } else { - // strip 'g' prefix from results: - let from = util_name.to_string() + ":"; - let to = &from[1..]; - ( - result.stdout_str().replace(&from, to), - result.stderr_str().replace(&from, to), - ) - }; - - Ok(CmdResult::new( - Some(result.tmpd()), - Some(result.code()), - result.succeeded(), - stdout.as_bytes(), - stderr.as_bytes(), - )) -} diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index bc2833a42..5394dfde9 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -1,3 +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. + extern crate uucore; use crate::common::util::*; @@ -20,19 +25,16 @@ fn test_long_format() { let login = "root"; let pw: Passwd = Passwd::locate(login).unwrap(); let real_name = pw.user_info().replace("&", &pw.name().capitalize()); - new_ucmd!() - .arg("-l") - .arg(login) - .succeeds() - .stdout_is(format!( - "Login name: {:<28}In real life: {}\nDirectory: {:<29}Shell: {}\n\n", - login, - real_name, - pw.user_dir(), - pw.user_shell() - )); + let ts = TestScenario::new(util_name!()); + ts.ucmd().arg("-l").arg(login).succeeds().stdout_is(format!( + "Login name: {:<28}In real life: {}\nDirectory: {:<29}Shell: {}\n\n", + login, + real_name, + pw.user_dir(), + pw.user_shell() + )); - new_ucmd!() + ts.ucmd() .arg("-lb") .arg(login) .succeeds() @@ -42,15 +44,18 @@ fn test_long_format() { )); } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_long_format_multiple_users() { let args = ["-l", "root", "root", "root"]; + let ts = TestScenario::new(util_name!()); + let expect = unwrap_or_return!(expected_result(&ts, &args)); - new_ucmd!() + ts.ucmd() .args(&args) .succeeds() - .stdout_is(expected_result(&args)); + .stdout_is(expect.stdout_str()) + .stderr_is(expect.stderr_str()); } #[test] @@ -59,55 +64,41 @@ fn test_long_format_wo_user() { new_ucmd!().arg("-l").fails().code_is(1); } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_short_format_i() { // allow whitespace variation // * minor whitespace differences occur between platform built-in outputs; specifically, the number of trailing TABs may be variant let args = ["-i"]; - let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); - let expect = expected_result(&args); + let ts = TestScenario::new(util_name!()); + let actual = ts.ucmd().args(&args).succeeds().stdout_move_str(); + let expect = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); let v_actual: Vec<&str> = actual.split_whitespace().collect(); let v_expect: Vec<&str> = expect.split_whitespace().collect(); assert_eq!(v_actual, v_expect); } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_short_format_q() { // allow whitespace variation // * minor whitespace differences occur between platform built-in outputs; specifically, the number of trailing TABs may be variant let args = ["-q"]; - let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); - let expect = expected_result(&args); + let ts = TestScenario::new(util_name!()); + let actual = ts.ucmd().args(&args).succeeds().stdout_move_str(); + let expect = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); let v_actual: Vec<&str> = actual.split_whitespace().collect(); let v_expect: Vec<&str> = expect.split_whitespace().collect(); assert_eq!(v_actual, v_expect); } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_no_flag() { - let actual = new_ucmd!().succeeds().stdout_move_str(); - let expect = expected_result(&[]); + let ts = TestScenario::new(util_name!()); + let actual = ts.ucmd().succeeds().stdout_move_str(); + let expect = unwrap_or_return!(expected_result(&ts, &[])).stdout_move_str(); let v_actual: Vec<&str> = actual.split_whitespace().collect(); let v_expect: Vec<&str> = expect.split_whitespace().collect(); assert_eq!(v_actual, v_expect); } - -#[cfg(any(target_vendor = "apple", target_os = "linux"))] -fn expected_result(args: &[&str]) -> String { - #[cfg(target_os = "linux")] - let util_name = util_name!(); - #[cfg(target_vendor = "apple")] - let util_name = format!("g{}", util_name!()); - - // note: clippy::needless_borrow *false positive* - #[allow(clippy::needless_borrow)] - TestScenario::new(&util_name) - .cmd_keepenv(util_name) - .env("LC_ALL", "C") - .args(args) - .succeeds() - .stdout_move_str() -} diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 1d41ddac5..4a1cc3fa9 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -951,3 +951,11 @@ fn test_conflict_check_out() { ); } } + +#[test] +fn test_key_takes_one_arg() { + new_ucmd!() + .args(&["-k", "2.3", "keys_open_ended.txt"]) + .succeeds() + .stdout_is_fixture("keys_open_ended.expected"); +} diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index ddf78815f..7cff0d89c 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -1,3 +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. + extern crate regex; use crate::common::util::*; @@ -109,30 +114,29 @@ const FS_FORMAT_STR: &str = "%b %c %i %l %n %s %S %t %T"; // avoid "%a %d %f" wh #[cfg(target_os = "linux")] fn test_terse_fs_format() { let args = ["-f", "-t", "/proc"]; - new_ucmd!() - .args(&args) - .run() - .stdout_is(expected_result(&args)); + let ts = TestScenario::new(util_name!()); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + ts.ucmd().args(&args).run().stdout_is(expected_stdout); } #[test] #[cfg(target_os = "linux")] fn test_fs_format() { let args = ["-f", "-c", FS_FORMAT_STR, "/dev/shm"]; - new_ucmd!() - .args(&args) - .run() - .stdout_is(expected_result(&args)); + let ts = TestScenario::new(util_name!()); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + ts.ucmd().args(&args).run().stdout_is(expected_stdout); } -#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[cfg(unix)] #[test] fn test_terse_normal_format() { // note: contains birth/creation date which increases test fragility // * results may vary due to built-in `stat` limitations as well as linux kernel and rust version capability variations let args = ["-t", "/"]; - let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); - let expect = expected_result(&args); + let ts = TestScenario::new(util_name!()); + let actual = ts.ucmd().args(&args).succeeds().stdout_move_str(); + let expect = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); println!("actual: {:?}", actual); println!("expect: {:?}", expect); let v_actual: Vec<&str> = actual.trim().split(' ').collect(); @@ -156,12 +160,13 @@ fn test_terse_normal_format() { ); } -#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[cfg(unix)] #[test] fn test_format_created_time() { let args = ["-c", "%w", "/bin"]; - let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); - let expect = expected_result(&args); + let ts = TestScenario::new(util_name!()); + let actual = ts.ucmd().args(&args).succeeds().stdout_move_str(); + let expect = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); println!("actual: {:?}", actual); println!("expect: {:?}", expect); // note: using a regex instead of `split_whitespace()` in order to detect whitespace differences @@ -180,12 +185,13 @@ fn test_format_created_time() { ); } -#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[cfg(unix)] #[test] fn test_format_created_seconds() { let args = ["-c", "%W", "/bin"]; - let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); - let expect = expected_result(&args); + let ts = TestScenario::new(util_name!()); + let actual = ts.ucmd().args(&args).succeeds().stdout_move_str(); + let expect = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); println!("actual: {:?}", actual); println!("expect: {:?}", expect); // note: using a regex instead of `split_whitespace()` in order to detect whitespace differences @@ -204,21 +210,20 @@ fn test_format_created_seconds() { ); } -#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[cfg(unix)] #[test] fn test_normal_format() { let args = ["-c", NORMAL_FORMAT_STR, "/bin"]; - new_ucmd!() - .args(&args) - .succeeds() - .stdout_is(expected_result(&args)); + let ts = TestScenario::new(util_name!()); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); } -#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[cfg(unix)] #[test] fn test_symlinks() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; let mut tested: bool = false; // arbitrarily chosen symlinks with hope that the CI environment provides at least one of them @@ -232,18 +237,12 @@ fn test_symlinks() { if at.file_exists(file) && at.is_symlink(file) { tested = true; let args = ["-c", NORMAL_FORMAT_STR, file]; - scene - .ucmd() - .args(&args) - .succeeds() - .stdout_is(expected_result(&args)); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); // -L, --dereference follow links let args = ["-L", "-c", NORMAL_FORMAT_STR, file]; - scene - .ucmd() - .args(&args) - .succeeds() - .stdout_is(expected_result(&args)); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); } } if !tested { @@ -269,13 +268,12 @@ fn test_char() { #[cfg(any(target_vendor = "apple"))] "/dev/ptmx", ]; - new_ucmd!() - .args(&args) - .succeeds() - .stdout_is(expected_result(&args)); + let ts = TestScenario::new(util_name!()); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); } -#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[cfg(unix)] #[test] fn test_multi_files() { let args = [ @@ -287,38 +285,19 @@ fn test_multi_files() { "/etc/fstab", "/var", ]; - new_ucmd!() - .args(&args) - .succeeds() - .stdout_is(expected_result(&args)); + let ts = TestScenario::new(util_name!()); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_printf() { let args = [ "--printf=123%-# 15q\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.23m\\x12\\167\\132\\112\\n", "/", ]; - new_ucmd!() - .args(&args) - .succeeds() - .stdout_is(expected_result(&args)); -} - -#[cfg(any(target_vendor = "apple", target_os = "linux"))] -fn expected_result(args: &[&str]) -> String { - #[cfg(target_os = "linux")] - let util_name = util_name!(); - #[cfg(target_vendor = "apple")] - let util_name = format!("g{}", util_name!()); - - // note: clippy::needless_borrow *false positive* - #[allow(clippy::needless_borrow)] - TestScenario::new(&util_name) - .cmd_keepenv(util_name) - .env("LC_ALL", "C") - .args(args) - .succeeds() - .stdout_move_str() + let ts = TestScenario::new(util_name!()); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); } diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 1867927da..79c24651a 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -718,3 +718,21 @@ fn test_bracket_syntax_missing_right_bracket() { .status_code(2) .stderr_is("[: missing ']'"); } + +#[test] +fn test_bracket_syntax_help() { + let scenario = TestScenario::new("["); + let mut ucmd = scenario.ucmd(); + + ucmd.arg("--help").succeeds().stdout_contains("USAGE:"); +} + +#[test] +fn test_bracket_syntax_version() { + let scenario = TestScenario::new("["); + let mut ucmd = scenario.ucmd(); + + ucmd.arg("--version") + .succeeds() + .stdout_matches(&r"\[ \d+\.\d+\.\d+".parse().unwrap()); +} diff --git a/tests/by-util/test_uname.rs b/tests/by-util/test_uname.rs index de3f42a6b..adcaa1072 100644 --- a/tests/by-util/test_uname.rs +++ b/tests/by-util/test_uname.rs @@ -1,5 +1,10 @@ use crate::common::util::*; +#[test] +fn test_uname() { + new_ucmd!().succeeds(); +} + #[test] fn test_uname_compatible() { new_ucmd!().arg("-a").succeeds(); @@ -45,3 +50,60 @@ fn test_uname_kernel() { #[cfg(not(target_os = "linux"))] ucmd.arg("-o").succeeds(); } + +#[test] +fn test_uname_operating_system() { + #[cfg(target_vendor = "apple")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("Darwin\n"); + #[cfg(target_os = "freebsd")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("FreeBSD\n"); + #[cfg(target_os = "fuchsia")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("Fuchsia\n"); + #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "")))] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("GNU/Linux\n"); + #[cfg(all(target_os = "linux", not(any(target_env = "gnu", target_env = ""))))] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("Linux\n"); + #[cfg(target_os = "netbsd")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("NetBSD\n"); + #[cfg(target_os = "openbsd")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("OpenBSD\n"); + #[cfg(target_os = "redox")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("Redox\n"); + #[cfg(target_os = "windows")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("Windows NT\n"); +} + +#[test] +fn test_uname_help() { + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("system information"); +} diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 9315a5956..d05715517 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -1,38 +1,42 @@ -use crate::common::util::*; +// * 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. // spell-checker:ignore (flags) runlevel mesg -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +use crate::common::util::*; + +#[cfg(unix)] #[test] fn test_count() { + let ts = TestScenario::new(util_name!()); for opt in &["-q", "--count"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_boot() { + let ts = TestScenario::new(util_name!()); for opt in &["-b", "--boot"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_heading() { + let ts = TestScenario::new(util_name!()); for opt in &["-H", "--heading"] { // allow whitespace variation // * minor whitespace differences occur between platform built-in outputs; // specifically number of TABs between "TIME" and "COMMENT" may be variant - let actual = new_ucmd!().arg(opt).succeeds().stdout_move_str(); - let expect = expected_result(&[opt]); + let actual = ts.ucmd().arg(opt).succeeds().stdout_move_str(); + let expect = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); println!("actual: {:?}", actual); println!("expect: {:?}", expect); let v_actual: Vec<&str> = actual.split_whitespace().collect(); @@ -41,76 +45,70 @@ fn test_heading() { } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_short() { + let ts = TestScenario::new(util_name!()); for opt in &["-s", "--short"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_login() { + let ts = TestScenario::new(util_name!()); for opt in &["-l", "--login"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_m() { + let ts = TestScenario::new(util_name!()); for opt in &["-m"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_process() { + let ts = TestScenario::new(util_name!()); for opt in &["-p", "--process"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } } +#[cfg(unix)] #[test] fn test_runlevel() { + let ts = TestScenario::new(util_name!()); for opt in &["-r", "--runlevel"] { - #[cfg(any(target_vendor = "apple", target_os = "linux"))] - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); #[cfg(not(target_os = "linux"))] - new_ucmd!().arg(opt).succeeds().stdout_is(""); + ts.ucmd().arg(opt).succeeds().stdout_is(""); } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_time() { + let ts = TestScenario::new(util_name!()); for opt in &["-t", "--time"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_mesg() { // -T, -w, --mesg @@ -119,22 +117,20 @@ fn test_mesg() { // same as -T // --writable // same as -T + let ts = TestScenario::new(util_name!()); for opt in &["-T", "-w", "--mesg", "--message", "--writable"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } } +#[cfg(unix)] #[test] fn test_arg1_arg2() { let args = ["am", "i"]; - - new_ucmd!() - .args(&args) - .succeeds() - .stdout_is(expected_result(&args)); + let ts = TestScenario::new(util_name!()); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); } #[test] @@ -146,12 +142,13 @@ fn test_too_many_args() { new_ucmd!().args(&args).fails().stderr_contains(EXPECTED); } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_users() { + let ts = TestScenario::new(util_name!()); for opt in &["-u", "--users"] { - let actual = new_ucmd!().arg(opt).succeeds().stdout_move_str(); - let expect = expected_result(&[opt]); + let actual = ts.ucmd().arg(opt).succeeds().stdout_move_str(); + let expect = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); println!("actual: {:?}", actual); println!("expect: {:?}", expect); @@ -170,28 +167,26 @@ fn test_users() { } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_lookup() { let opt = "--lookup"; - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let ts = TestScenario::new(util_name!()); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_dead() { + let ts = TestScenario::new(util_name!()); for opt in &["-d", "--dead"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_all_separately() { if cfg!(target_os = "macos") { @@ -201,20 +196,14 @@ fn test_all_separately() { // -a, --all same as -b -d --login -p -r -t -T -u let args = ["-b", "-d", "--login", "-p", "-r", "-t", "-T", "-u"]; - let scene = TestScenario::new(util_name!()); - scene - .ucmd() - .args(&args) - .succeeds() - .stdout_is(expected_result(&args)); - scene - .ucmd() - .arg("--all") - .succeeds() - .stdout_is(expected_result(&args)); + let ts = TestScenario::new(util_name!()); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &["--all"])).stdout_move_str(); + ts.ucmd().arg("--all").succeeds().stdout_is(expected_stdout); } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[test] fn test_all() { if cfg!(target_os = "macos") { @@ -222,27 +211,9 @@ fn test_all() { return; } + let ts = TestScenario::new(util_name!()); for opt in &["-a", "--all"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(&[opt])); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); + ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); } } - -#[cfg(any(target_vendor = "apple", target_os = "linux"))] -fn expected_result(args: &[&str]) -> String { - #[cfg(target_os = "linux")] - let util_name = util_name!(); - #[cfg(target_vendor = "apple")] - let util_name = format!("g{}", util_name!()); - - // note: clippy::needless_borrow *false positive* - #[allow(clippy::needless_borrow)] - TestScenario::new(&util_name) - .cmd_keepenv(util_name) - .env("LC_ALL", "C") - .args(args) - .succeeds() - .stdout_move_str() -} diff --git a/tests/by-util/test_whoami.rs b/tests/by-util/test_whoami.rs index a98541b2d..0afe94161 100644 --- a/tests/by-util/test_whoami.rs +++ b/tests/by-util/test_whoami.rs @@ -1,63 +1,48 @@ +// * 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. + +#[cfg(unix)] use crate::common::util::*; -// Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. -// If we are running inside the CI and "needle" is in "stderr" skipping this test is -// considered okay. If we are not inside the CI this calls assert!(result.success). -// -// From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" -// stderr: "whoami: failed to get username" -// Maybe: "adduser --uid 1001 username" can put things right? -fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool { - if !result.succeeded() { - println!("result.stdout = {}", result.stdout_str()); - println!("result.stderr = {}", result.stderr_str()); - if is_ci() && result.stderr_str().contains(needle) { - println!("test skipped:"); - return true; - } else { - result.success(); - } - } - false -} - #[test] +#[cfg(unix)] fn test_normal() { - let (_, mut ucmd) = at_and_ucmd!(); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().run(); + let exp_result = unwrap_or_return!(expected_result(&ts, &[])); - let result = ucmd.run(); - - // use std::env; - // println!("env::var(CI).is_ok() = {}", env::var("CI").is_ok()); - // for (key, value) in env::vars() { - // println!("{}: {}", key, value); - // } - - if skipping_test_is_okay(&result, "failed to get username") { - return; - } - - result.no_stderr(); - assert!(!result.stdout_str().trim().is_empty()); + result + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); } #[test] -#[cfg(not(windows))] +#[cfg(unix)] fn test_normal_compare_id() { - let scene = TestScenario::new(util_name!()); - - let result_ucmd = scene.ucmd().run(); - if skipping_test_is_okay(&result_ucmd, "failed to get username") { - return; + let ts = TestScenario::new("id"); + let id_un = unwrap_or_return!(expected_result(&ts, &["-un"])); + if id_un.succeeded() { + new_ucmd!().succeeds().stdout_is(id_un.stdout_str()); + } else if is_ci() && id_un.stderr_str().contains("cannot find name for user ID") { + println!("test skipped:"); + } else { + id_un.success(); + } +} + +#[test] +#[cfg(unix)] +fn test_normal_compare_env() { + let whoami = whoami(); + if whoami == "nobody" { + println!("test skipped:"); + return; + } else if !is_ci() { + new_ucmd!().succeeds().stdout_is(format!("{}\n", whoami)); + } else { + println!("test skipped:"); } - - let result_cmd = scene.cmd("id").arg("-un").run(); - if skipping_test_is_okay(&result_cmd, "cannot find name for user ID") { - return; - } - - assert_eq!( - result_ucmd.stdout_str().trim(), - result_cmd.stdout_str().trim() - ); } diff --git a/tests/common/macros.rs b/tests/common/macros.rs index 03d2051d0..62b8c4824 100644 --- a/tests/common/macros.rs +++ b/tests/common/macros.rs @@ -1,3 +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. + /// Platform-independent helper for constructing a PathBuf from individual elements #[macro_export] macro_rules! path_concat { @@ -66,3 +71,19 @@ macro_rules! at_and_ucmd { (ts.fixtures.clone(), ts.ucmd()) }}; } + +/// If `common::util::expected_result` returns an error, i.e. the `util` in `$PATH` doesn't +/// include a coreutils version string or the version is too low, +/// this macro can be used to automatically skip the test and print the reason. +#[macro_export] +macro_rules! unwrap_or_return { + ( $e:expr ) => { + match $e { + Ok(x) => x, + Err(e) => { + println!("test skipped: {}", e); + return; + } + } + }; +} diff --git a/tests/common/util.rs b/tests/common/util.rs index f881cff21..7a641a353 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -1,10 +1,17 @@ -//spell-checker: ignore (linux) rlimit prlimit Rlim +// * 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. + +//spell-checker: ignore (linux) rlimit prlimit Rlim coreutil #![allow(dead_code)] use pretty_assertions::assert_eq; #[cfg(target_os = "linux")] use rlimit::{prlimit, rlim}; +#[cfg(unix)] +use std::borrow::Cow; use std::env; #[cfg(not(windows))] use std::ffi::CString; @@ -695,7 +702,7 @@ impl AtPath { /// Fixtures can be found under `tests/fixtures/$util_name/` pub struct TestScenario { bin_path: PathBuf, - util_name: String, + pub util_name: String, pub fixtures: AtPath, tmpd: Rc, } @@ -1036,6 +1043,179 @@ pub fn vec_of_size(n: usize) -> Vec { result } +pub fn whoami() -> String { + // Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. + // + // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" + // whoami: cannot find name for user ID 1001 + // id --name: cannot find name for user ID 1001 + // id --name: cannot find name for group ID 116 + // + // However, when running "id" from within "/bin/bash" it looks fine: + // id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)" + // whoami: "runner" + + // Use environment variable to get current user instead of + // invoking `whoami` and fall back to user "nobody" on error. + std::env::var("USER").unwrap_or_else(|e| { + println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e); + "nobody".to_string() + }) +} + +/// Add prefix 'g' for `util_name` if not on linux +#[cfg(unix)] +pub fn host_name_for<'a>(util_name: &'a str) -> Cow<'a, str> { + // In some environments, e.g. macOS/freebsd, the GNU coreutils are prefixed with "g" + // to not interfere with the BSD counterparts already in `$PATH`. + #[cfg(not(target_os = "linux"))] + return format!("g{}", util_name).into(); + #[cfg(target_os = "linux")] + return util_name.into(); +} + +// GNU coreutils version 8.32 is the reference version since it is the latest version and the +// GNU test suite in "coreutils/.github/workflows/GnuTests.yml" runs against it. +// However, here 8.30 was chosen because right now there's no ubuntu image for the github actions +// CICD available with a higher version than 8.30. +// GNU coreutils versions from the CICD images for comparison: +// ubuntu-2004: 8.30 (latest) +// ubuntu-1804: 8.28 +// macos-latest: 8.32 +const VERSION_MIN: &str = "8.30"; // minimum Version for the reference `coreutil` in `$PATH` + +const UUTILS_WARNING: &str = "uutils-tests-warning"; +const UUTILS_INFO: &str = "uutils-tests-info"; + +/// Run `util_name --version` and return Ok if the version is >= `version_expected`. +/// Returns an error if +/// * `util_name` cannot run +/// * the version cannot be parsed +/// * the version is too low +/// +/// This is used by `expected_result` to check if the coreutils version is >= `VERSION_MIN`. +/// It makes sense to use this manually in a test if a feature +/// is tested that was introduced after `VERSION_MIN` +/// +/// Example: +/// +/// ```no_run +/// use crate::common::util::*; +/// const VERSION_MIN_MULTIPLE_USERS: &str = "8.31"; +/// +/// #[test] +/// fn test_xyz() { +/// unwrap_or_return!(check_coreutil_version( +/// util_name!(), +/// VERSION_MIN_MULTIPLE_USERS +/// )); +/// // proceed with the test... +/// } +/// ``` +#[cfg(unix)] +pub fn check_coreutil_version( + util_name: &str, + version_expected: &str, +) -> std::result::Result { + // example: + // $ id --version | head -n 1 + // id (GNU coreutils) 8.32.162-4eda + + let util_name = &host_name_for(util_name); + log_info("run", format!("{} --version", util_name)); + let version_check = match Command::new(util_name.as_ref()) + .env("LC_ALL", "C") + .arg("--version") + .output() + { + Ok(s) => s, + Err(e) => { + return Err(format!( + "{}: '{}' {}", + UUTILS_WARNING, + util_name, + e.to_string() + )) + } + }; + std::str::from_utf8(&version_check.stdout).unwrap() + .split('\n') + .collect::>() + .get(0) + .map_or_else( + || Err(format!("{}: unexpected output format for reference coreutil: '{} --version'", UUTILS_WARNING, util_name)), + |s| { + if s.contains(&format!("(GNU coreutils) {}", version_expected)) { + Ok(format!("{}: {}", UUTILS_INFO, s.to_string())) + } else if s.contains("(GNU coreutils)") { + let version_found = s.split_whitespace().last().unwrap()[..4].parse::().unwrap_or_default(); + let version_expected = version_expected.parse::().unwrap_or_default(); + if version_found > version_expected { + Ok(format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found)) + } else { + Err(format!("{}: version for the reference coreutil '{}' does not match; expected: {}, found: {}", UUTILS_WARNING, util_name, version_expected, version_found)) } + } else { + Err(format!("{}: no coreutils version string found for reference coreutils '{} --version'", UUTILS_WARNING, util_name)) + } + }, + ) +} + +/// This runs the GNU coreutils `util_name` binary in `$PATH` in order to +/// dynamically gather reference values on the system. +/// If the `util_name` in `$PATH` doesn't include a coreutils version string, +/// or the version is too low, this returns an error and the test should be skipped. +/// +/// Example: +/// +/// ```no_run +/// use crate::common::util::*; +/// #[test] +/// fn test_xyz() { +/// let ts = TestScenario::new(util_name!()); +/// let result = ts.ucmd().run(); +/// let exp_result = unwrap_or_return!(expected_result(&ts, &[])); +/// result +/// .stdout_is(exp_result.stdout_str()) +/// .stderr_is(exp_result.stderr_str()) +/// .code_is(exp_result.code()); +/// } +///``` +#[cfg(unix)] +pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result { + let util_name = &host_name_for(&ts.util_name); + println!("{}", check_coreutil_version(util_name, VERSION_MIN)?); + + let result = ts + .cmd_keepenv(util_name.as_ref()) + .env("LC_ALL", "C") + .args(args) + .run(); + + let (stdout, stderr): (String, String) = if cfg!(target_os = "linux") { + ( + result.stdout_str().to_string(), + result.stderr_str().to_string(), + ) + } else { + // `host_name_for` added prefix, strip 'g' prefix from results: + let from = util_name.to_string() + ":"; + let to = &from[1..]; + ( + result.stdout_str().replace(&from, to), + result.stderr_str().replace(&from, to), + ) + }; + + Ok(CmdResult::new( + Some(result.tmpd()), + Some(result.code()), + result.succeeded(), + stdout.as_bytes(), + stderr.as_bytes(), + )) +} + /// Sanity checks for test utils #[cfg(test)] mod tests { @@ -1272,4 +1452,33 @@ mod tests { res.normalized_newlines_stdout_is("A\r\nB\nC\n"); } + + #[test] + #[cfg(unix)] + fn test_check_coreutil_version() { + match check_coreutil_version("id", VERSION_MIN) { + Ok(s) => assert!(s.starts_with("uutils-tests-")), + Err(s) => assert!(s.starts_with("uutils-tests-warning")), + }; + #[cfg(target_os = "linux")] + std::assert_eq!( + check_coreutil_version("no test name", VERSION_MIN), + Err("uutils-tests-warning: 'no test name' \ + No such file or directory (os error 2)" + .to_string()) + ); + } + + #[test] + #[cfg(unix)] + fn test_expected_result() { + let ts = TestScenario::new("id"); + // assert!(expected_result(&ts, &[]).is_ok()); + match expected_result(&ts, &[]) { + Ok(r) => assert!(r.succeeded()), + Err(s) => assert!(s.starts_with("uutils-tests-warning")), + } + let ts = TestScenario::new("no test name"); + assert!(expected_result(&ts, &[]).is_err()); + } }