diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index e7ce52650..4a5a537e5 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -3,7 +3,7 @@ name = "uu_groups" version = "0.0.6" authors = ["uutils developers"] license = "MIT" -description = "groups ~ (uutils) print the groups a user is in" +description = "groups ~ (uutils) display group memberships for USERNAME" homepage = "https://github.com/uutils/coreutils" repository = "https://github.com/uutils/coreutils/tree/master/src/uu/groups" diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 22e7b8918..6585f3d16 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -7,7 +7,7 @@ // file that was distributed with this source code. // // ============================================================================ -// Testsuite summary for GNU coreutils 8.32.162-4eda +// Test suite summary for GNU coreutils 8.32.162-4eda // ============================================================================ // PASS: tests/misc/groups-dash.sh // PASS: tests/misc/groups-process-all.sh @@ -52,7 +52,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - let mut exit_code = 1; + let mut exit_code = 0; if users.is_empty() { println!( diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index c1b98aea1..9bd0cd12a 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -1,56 +1,176 @@ 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() { - if !is_ci() { - new_ucmd!().succeeds().stdout_is(expected_result(&[])); - } else { - // TODO: investigate how this could be tested in CI - // stderr = groups: cannot find name for group ID 116 - println!("test skipped:"); - } + let result = new_ucmd!().run(); + let exp_result = unwrap_or_return!(expected_result(&[])); + + result + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); } #[test] #[cfg(unix)] -#[ignore = "fixme: 'groups USERNAME' needs more debugging"] fn test_groups_username() { - let scene = TestScenario::new(util_name!()); - let whoami_result = scene.cmd("whoami").run(); + let test_users = [&whoami()[..]]; - let username = if whoami_result.succeeded() { - whoami_result.stdout_move_str() - } else if is_ci() { - String::from("docker") - } else { - println!("test skipped:"); + let result = new_ucmd!().args(&test_users).run(); + let exp_result = unwrap_or_return!(expected_result(&test_users)); + + result + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); +} + +#[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; + } + 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)); + + 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), + ) }; - // TODO: stdout should be in the form: "username : group1 group2 group3" - - scene - .ucmd() - .arg(&username) - .succeeds() - .stdout_is(expected_result(&[&username])); -} - -#[cfg(unix)] -fn expected_result(args: &[&str]) -> String { - // We want to use GNU id. On most linux systems, this is "id", but on - // bsd-like systems (e.g. FreeBSD, MacOS), it is commonly "gid". - #[cfg(any(target_os = "linux"))] - let util_name = "id"; - #[cfg(not(target_os = "linux"))] - let util_name = "gid"; - - TestScenario::new(util_name) - .cmd_keepenv(util_name) - .env("LANGUAGE", "C") - .args(args) - .args(&["-Gn"]) - .succeeds() - .stdout_move_str() + Ok(CmdResult::new( + Some(result.tmpd()), + Some(result.code()), + result.succeeded(), + stdout.as_bytes(), + stderr.as_bytes(), + )) }