From 63203a0a6874aed9d706df0a05f4cf845deb7e38 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 25 Sep 2022 03:33:36 +0200 Subject: [PATCH 1/3] test: add -N FILE exists and has been modified since it was last read Upstream: tests/misc/test-N.sh --- src/uu/test/src/parser.rs | 6 +++--- src/uu/test/src/test.rs | 5 +++++ tests/by-util/test_test.rs | 13 +++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index 5d27f8838..1177f49ea 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -55,8 +55,8 @@ impl Symbol { "-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Self::Op(Operator::Int(s)), "-ef" | "-nt" | "-ot" => Self::Op(Operator::File(s)), "-n" | "-z" => Self::UnaryOp(UnaryOperator::StrlenOp(s)), - "-b" | "-c" | "-d" | "-e" | "-f" | "-g" | "-G" | "-h" | "-k" | "-L" | "-O" - | "-p" | "-r" | "-s" | "-S" | "-t" | "-u" | "-w" | "-x" => { + "-b" | "-c" | "-d" | "-e" | "-f" | "-g" | "-G" | "-h" | "-k" | "-L" | "-N" + | "-O" | "-p" | "-r" | "-s" | "-S" | "-t" | "-u" | "-w" | "-x" => { Self::UnaryOp(UnaryOperator::FiletestOp(s)) } _ => Self::Literal(s), @@ -108,7 +108,7 @@ impl Symbol { /// INTOP → -eq | -ge | -gt | -le | -lt | -ne /// FILEOP → -ef | -nt | -ot /// STRLEN → -n | -z -/// FILETEST → -b | -c | -d | -e | -f | -g | -G | -h | -k | -L | -O | -p | +/// FILETEST → -b | -c | -d | -e | -f | -g | -G | -h | -k | -L | -N | -O | -p | /// -r | -s | -S | -t | -u | -w | -x /// BOOLOP → -a | -o /// diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index e53ba4db1..e16ac85ea 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -205,6 +205,7 @@ fn eval(stack: &mut Vec) -> Result { "-h" => path(&f, &PathCondition::SymLink), "-k" => path(&f, &PathCondition::Sticky), "-L" => path(&f, &PathCondition::SymLink), + "-N" => path(&f, &PathCondition::ExistsModifiedLastRead), "-O" => path(&f, &PathCondition::UserOwns), "-p" => path(&f, &PathCondition::Fifo), "-r" => path(&f, &PathCondition::Readable), @@ -273,6 +274,7 @@ enum PathCondition { CharacterSpecial, Directory, Exists, + ExistsModifiedLastRead, Regular, GroupIdFlag, GroupOwns, @@ -351,6 +353,9 @@ fn path(path: &OsStr, condition: &PathCondition) -> bool { PathCondition::CharacterSpecial => file_type.is_char_device(), PathCondition::Directory => file_type.is_dir(), PathCondition::Exists => true, + PathCondition::ExistsModifiedLastRead => { + metadata.accessed().unwrap() < metadata.modified().unwrap() + } PathCondition::Regular => file_type.is_file(), PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0, PathCondition::GroupOwns => metadata.gid() == getegid(), diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 3d74dfa90..8ee4f8732 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -882,3 +882,16 @@ fn test_bracket_syntax_version() { .succeeds() .stdout_matches(&r"\[ \d+\.\d+\.\d+".parse().unwrap()); } + +#[test] +#[allow(non_snake_case)] +fn test_file_N() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + scene.ucmd().args(&["-N", "regular_file"]).fails(); + // The file will have different create/modified data + // so, test -N will return 0 + sleep(std::time::Duration::from_millis(1000)); + at.touch("regular_file"); + scene.ucmd().args(&["-N", "regular_file"]).succeeds(); +} From f0b8b33dc171a670277a033b455f962bdfef335b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 Oct 2022 18:11:40 -1000 Subject: [PATCH 2/3] test: add support for -ef, -nt & -ot --- src/uu/test/src/test.rs | 47 ++++++++++++++++++++++++++++++++--- tests/by-util/test_test.rs | 50 ++++++++++++++++++++++++++++++++------ 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index e16ac85ea..c220ef7e9 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -13,6 +13,9 @@ mod parser; use clap::{crate_version, Command}; use parser::{parse, Operator, Symbol, UnaryOperator}; use std::ffi::{OsStr, OsString}; +use std::fs; +#[cfg(unix)] +use std::os::unix::fs::MetadataExt; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::format_usage; @@ -170,7 +173,11 @@ fn eval(stack: &mut Vec) -> Result { Ok(integers(&a, &b, &op)?) } - Some(Symbol::Op(Operator::File(_op))) => unimplemented!(), + Some(Symbol::Op(Operator::File(op))) => { + let b = pop_literal!(); + let a = pop_literal!(); + Ok(files(&a, &b, &op)?) + } Some(Symbol::UnaryOp(UnaryOperator::StrlenOp(op))) => { let s = match stack.pop() { Some(Symbol::Literal(s)) => s, @@ -230,9 +237,14 @@ fn eval(stack: &mut Vec) -> Result { } } +/// Operations to compare integers +/// `a` is the left hand side +/// `b` is the left hand side +/// `op` the operation (ex: -eq, -lt, etc) fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result { let format_err = |value: &OsStr| format!("invalid integer {}", value.quote()); + // Parse the two inputs let a: i64 = a .to_str() .and_then(|s| s.parse().ok()) @@ -243,6 +255,7 @@ fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result { .and_then(|s| s.parse().ok()) .ok_or_else(|| format_err(b))?; + // Do the maths Ok(match op.to_str() { Some("-eq") => a == b, Some("-ne") => a != b, @@ -254,6 +267,33 @@ fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result { }) } +/// Operations to compare files metadata +/// `a` is the left hand side +/// `b` is the left hand side +/// `op` the operation (ex: -ef, -nt, etc) +fn files(a: &OsStr, b: &OsStr, op: &OsStr) -> Result { + // Don't manage the error. GNU doesn't show error when doing + // test foo -nt bar + let f_a = match fs::metadata(a) { + Ok(f) => f, + Err(_) => return Ok(false), + }; + let f_b = match fs::metadata(b) { + Ok(f) => f, + Err(_) => return Ok(false), + }; + + 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.created().unwrap() > f_b.created().unwrap(), + _ => return Err(format!("unknown operator {}", op.quote())), + }) +} + fn isatty(fd: &OsStr) -> Result { fd.to_str() .and_then(|s| s.parse().ok()) @@ -292,8 +332,8 @@ enum PathCondition { #[cfg(not(windows))] fn path(path: &OsStr, condition: &PathCondition) -> bool { - use std::fs::{self, Metadata}; - use std::os::unix::fs::{FileTypeExt, MetadataExt}; + use std::fs::Metadata; + use std::os::unix::fs::FileTypeExt; const S_ISUID: u32 = 0o4000; const S_ISGID: u32 = 0o2000; @@ -386,6 +426,7 @@ fn path(path: &OsStr, condition: &PathCondition) -> bool { PathCondition::CharacterSpecial => false, PathCondition::Directory => stat.is_dir(), PathCondition::Exists => true, + PathCondition::ExistsModifiedLastRead => unimplemented!(), PathCondition::Regular => stat.is_file(), PathCondition::GroupIdFlag => false, PathCondition::GroupOwns => unimplemented!(), diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 8ee4f8732..028babc4b 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -11,6 +11,7 @@ // spell-checker:ignore (words) egid euid pseudofloat use crate::common::util::*; +use std::thread::sleep; #[test] fn test_empty_test_equivalent_to_false() { @@ -332,7 +333,7 @@ fn test_invalid_utf8_integer_compare() { } #[test] -#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] +#[cfg(unix)] fn test_file_is_itself() { new_ucmd!() .args(&["regular_file", "-ef", "regular_file"]) @@ -340,7 +341,7 @@ fn test_file_is_itself() { } #[test] -#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] +#[cfg(not(target_env = "musl"))] fn test_file_is_newer_than_and_older_than_itself() { // odd but matches GNU new_ucmd!() @@ -354,15 +355,47 @@ fn test_file_is_newer_than_and_older_than_itself() { } #[test] -#[ignore = "todo: implement these"] +fn test_non_existing_files() { + let scenario = TestScenario::new(util_name!()); + + let result = scenario + .ucmd() + .args(&["newer_file", "-nt", "regular_file"]) + .fails(); + assert!(result.stderr().is_empty()); +} + +#[test] +#[cfg(unix)] +fn test_same_device_inode() { + let scenario = TestScenario::new(util_name!()); + let at = &scenario.fixtures; + + scenario.cmd("touch").arg("regular_file").succeeds(); + scenario.cmd("touch").arg("regular_file_second").succeeds(); + + at.symlink_file("regular_file", "symlink"); + + scenario + .ucmd() + .args(&["regular_file", "-ef", "regular_file_second"]) + .fails(); + + scenario + .ucmd() + .args(&["regular_file", "-ef", "symlink"]) + .succeeds(); +} + +#[test] +#[cfg(not(target_env = "musl"))] +// musl: creation time is not available on this platform currently fn test_newer_file() { let scenario = TestScenario::new(util_name!()); + scenario.cmd("touch").arg("regular_file").succeeds(); + sleep(std::time::Duration::from_millis(1000)); scenario.cmd("touch").arg("newer_file").succeeds(); - scenario - .cmd("touch") - .args(&["-m", "-d", "last Thursday", "regular_file"]) - .succeeds(); scenario .ucmd() @@ -370,7 +403,7 @@ fn test_newer_file() { .succeeds(); scenario .ucmd() - .args(&["regular_file", "-ot", "newer_file"]) + .args(&["newer_file", "-ot", "regular_file"]) .succeeds(); } @@ -885,6 +918,7 @@ fn test_bracket_syntax_version() { #[test] #[allow(non_snake_case)] +#[cfg(unix)] fn test_file_N() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; From 33de6c89db561fb1c12ad1ece634a14500a469ce Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 Oct 2022 19:56:54 -1000 Subject: [PATCH 3/3] test: allow parsing of bigger numbers Fixes: tests/misc/test.pl --- src/uu/test/src/test.rs | 29 +++++++++++++++++++++++++++-- tests/by-util/test_test.rs | 23 +++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index c220ef7e9..e6e2e5360 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -245,12 +245,12 @@ fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result { let format_err = |value: &OsStr| format!("invalid integer {}", value.quote()); // Parse the two inputs - let a: i64 = a + let a: i128 = a .to_str() .and_then(|s| s.parse().ok()) .ok_or_else(|| format_err(a))?; - let b: i64 = b + let b: i128 = b .to_str() .and_then(|s| s.parse().ok()) .ok_or_else(|| format_err(b))?; @@ -442,3 +442,28 @@ fn path(path: &OsStr, condition: &PathCondition) -> bool { PathCondition::Executable => false, // TODO } } + +#[cfg(test)] +mod tests { + use super::integers; + use std::ffi::OsStr; + + #[test] + fn test_integer_op() { + let a = OsStr::new("18446744073709551616"); + let b = OsStr::new("0"); + assert_eq!(integers(a, b, OsStr::new("-lt")).unwrap(), false); + let a = OsStr::new("18446744073709551616"); + let b = OsStr::new("0"); + assert_eq!(integers(a, b, OsStr::new("-gt")).unwrap(), true); + let a = OsStr::new("-1"); + let b = OsStr::new("0"); + assert_eq!(integers(a, b, OsStr::new("-lt")).unwrap(), true); + let a = OsStr::new("42"); + let b = OsStr::new("42"); + assert_eq!(integers(a, b, OsStr::new("-eq")).unwrap(), true); + let a = OsStr::new("42"); + let b = OsStr::new("42"); + assert_eq!(integers(a, b, OsStr::new("-ne")).unwrap(), false); + } +} diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 028babc4b..54ee03985 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -929,3 +929,26 @@ fn test_file_N() { at.touch("regular_file"); scene.ucmd().args(&["-N", "regular_file"]).succeeds(); } + +#[test] +fn test_long_integer() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .args(&["18446744073709551616", "-eq", "0"]) + .fails(); + scene + .ucmd() + .args(&["-9223372036854775809", "-ge", "18446744073709551616"]) + .fails(); + scene + .ucmd() + .args(&[ + "'('", + "-9223372036854775809", + "-ge", + "18446744073709551616", + "')'", + ]) + .fails(); +}