diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index d45f99418..fba2db7ab 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -37,8 +37,8 @@ use uucore::{backup_control, update_control}; // requires these enum. pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; use uucore::{ - format_usage, help_about, help_section, help_usage, prompt_yes, show_error, show_warning, - util_name, + format_usage, help_about, help_section, help_usage, prompt_yes, + shortcut_value_parser::ShortcutValueParser, show_error, show_warning, util_name, }; use crate::copydir::copy_directory; @@ -396,22 +396,14 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[ "ownership", "timestamps", "context", - "link", "links", "xattr", "all", ]; #[cfg(not(unix))] -static PRESERVABLE_ATTRIBUTES: &[&str] = &[ - "mode", - "timestamps", - "context", - "link", - "links", - "xattr", - "all", -]; +static PRESERVABLE_ATTRIBUTES: &[&str] = + &["mode", "timestamps", "context", "links", "xattr", "all"]; pub fn uu_app() -> Command { const MODE_ARGS: &[&str] = &[ @@ -543,7 +535,7 @@ pub fn uu_app() -> Command { .overrides_with_all(MODE_ARGS) .require_equals(true) .default_missing_value("always") - .value_parser(["auto", "always", "never"]) + .value_parser(ShortcutValueParser::new(["auto", "always", "never"])) .num_args(0..=1) .help("control clone/CoW copies. See below"), ) @@ -559,9 +551,7 @@ pub fn uu_app() -> Command { .long(options::PRESERVE) .action(ArgAction::Append) .use_value_delimiter(true) - .value_parser(clap::builder::PossibleValuesParser::new( - PRESERVABLE_ATTRIBUTES, - )) + .value_parser(ShortcutValueParser::new(PRESERVABLE_ATTRIBUTES)) .num_args(0..) .require_equals(true) .value_name("ATTR_LIST") @@ -655,7 +645,7 @@ pub fn uu_app() -> Command { Arg::new(options::SPARSE) .long(options::SPARSE) .value_name("WHEN") - .value_parser(["never", "auto", "always"]) + .value_parser(ShortcutValueParser::new(["never", "auto", "always"])) .help("control creation of sparse files. See below"), ) // TODO: implement the following args diff --git a/src/uucore/src/lib/features/update_control.rs b/src/uucore/src/lib/features/update_control.rs index bd4292514..83700e551 100644 --- a/src/uucore/src/lib/features/update_control.rs +++ b/src/uucore/src/lib/features/update_control.rs @@ -61,6 +61,7 @@ pub enum UpdateMode { } pub mod arguments { + use crate::shortcut_value_parser::ShortcutValueParser; use clap::ArgAction; pub static OPT_UPDATE: &str = "update"; @@ -71,7 +72,7 @@ pub mod arguments { clap::Arg::new(OPT_UPDATE) .long("update") .help("move only when the SOURCE file is newer than the destination file or when the destination file is missing") - .value_parser(["none", "all", "older"]) + .value_parser(ShortcutValueParser::new(["none", "all", "older"])) .num_args(0..=1) .default_missing_value("older") .require_equals(true) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index bdfc9c8e8..1f81011db 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs ROOTDIR USERDIR procfs outfile uufs xattrs +// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR procfs outfile uufs xattrs use crate::common::util::TestScenario; #[cfg(not(windows))] @@ -286,16 +286,18 @@ fn test_cp_arg_update_interactive_error() { #[test] fn test_cp_arg_update_none() { - let (at, mut ucmd) = at_and_ucmd!(); + for argument in ["--update=none", "--update=non", "--update=n"] { + let (at, mut ucmd) = at_and_ucmd!(); - ucmd.arg(TEST_HELLO_WORLD_SOURCE) - .arg(TEST_HOW_ARE_YOU_SOURCE) - .arg("--update=none") - .succeeds() - .no_stderr() - .no_stdout(); + ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .arg(argument) + .succeeds() + .no_stderr() + .no_stdout(); - assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); + } } #[test] @@ -1402,29 +1404,28 @@ fn test_cp_preserve_no_args_before_opts() { #[test] fn test_cp_preserve_all() { - let (at, mut ucmd) = at_and_ucmd!(); - let src_file = "a"; - let dst_file = "b"; + for argument in ["--preserve=all", "--preserve=al"] { + let (at, mut ucmd) = at_and_ucmd!(); + let src_file = "a"; + let dst_file = "b"; - // Prepare the source file - at.touch(src_file); - #[cfg(unix)] - at.set_mode(src_file, 0o0500); + // Prepare the source file + at.touch(src_file); + #[cfg(unix)] + at.set_mode(src_file, 0o0500); - // TODO: create a destination that does not allow copying of xattr and context - // Copy - ucmd.arg(src_file) - .arg(dst_file) - .arg("--preserve=all") - .succeeds(); + // TODO: create a destination that does not allow copying of xattr and context + // Copy + ucmd.arg(src_file).arg(dst_file).arg(argument).succeeds(); - #[cfg(all(unix, not(target_os = "freebsd")))] - { - // Assert that the mode, ownership, and timestamps are preserved - // NOTICE: the ownership is not modified on the src file, because that requires root permissions - let metadata_src = at.metadata(src_file); - let metadata_dst = at.metadata(dst_file); - assert_metadata_eq!(metadata_src, metadata_dst); + #[cfg(all(unix, not(target_os = "freebsd")))] + { + // Assert that the mode, ownership, and timestamps are preserved + // NOTICE: the ownership is not modified on the src file, because that requires root permissions + let metadata_src = at.metadata(src_file); + let metadata_dst = at.metadata(dst_file); + assert_metadata_eq!(metadata_src, metadata_dst); + } } } @@ -1472,6 +1473,35 @@ fn test_cp_preserve_all_context_fails_on_non_selinux() { .fails(); } +#[test] +fn test_cp_preserve_link_parses() { + // TODO: Also check whether --preserve=link did the right thing! + for argument in [ + "--preserve=links", + "--preserve=link", + "--preserve=li", + "--preserve=l", + ] { + new_ucmd!() + .arg(argument) + .arg(TEST_COPY_FROM_FOLDER_FILE) + .arg(TEST_HELLO_WORLD_DEST) + .succeeds() + .no_output(); + } +} + +#[test] +fn test_cp_preserve_invalid_rejected() { + new_ucmd!() + .arg("--preserve=invalid-value") + .arg(TEST_COPY_FROM_FOLDER_FILE) + .arg(TEST_HELLO_WORLD_DEST) + .fails() + .code_is(1) + .no_stdout(); +} + #[test] #[cfg(target_os = "android")] #[cfg(disabled_until_fixed)] // FIXME: the test looks to .succeed on android @@ -2196,14 +2226,16 @@ fn test_cp_reflink_none() { #[test] #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] fn test_cp_reflink_never() { - let (at, mut ucmd) = at_and_ucmd!(); - ucmd.arg("--reflink=never") - .arg(TEST_HELLO_WORLD_SOURCE) - .arg(TEST_EXISTING_FILE) - .succeeds(); + for argument in ["--reflink=never", "--reflink=neve", "--reflink=n"] { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.arg(argument) + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .succeeds(); - // Check the content of the destination file - assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); + // Check the content of the destination file + assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); + } } #[test] @@ -2286,19 +2318,21 @@ fn test_cp_sparse_never_empty() { #[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn test_cp_sparse_always_empty() { - let (at, mut ucmd) = at_and_ucmd!(); + for argument in ["--sparse=always", "--sparse=alway", "--sparse=al"] { + let (at, mut ucmd) = at_and_ucmd!(); - const BUFFER_SIZE: usize = 4096 * 4; - let buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; + const BUFFER_SIZE: usize = 4096 * 4; + let buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; - at.make_file("src_file1"); - at.write_bytes("src_file1", &buf); + at.make_file("src_file1"); + at.write_bytes("src_file1", &buf); - ucmd.args(&["--sparse=always", "src_file1", "dst_file_sparse"]) - .succeeds(); + ucmd.args(&[argument, "src_file1", "dst_file_sparse"]) + .succeeds(); - assert_eq!(at.read_bytes("dst_file_sparse"), buf); - assert_eq!(at.metadata("dst_file_sparse").blocks(), 0); + assert_eq!(at.read_bytes("dst_file_sparse"), buf); + assert_eq!(at.metadata("dst_file_sparse").blocks(), 0); + } } #[cfg(any(target_os = "linux", target_os = "android"))]