Skip to content

Commit 0554a20

Browse files
authored
tests(ls): Add GNU-compat TIME_STYLE tests (#8796)
* tests(ls): Add GNU-compat TIME_STYLE tests (#4627) Add comprehensive test coverage for ls TIME_STYLE behavior to align with GNU coreutils: - test_ls_time_style_env_full_iso: TIME_STYLE environment variable with full-iso format - test_ls_time_style_iso_recent_and_older: --time-style=iso formatting for recent vs older files - test_ls_time_style_posix_locale_override: Locale-based fallback with LC_ALL=POSIX - test_ls_time_style_precedence_last_wins: Precedence between --full-time and --time-style - test_ls_time_sort_without_long: Time-based sorting without -l flag These tests directly address remaining gaps highlighted in #4627 for tests/ls/ls-time.sh from the GNU test suite. * tests(ls): use set_modified instead of touch; simplify assertions - Replace #[cfg(feature = "touch")] with set_modified(UNIX_EPOCH) in test_ls_time_style_iso_recent_and_older - Remove conditional blocks in test_ls_time_sort_without_long and set mtimes via set_modified - Simplify assertion to assert_ne!(def, t) to compare full outputs Rationale: Improves determinism, portability, and reduces branching as suggested by maintainer cakebaker in PR review comments.
1 parent b4f8eac commit 0554a20

File tree

2 files changed

+150
-1
lines changed

2 files changed

+150
-1
lines changed

.vscode/cspell.dictionaries/workspace.wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ ENOSYS
128128
ENOTEMPTY
129129
EOPNOTSUPP
130130
EPERM
131+
EPIPE
131132
EROFS
132133

133134
# * vars/fcntl

tests/by-util/test_ls.rs

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
5-
// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs mdir COLORTERM mexe bcdef mfoo
5+
// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs mdir COLORTERM mexe bcdef mfoo timefile
66
// spell-checker:ignore (words) fakeroot setcap drwxr bcdlps
77
#![allow(
88
clippy::similar_names,
@@ -6163,3 +6163,151 @@ fn ls_emoji_alignment() {
61636163
.stdout_contains("💐")
61646164
.stdout_contains("漢");
61656165
}
6166+
6167+
// Additional tests for TIME_STYLE and time sorting compatibility with GNU
6168+
#[test]
6169+
fn test_ls_time_style_env_full_iso() {
6170+
let scene = TestScenario::new(util_name!());
6171+
let at = &scene.fixtures;
6172+
at.touch("t1");
6173+
6174+
let out = scene
6175+
.ucmd()
6176+
.env("TZ", "UTC")
6177+
.env("TIME_STYLE", "full-iso")
6178+
.arg("-l")
6179+
.arg("t1")
6180+
.succeeds();
6181+
6182+
// Expect an ISO-like timestamp in output (YYYY-MM-DD HH:MM)
6183+
let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}").unwrap();
6184+
assert!(
6185+
re.is_match(out.stdout_str()),
6186+
"unexpected timestamp: {}",
6187+
out.stdout_str()
6188+
);
6189+
}
6190+
6191+
#[test]
6192+
fn test_ls_time_style_iso_recent_and_older() {
6193+
let scene = TestScenario::new(util_name!());
6194+
let at = &scene.fixtures;
6195+
// Recent file (now)
6196+
at.touch("recent");
6197+
6198+
// Recent format for --time-style=iso is %m-%d %H:%M
6199+
let recent = scene
6200+
.ucmd()
6201+
.env("TZ", "UTC")
6202+
.arg("-l")
6203+
.arg("--time-style=iso")
6204+
.arg("recent")
6205+
.succeeds();
6206+
let re_recent = Regex::new(r"(^|\n).*\d{2}-\d{2} \d{2}:\d{2} ").unwrap();
6207+
assert!(
6208+
re_recent.is_match(recent.stdout_str()),
6209+
"recent not matched: {}",
6210+
recent.stdout_str()
6211+
);
6212+
6213+
// Older format appends a full ISO date padded (year present)
6214+
let f = at.make_file("older");
6215+
f.set_modified(std::time::UNIX_EPOCH).unwrap();
6216+
6217+
let older = scene
6218+
.ucmd()
6219+
.arg("-l")
6220+
.arg("--time-style=iso")
6221+
.arg("older")
6222+
.succeeds();
6223+
let re_older = Regex::new(r"(^|\n).*\d{4}-\d{2}-\d{2} +").unwrap();
6224+
assert!(
6225+
re_older.is_match(older.stdout_str()),
6226+
"older not matched: {}",
6227+
older.stdout_str()
6228+
);
6229+
}
6230+
6231+
#[test]
6232+
fn test_ls_time_style_posix_locale_override() {
6233+
let scene = TestScenario::new(util_name!());
6234+
let at = &scene.fixtures;
6235+
at.touch("p1");
6236+
6237+
// With LC_ALL=POSIX and TIME_STYLE=posix-full-iso, GNU falls back to locale format like "%b %e %H:%M"
6238+
let out = scene
6239+
.ucmd()
6240+
.env("TZ", "UTC")
6241+
.env("LC_ALL", "POSIX")
6242+
.env("TIME_STYLE", "posix-full-iso")
6243+
.arg("-l")
6244+
.arg("p1")
6245+
.succeeds();
6246+
// Expect month name rather than ISO dashes
6247+
let re_locale = Regex::new(r" [A-Z][a-z]{2} +\d{1,2} +\d{2}:\d{2} ").unwrap();
6248+
assert!(
6249+
re_locale.is_match(out.stdout_str()),
6250+
"locale format not matched: {}",
6251+
out.stdout_str()
6252+
);
6253+
}
6254+
6255+
#[test]
6256+
fn test_ls_time_style_precedence_last_wins() {
6257+
let scene = TestScenario::new(util_name!());
6258+
let at = &scene.fixtures;
6259+
at.touch("timefile");
6260+
6261+
// time-style first, full-time last -> expect full-iso-like (seconds)
6262+
let out1 = scene
6263+
.ucmd()
6264+
.arg("--time-style=long-iso")
6265+
.arg("--full-time")
6266+
.arg("-l")
6267+
.arg("timefile")
6268+
.succeeds();
6269+
let has_seconds = Regex::new(r"\d{2}:\d{2}:\d{2}")
6270+
.unwrap()
6271+
.is_match(out1.stdout_str());
6272+
assert!(
6273+
has_seconds,
6274+
"expected seconds in full-time: {}",
6275+
out1.stdout_str()
6276+
);
6277+
6278+
// full-time first, time-style last -> expect style override (no seconds for long-iso)
6279+
let out2 = scene
6280+
.ucmd()
6281+
.arg("--full-time")
6282+
.arg("--time-style=long-iso")
6283+
.arg("-l")
6284+
.arg("timefile")
6285+
.succeeds();
6286+
let no_seconds = !Regex::new(r"\d{2}:\d{2}:\d{2}")
6287+
.unwrap()
6288+
.is_match(out2.stdout_str());
6289+
assert!(
6290+
no_seconds,
6291+
"expected no seconds in long-iso: {}",
6292+
out2.stdout_str()
6293+
);
6294+
}
6295+
6296+
#[test]
6297+
fn test_ls_time_sort_without_long() {
6298+
let scene = TestScenario::new(util_name!());
6299+
6300+
// Create two files with deterministic, distinct modification times
6301+
let at = &scene.fixtures;
6302+
let f = at.make_file("a");
6303+
f.set_modified(std::time::UNIX_EPOCH).unwrap();
6304+
at.touch("b");
6305+
6306+
// Compare default (name order) vs time-sorted (-t) order; they should differ
6307+
let default_out = scene.ucmd().succeeds();
6308+
let t_out = scene.ucmd().arg("-t").succeeds();
6309+
6310+
let def = default_out.stdout_str();
6311+
let t = t_out.stdout_str();
6312+
assert_ne!(def, t);
6313+
}

0 commit comments

Comments
 (0)