diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3793a0968..47793977e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,7 @@ search the issues to make sure no one else is working on it. ## Best practices 1. Follow what GNU is doing in term of options and behavior. +1. If possible, look at the GNU test suite execution in the CI and make the test work if failing. 1. Use clap for argument management. 1. Make sure that the code coverage is covering all of the cases, including errors. 1. The code must be clippy-warning-free and rustfmt-compliant. diff --git a/README.md b/README.md index 8ab4c6128..fde01d64a 100644 --- a/README.md +++ b/README.md @@ -327,8 +327,12 @@ To run locally: ```bash $ bash util/build-gnu.sh $ bash util/run-gnu-test.sh +# To run a single test: +$ bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example ``` +Note that it relies on individual utilities (not the multicall binary). + ## Contribute To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index b76e04b7a..1b560553f 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -155,6 +155,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { (now, now) }; + let mut error_code = 0; + for filename in &files { let path = &filename[..]; @@ -166,6 +168,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if let Err(e) = File::create(path) { show_warning!("cannot touch '{}': {}", path, e); + error_code = 1; continue; }; @@ -202,14 +205,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if matches.is_present(options::NO_DEREF) { if let Err(e) = set_symlink_file_times(path, atime, mtime) { - show_warning!("cannot touch '{}': {}", path, e); + // we found an error, it should fail in any case + error_code = 1; + if e.kind() == std::io::ErrorKind::PermissionDenied { + // GNU compatibility (not-owner.sh) + show_error!("setting times of '{}': {}", path, "Permission denied"); + } else { + show_error!("setting times of '{}': {}", path, e); + } } } else if let Err(e) = filetime::set_file_times(path, atime, mtime) { - show_warning!("cannot touch '{}': {}", path, e); + // we found an error, it should fail in any case + error_code = 1; + + if e.kind() == std::io::ErrorKind::PermissionDenied { + // GNU compatibility (not-owner.sh) + show_error!("setting times of '{}': {}", path, "Permission denied"); + } else { + show_error!("setting times of '{}': {}", path, e); + } } } - - 0 + error_code } fn stat(path: &str, follow: bool) -> (FileTime, FileTime) { diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 3c803e1c6..c861a50dd 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -449,3 +449,20 @@ fn test_touch_mtime_dst_fails() { ucmd.args(&["-m", "-t", &s, file]).fails(); } } + +#[test] +#[cfg(unix)] +fn test_touch_system_fails() { + let (_at, mut ucmd) = at_and_ucmd!(); + let file = "/"; + ucmd.args(&[file]) + .fails() + .stderr_contains("setting times of '/'"); +} + +#[test] +fn test_touch_trailing_slash() { + let (_at, mut ucmd) = at_and_ucmd!(); + let file = "no-file/"; + ucmd.args(&[file]).fails(); +} diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh index 61034e015..9d51a983e 100644 --- a/util/run-gnu-test.sh +++ b/util/run-gnu-test.sh @@ -5,4 +5,11 @@ BUILDDIR="${PWD}/uutils/target/release" GNULIB_DIR="${PWD}/gnulib" pushd gnu -timeout -sKILL 2h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make +export RUST_BACKTRACE=1 + +if test -n "$1"; then + # if set, run only the test passed + export RUN_TEST="TESTS=$1" +fi + +timeout -sKILL 2h make -j "$(nproc)" check $RUN_TEST SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make