From e502d894dc6534f77b94f89abbec550b8dc6c892 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 12 Nov 2025 16:18:18 +0100 Subject: [PATCH] test: existing file is newer than non-existing existing -nt non-existing => true non-existing -nt existing => false existing -ot non-existing => false non-existing -ot existing => true --- Cargo.lock | 1 + src/uu/test/Cargo.toml | 9 ++-- src/uu/test/src/test.rs | 107 +++++++++++++++++++++++++++++++------ tests/by-util/test_test.rs | 30 +++++++---- 4 files changed, 118 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2f8412cc..fa3a3018e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4104,6 +4104,7 @@ dependencies = [ "clap", "fluent", "libc", + "tempfile", "thiserror 2.0.17", "uucore", ] diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index 1785063a0..3348efd7b 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -19,10 +19,13 @@ path = "src/test.rs" [dependencies] clap = { workspace = true } -libc = { workspace = true } -uucore = { workspace = true, features = ["process"] } -thiserror = { workspace = true } fluent = { workspace = true } +libc = { workspace = true } +thiserror = { workspace = true } +uucore = { workspace = true, features = ["process"] } + +[dev-dependencies] +tempfile = { workspace = true } [[bin]] name = "test" diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 34d594241..0e4e809d7 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -205,24 +205,26 @@ fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> ParseResult { /// Operations to compare files metadata /// `a` is the left hand side -/// `b` is the left hand side +/// `b` is the right hand side /// `op` the operation (ex: -ef, -nt, etc) fn files(a: &OsStr, b: &OsStr, op: &OsStr) -> ParseResult { - // Don't manage the error. GNU doesn't show error when doing - // test foo -nt bar - let (Ok(f_a), Ok(f_b)) = (fs::metadata(a), fs::metadata(b)) else { - return Ok(false); + let f_a = fs::metadata(a); + let f_b = fs::metadata(b); + + let result = match (op.to_str(), f_a, f_b) { + #[cfg(unix)] + (Some("-ef"), Ok(f_a), Ok(f_b)) => f_a.ino() == f_b.ino() && f_a.dev() == f_b.dev(), + #[cfg(not(unix))] + (Some("-ef"), Ok(_), Ok(_)) => unimplemented!(), + (Some("-nt"), Ok(f_a), Ok(f_b)) => f_a.modified().unwrap() > f_b.modified().unwrap(), + (Some("-nt"), Ok(_), _) => true, + (Some("-ot"), Ok(f_a), Ok(f_b)) => f_a.modified().unwrap() < f_b.modified().unwrap(), + (Some("-ot"), _, Ok(_)) => true, + (Some("-ef" | "-nt" | "-ot"), _, _) => false, + (_, _, _) => return Err(ParseError::UnknownOperator(op.quote().to_string())), }; - Ok(match op.to_str() { - #[cfg(unix)] - Some("-ef") => f_a.ino() == f_b.ino() && f_a.dev() == f_b.dev(), - #[cfg(not(unix))] - Some("-ef") => unimplemented!(), - Some("-nt") => f_a.modified().unwrap() > f_b.modified().unwrap(), - Some("-ot") => f_a.modified().unwrap() < f_b.modified().unwrap(), - _ => return Err(ParseError::UnknownOperator(op.quote().to_string())), - }) + Ok(result) } fn isatty(fd: &OsStr) -> ParseResult { @@ -347,8 +349,81 @@ fn path(path: &OsStr, condition: &PathCondition) -> bool { #[cfg(test)] mod tests { - use super::integers; - use std::ffi::OsStr; + use super::*; + use std::{ffi::OsStr, time::UNIX_EPOCH}; + use tempfile::NamedTempFile; + + #[test] + fn test_files_with_unknown_op() { + let a = NamedTempFile::new().unwrap(); + let b = NamedTempFile::new().unwrap(); + let a = OsStr::new(a.path()); + let b = OsStr::new(b.path()); + let op = OsStr::new("unknown_op"); + + assert!(files(a, b, op).is_err()); + } + + #[test] + #[cfg(unix)] + fn test_files_with_ef_op() { + let a = NamedTempFile::new().unwrap(); + let b = NamedTempFile::new().unwrap(); + let a = OsStr::new(a.path()); + let b = OsStr::new(b.path()); + let op = OsStr::new("-ef"); + + assert!(files(a, a, op).unwrap()); + assert!(!files(a, b, op).unwrap()); + assert!(!files(b, a, op).unwrap()); + + let existing_file = a; + let non_existing_file = OsStr::new("non_existing_file"); + + assert!(!files(existing_file, non_existing_file, op).unwrap()); + assert!(!files(non_existing_file, existing_file, op).unwrap()); + assert!(!files(non_existing_file, non_existing_file, op).unwrap()); + } + + #[test] + fn test_files_with_nt_op() { + let older_file = NamedTempFile::new().unwrap(); + older_file.as_file().set_modified(UNIX_EPOCH).unwrap(); + let older_file = OsStr::new(older_file.path()); + let newer_file = NamedTempFile::new().unwrap(); + let newer_file = OsStr::new(newer_file.path()); + let op = OsStr::new("-nt"); + + assert!(files(newer_file, older_file, op).unwrap()); + assert!(!files(older_file, newer_file, op).unwrap()); + + let existing_file = newer_file; + let non_existing_file = OsStr::new("non_existing_file"); + + assert!(files(existing_file, non_existing_file, op).unwrap()); + assert!(!files(non_existing_file, existing_file, op).unwrap()); + assert!(!files(non_existing_file, non_existing_file, op).unwrap()); + } + + #[test] + fn test_files_with_ot_op() { + let older_file = NamedTempFile::new().unwrap(); + older_file.as_file().set_modified(UNIX_EPOCH).unwrap(); + let older_file = OsStr::new(older_file.path()); + let newer_file = NamedTempFile::new().unwrap(); + let newer_file = OsStr::new(newer_file.path()); + let op = OsStr::new("-ot"); + + assert!(!files(newer_file, older_file, op).unwrap()); + assert!(files(older_file, newer_file, op).unwrap()); + + let existing_file = newer_file; + let non_existing_file = OsStr::new("non_existing_file"); + + assert!(!files(existing_file, non_existing_file, op).unwrap()); + assert!(files(non_existing_file, existing_file, op).unwrap()); + assert!(!files(non_existing_file, non_existing_file, op).unwrap()); + } #[test] fn test_integer_op() { diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 1dba782f5..4b5460cfd 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -5,10 +5,8 @@ // spell-checker:ignore (words) egid euid pseudofloat -use uutests::at_and_ucmd; -use uutests::new_ucmd; use uutests::util::TestScenario; -use uutests::util_name; +use uutests::{at_and_ucmd, new_ucmd, util_name}; #[test] fn test_empty_test_equivalent_to_false() { @@ -337,14 +335,26 @@ fn test_file_is_newer_than_and_older_than_itself() { } #[test] -fn test_non_existing_files() { - let scenario = TestScenario::new(util_name!()); +fn test_file_is_newer_than_non_existing_file() { + new_ucmd!() + .args(&["non_existing_file", "-nt", "regular_file"]) + .fails_with_code(1) + .no_output(); - let result = scenario - .ucmd() - .args(&["newer_file", "-nt", "regular_file"]) - .fails_with_code(1); - assert!(result.stderr().is_empty()); + new_ucmd!() + .args(&["regular_file", "-nt", "non_existing_file"]) + .succeeds() + .no_output(); + + new_ucmd!() + .args(&["non_existing_file", "-ot", "regular_file"]) + .succeeds() + .no_output(); + + new_ucmd!() + .args(&["regular_file", "-ot", "non_existing_file"]) + .fails_with_code(1) + .no_output(); } #[test]