From 4dd88680eb2d5828533ce774263c4dc16b326b4d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 23 Mar 2023 18:15:05 +0100 Subject: [PATCH 1/2] tests: define default env vars for test commands We now run each command with TZ=UTC and LC_ALL=C to ensure consistent behavior independent from external timezone and locale settings. These can still be overridden with other values if necessary. --- tests/by-util/test_pr.rs | 18 +++++++----------- tests/by-util/test_touch.rs | 7 +------ tests/common/util.rs | 25 +++++++++++++++++++++---- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index 59d3c6c64..66f4f1309 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -18,11 +18,7 @@ fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String { i.modified() .map(|x| { let date_time: OffsetDateTime = x.into(); - let offset = OffsetDateTime::now_local().unwrap().offset(); - date_time - .to_offset(offset) - .format(&DATE_TIME_FORMAT) - .unwrap() + date_time.format(&DATE_TIME_FORMAT).unwrap() }) .unwrap_or_default() }) @@ -42,7 +38,7 @@ fn all_minutes(from: OffsetDateTime, to: OffsetDateTime) -> Vec { } fn valid_last_modified_template_vars(from: OffsetDateTime) -> Vec> { - all_minutes(from, OffsetDateTime::now_local().unwrap()) + all_minutes(from, OffsetDateTime::now_utc()) .into_iter() .map(|time| vec![("{last_modified_time}".to_string(), time)]) .collect() @@ -258,7 +254,7 @@ fn test_with_suppress_error_option() { fn test_with_stdin() { let expected_file_path = "stdin.log.expected"; let mut scenario = new_ucmd!(); - let start = OffsetDateTime::now_local().unwrap(); + let start = OffsetDateTime::now_utc(); scenario .pipe_in_fixture("stdin.log") .args(&["--pages=1:2", "-n", "-"]) @@ -321,7 +317,7 @@ fn test_with_mpr() { let expected_test_file_path = "mpr.log.expected"; let expected_test_file_path1 = "mpr1.log.expected"; let expected_test_file_path2 = "mpr2.log.expected"; - let start = OffsetDateTime::now_local().unwrap(); + let start = OffsetDateTime::now_utc(); new_ucmd!() .args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1]) .succeeds() @@ -330,7 +326,7 @@ fn test_with_mpr() { &valid_last_modified_template_vars(start), ); - let start = OffsetDateTime::now_local().unwrap(); + let start = OffsetDateTime::now_utc(); new_ucmd!() .args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1]) .succeeds() @@ -339,7 +335,7 @@ fn test_with_mpr() { &valid_last_modified_template_vars(start), ); - let start = OffsetDateTime::now_local().unwrap(); + let start = OffsetDateTime::now_utc(); new_ucmd!() .args(&[ "--pages=1:2", @@ -446,7 +442,7 @@ fn test_with_join_lines_option() { let test_file_2 = "test.log"; let expected_file_path = "joined.log.expected"; let mut scenario = new_ucmd!(); - let start = OffsetDateTime::now_local().unwrap(); + let start = OffsetDateTime::now_utc(); scenario .args(&["+1:2", "-J", "-m", test_file_1, test_file_2]) .run() diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index a0d3fd3dc..6267e018e 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -49,12 +49,7 @@ fn str_to_filetime(format: &str, s: &str) -> FileTime { _ => panic!("unexpected dt format"), }; let tm = time::PrimitiveDateTime::parse(s, &format_description).unwrap(); - let d = match time::OffsetDateTime::now_local() { - Ok(now) => now, - Err(e) => { - panic!("Error {e} retrieving the OffsetDateTime::now_local"); - } - }; + let d = time::OffsetDateTime::now_utc(); let offset_dt = tm.assume_offset(d.offset()); FileTime::from_unix_time(offset_dt.unix_timestamp(), tm.nanosecond()) } diff --git a/tests/common/util.rs b/tests/common/util.rs index 4005c87d9..04876bcd3 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -48,6 +48,9 @@ static NO_STDIN_MEANINGLESS: &str = "Setting this flag has no effect if there is pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_coreutils"); +/// Default environment variables to run the commands with +const DEFAULT_ENV: [(&str, &str); 2] = [("LC_ALL", "C"), ("TZ", "UTC")]; + /// Test if the program is running under CI pub fn is_ci() -> bool { std::env::var("CI") @@ -1340,6 +1343,18 @@ impl UCommand { self } + pub fn envs(&mut self, iter: I) -> &mut Self + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + for (k, v) in iter { + self.env(k, v); + } + self + } + #[cfg(any(target_os = "linux", target_os = "android"))] pub fn limit( &mut self, @@ -1454,7 +1469,9 @@ impl UCommand { } } - command.envs(self.env_vars.iter().cloned()); + command + .envs(DEFAULT_ENV) + .envs(self.env_vars.iter().cloned()); if self.timeout.is_none() { self.timeout = Some(Duration::from_secs(30)); @@ -2456,7 +2473,7 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result< let result = ts .cmd(util_name.as_ref()) .keep_env() - .env("LC_ALL", "C") + .envs(DEFAULT_ENV) .args(args) .run(); @@ -2521,7 +2538,7 @@ pub fn run_ucmd_as_root( // check if we can run 'sudo' log_info("run", "sudo -E --non-interactive whoami"); match Command::new("sudo") - .env("LC_ALL", "C") + .envs(DEFAULT_ENV) .args(["-E", "--non-interactive", "whoami"]) .output() { @@ -2531,7 +2548,7 @@ pub fn run_ucmd_as_root( Ok(ts .cmd("sudo") .keep_env() - .env("LC_ALL", "C") + .envs(DEFAULT_ENV) .arg("-E") .arg("--non-interactive") .arg(&ts.bin_path) From c8ece3e9726a7cbc54da8e7ac77bc5c33866c9a2 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 23 Mar 2023 18:16:57 +0100 Subject: [PATCH 2/2] test_touch: test a specific time with DST problems Use a specific time to check whether any DST problems arise. The test is also ignored, because this functionality seems to be broken at the moment. Still, this is an improvement, because it is more reliable and does not need to obtain a local offset, which might lead to a panic in the time library. --- tests/by-util/test_touch.rs | 62 +++++++------------------------------ 1 file changed, 11 insertions(+), 51 deletions(-) diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 6267e018e..0c0a0b042 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -10,7 +10,7 @@ extern crate touch; use self::touch::filetime::{self, FileTime}; extern crate time; -use time::macros::{datetime, format_description}; +use time::macros::format_description; use crate::common::util::{AtPath, TestScenario}; use std::fs::remove_file; @@ -625,61 +625,21 @@ fn test_touch_mtime_dst_succeeds() { assert_eq!(target_time, mtime); } -// // is_dst_switch_hour returns true if timespec ts is just before the switch -// // to Daylight Saving Time. -// // For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 } -// // for March 8 2020 01:00:00 AM -// // is just before the switch because on that day clock jumps by 1 hour, -// // so 1 minute after 01:59:00 is 03:00:00. -// fn is_dst_switch_hour(ts: time::Timespec) -> bool { -// let ts_after = ts + time::Duration::hours(1); -// let tm = time::at(ts); -// let tm_after = time::at(ts_after); -// tm_after.tm_hour == tm.tm_hour + 2 -// } - -// get_dst_switch_hour returns date string for which touch -m -t fails. -// For example, in EST (UTC-5), that will be "202003080200" so -// touch -m -t 202003080200 file -// fails (that date/time does not exist). -// In other locales it will be a different date/time, and in some locales -// it doesn't exist at all, in which case this function will return None. -fn get_dst_switch_hour() -> Option { - //let now = time::OffsetDateTime::now_local().unwrap(); - let now = match time::OffsetDateTime::now_local() { - Ok(now) => now, - Err(e) => { - panic!("Error {e} retrieving the OffsetDateTime::now_local"); - } - }; - - // Start from January 1, 2020, 00:00. - let tm = datetime!(2020-01-01 00:00 UTC); - tm.to_offset(now.offset()); - - // let mut ts = tm.to_timespec(); - // // Loop through all hours in year 2020 until we find the hour just - // // before the switch to DST. - // for _i in 0..(366 * 24) { - // // if is_dst_switch_hour(ts) { - // // let mut tm = time::at(ts); - // // tm.tm_hour += 1; - // // let s = time::strftime("%Y%m%d%H%M", &tm).unwrap(); - // // return Some(s); - // // } - // ts = ts + time::Duration::hours(1); - // } - None -} - #[test] +#[ignore = "not implemented"] fn test_touch_mtime_dst_fails() { let (_at, mut ucmd) = at_and_ucmd!(); let file = "test_touch_set_mtime_dst_fails"; - if let Some(s) = get_dst_switch_hour() { - ucmd.args(&["-m", "-t", &s, file]).fails(); - } + // Some timezones use daylight savings time, this leads to problems if the + // specified time is within the jump forward. In EST (UTC-5), there is a + // jump from 1:59AM to 3:00AM on, March 8 2020, so any thing in-between is + // invalid. + // See https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + // for information on the TZ variable, which where the string is copied from. + ucmd.env("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2") + .args(&["-m", "-t", "202003080200", file]) + .fails(); } #[test]