Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/uu/who/src/platform/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,20 @@ fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> {
}

fn time_string(ut: &UtmpxRecord) -> String {
// "%b %e %H:%M"
let time_format: Vec<time::format_description::FormatItem> =
let lc_time = std::env::var("LC_ALL")
.or_else(|_| std::env::var("LC_TIME"))
.or_else(|_| std::env::var("LANG"))
.unwrap_or_default();

let time_format: Vec<time::format_description::FormatItem> = if lc_time == "C" {
// "%b %e %H:%M"
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
.unwrap();
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
.unwrap()
} else {
// "%Y-%m-%d %H:%M"
time::format_description::parse("[year]-[month]-[day] [hour]:[minute]").unwrap()
};
Comment on lines +171 to +178
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably outside the scope of this PR: GNU who also uses the C format for invalid/non-installed locales:

$ LC_ALL=invalid who -b
         system boot  Sep  1 13:24
$ LC_ALL=fr_FR.UTF-8 who -b
         system boot  Sep  1 13:24

ut.login_time().format(&time_format).unwrap()
}

#[inline]
Expand Down
27 changes: 26 additions & 1 deletion tests/by-util/test_who.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

use uutests::new_ucmd;
use uutests::unwrap_or_return;
use uutests::util::{TestScenario, expected_result};
use uutests::util::{TestScenario, expected_result, gnu_cmd_result};
use uutests::util_name;
#[test]
fn test_invalid_arg() {
Expand Down Expand Up @@ -250,3 +250,28 @@ fn test_all() {
ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout);
}
}

#[cfg(unix)]
#[test]
#[ignore = "issue #3219"]
fn test_locale() {
let ts = TestScenario::new(util_name!());

let expected_stdout =
unwrap_or_return!(gnu_cmd_result(&ts, &[], &[("LC_ALL", "C")])).stdout_move_str();
ts.ucmd()
.env("LC_ALL", "C")
.succeeds()
.stdout_is(&expected_stdout);

let expected_stdout =
unwrap_or_return!(gnu_cmd_result(&ts, &[], &[("LC_ALL", "en_US.UTF-8")])).stdout_move_str();
ts.ucmd()
.env("LC_ALL", "C")
.succeeds()
.stdout_str_check(|s| s != expected_stdout);
ts.ucmd()
.env("LC_ALL", "en_US.UTF-8")
.succeeds()
.stdout_is(&expected_stdout);
}
37 changes: 36 additions & 1 deletion tests/uutests/src/lib/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3003,6 +3003,11 @@ fn parse_coreutil_version(version_string: &str) -> f32 {
/// 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.
///
/// Arguments:
/// - `ts`: The test context.
/// - `args`: Command-line variables applied to the command.
/// - `envs`: Environment variables applied to the command invocation.
///
/// Example:
///
/// ```no_run
Expand All @@ -3019,7 +3024,11 @@ fn parse_coreutil_version(version_string: &str) -> f32 {
/// }
///```
#[cfg(unix)]
pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<CmdResult, String> {
pub fn gnu_cmd_result(
ts: &TestScenario,
args: &[&str],
envs: &[(&str, &str)],
) -> std::result::Result<CmdResult, String> {
let util_name = ts.util_name.as_str();
println!("{}", check_coreutil_version(util_name, VERSION_MIN)?);
let util_name = host_name_for(util_name);
Expand All @@ -3028,6 +3037,7 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
.cmd(util_name.as_ref())
.env("PATH", PATH)
.envs(DEFAULT_ENV)
.envs(envs.iter().copied())
.args(args)
.run();

Expand Down Expand Up @@ -3056,6 +3066,31 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
))
}

/// 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 uutests::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<CmdResult, String> {
gnu_cmd_result(ts, args, &[])
}

/// This is a convenience wrapper to run a ucmd with root permissions.
/// It can be used to test programs when being root is needed
/// This runs `sudo -E --non-interactive target/debug/coreutils util_name args`
Expand Down
Loading