mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
mv: gnu test case mv-n compatibility (#6599)
* uucore: add update control `none-fail` * uucore: show suggestion when parse errors occurs because of an ambiguous value * added tests for fail-none and ambiguous parse error * uucore: ambiguous value code refractor * cp: no-clobber fail silently and outputs skipped message in debug * mv: add --debug support * minor changes --------- Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
This commit is contained in:
parent
db402875f6
commit
8a9fb84a8e
6 changed files with 147 additions and 63 deletions
|
@ -39,7 +39,7 @@ use uucore::{backup_control, update_control};
|
||||||
pub use uucore::{backup_control::BackupMode, update_control::UpdateMode};
|
pub use uucore::{backup_control::BackupMode, update_control::UpdateMode};
|
||||||
use uucore::{
|
use uucore::{
|
||||||
format_usage, help_about, help_section, help_usage, prompt_yes,
|
format_usage, help_about, help_section, help_usage, prompt_yes,
|
||||||
shortcut_value_parser::ShortcutValueParser, show_error, show_warning, util_name,
|
shortcut_value_parser::ShortcutValueParser, show_error, show_warning,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::copydir::copy_directory;
|
use crate::copydir::copy_directory;
|
||||||
|
@ -79,8 +79,10 @@ quick_error! {
|
||||||
StripPrefixError(err: StripPrefixError) { from() }
|
StripPrefixError(err: StripPrefixError) { from() }
|
||||||
|
|
||||||
/// Result of a skipped file
|
/// Result of a skipped file
|
||||||
/// Currently happens when "no" is selected in interactive mode
|
/// Currently happens when "no" is selected in interactive mode or when
|
||||||
Skipped { }
|
/// `no-clobber` flag is set and destination is already present.
|
||||||
|
/// `exit with error` is used to determine which exit code should be returned.
|
||||||
|
Skipped(exit_with_error:bool) { }
|
||||||
|
|
||||||
/// Result of a skipped file
|
/// Result of a skipped file
|
||||||
InvalidArgument(description: String) { display("{}", description) }
|
InvalidArgument(description: String) { display("{}", description) }
|
||||||
|
@ -1210,7 +1212,7 @@ fn show_error_if_needed(error: &Error) {
|
||||||
Error::NotAllFilesCopied => {
|
Error::NotAllFilesCopied => {
|
||||||
// Need to return an error code
|
// Need to return an error code
|
||||||
}
|
}
|
||||||
Error::Skipped => {
|
Error::Skipped(_) => {
|
||||||
// touch a b && echo "n"|cp -i a b && echo $?
|
// touch a b && echo "n"|cp -i a b && echo $?
|
||||||
// should return an error from GNU 9.2
|
// should return an error from GNU 9.2
|
||||||
}
|
}
|
||||||
|
@ -1295,7 +1297,9 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult
|
||||||
&mut copied_files,
|
&mut copied_files,
|
||||||
) {
|
) {
|
||||||
show_error_if_needed(&error);
|
show_error_if_needed(&error);
|
||||||
|
if !matches!(error, Error::Skipped(false)) {
|
||||||
non_fatal_errors = true;
|
non_fatal_errors = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
copied_destinations.insert(dest.clone());
|
copied_destinations.insert(dest.clone());
|
||||||
}
|
}
|
||||||
|
@ -1397,17 +1401,19 @@ fn copy_source(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OverwriteMode {
|
impl OverwriteMode {
|
||||||
fn verify(&self, path: &Path) -> CopyResult<()> {
|
fn verify(&self, path: &Path, debug: bool) -> CopyResult<()> {
|
||||||
match *self {
|
match *self {
|
||||||
Self::NoClobber => {
|
Self::NoClobber => {
|
||||||
eprintln!("{}: not replacing {}", util_name(), path.quote());
|
if debug {
|
||||||
Err(Error::NotAllFilesCopied)
|
println!("skipped {}", path.quote());
|
||||||
|
}
|
||||||
|
Err(Error::Skipped(false))
|
||||||
}
|
}
|
||||||
Self::Interactive(_) => {
|
Self::Interactive(_) => {
|
||||||
if prompt_yes!("overwrite {}?", path.quote()) {
|
if prompt_yes!("overwrite {}?", path.quote()) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::Skipped)
|
Err(Error::Skipped(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Clobber(_) => Ok(()),
|
Self::Clobber(_) => Ok(()),
|
||||||
|
@ -1654,7 +1660,7 @@ fn handle_existing_dest(
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.update != UpdateMode::ReplaceIfOlder {
|
if options.update != UpdateMode::ReplaceIfOlder {
|
||||||
options.overwrite.verify(dest)?;
|
options.overwrite.verify(dest, options.debug)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut is_dest_removed = false;
|
let mut is_dest_removed = false;
|
||||||
|
@ -1892,6 +1898,9 @@ fn handle_copy_mode(
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
update_control::UpdateMode::ReplaceNoneFail => {
|
||||||
|
return Err(Error::Error(format!("not replacing '{}'", dest.display())));
|
||||||
|
}
|
||||||
update_control::UpdateMode::ReplaceIfOlder => {
|
update_control::UpdateMode::ReplaceIfOlder => {
|
||||||
let dest_metadata = fs::symlink_metadata(dest)?;
|
let dest_metadata = fs::symlink_metadata(dest)?;
|
||||||
|
|
||||||
|
@ -1900,7 +1909,7 @@ fn handle_copy_mode(
|
||||||
if src_time <= dest_time {
|
if src_time <= dest_time {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
options.overwrite.verify(dest)?;
|
options.overwrite.verify(dest, options.debug)?;
|
||||||
|
|
||||||
copy_helper(
|
copy_helper(
|
||||||
source,
|
source,
|
||||||
|
@ -2262,7 +2271,7 @@ fn copy_helper(
|
||||||
File::create(dest).context(dest.display().to_string())?;
|
File::create(dest).context(dest.display().to_string())?;
|
||||||
} else if source_is_fifo && options.recursive && !options.copy_contents {
|
} else if source_is_fifo && options.recursive && !options.copy_contents {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
copy_fifo(dest, options.overwrite)?;
|
copy_fifo(dest, options.overwrite, options.debug)?;
|
||||||
} else if source_is_symlink {
|
} else if source_is_symlink {
|
||||||
copy_link(source, dest, symlinked_files)?;
|
copy_link(source, dest, symlinked_files)?;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2287,9 +2296,9 @@ fn copy_helper(
|
||||||
// "Copies" a FIFO by creating a new one. This workaround is because Rust's
|
// "Copies" a FIFO by creating a new one. This workaround is because Rust's
|
||||||
// built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390).
|
// built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390).
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> {
|
fn copy_fifo(dest: &Path, overwrite: OverwriteMode, debug: bool) -> CopyResult<()> {
|
||||||
if dest.exists() {
|
if dest.exists() {
|
||||||
overwrite.verify(dest)?;
|
overwrite.verify(dest, debug)?;
|
||||||
fs::remove_file(dest)?;
|
fs::remove_file(dest)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,9 @@ pub struct Options {
|
||||||
|
|
||||||
/// '-g, --progress'
|
/// '-g, --progress'
|
||||||
pub progress_bar: bool,
|
pub progress_bar: bool,
|
||||||
|
|
||||||
|
/// `--debug`
|
||||||
|
pub debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// specifies behavior of the overwrite flag
|
/// specifies behavior of the overwrite flag
|
||||||
|
@ -109,6 +112,7 @@ static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory";
|
||||||
static OPT_VERBOSE: &str = "verbose";
|
static OPT_VERBOSE: &str = "verbose";
|
||||||
static OPT_PROGRESS: &str = "progress";
|
static OPT_PROGRESS: &str = "progress";
|
||||||
static ARG_FILES: &str = "files";
|
static ARG_FILES: &str = "files";
|
||||||
|
static OPT_DEBUG: &str = "debug";
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
@ -135,10 +139,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let backup_mode = backup_control::determine_backup_mode(&matches)?;
|
let backup_mode = backup_control::determine_backup_mode(&matches)?;
|
||||||
let update_mode = update_control::determine_update_mode(&matches);
|
let update_mode = update_control::determine_update_mode(&matches);
|
||||||
|
|
||||||
if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup {
|
if backup_mode != BackupMode::NoBackup
|
||||||
|
&& (overwrite_mode == OverwriteMode::NoClobber
|
||||||
|
|| update_mode == UpdateMode::ReplaceNone
|
||||||
|
|| update_mode == UpdateMode::ReplaceNoneFail)
|
||||||
|
{
|
||||||
return Err(UUsageError::new(
|
return Err(UUsageError::new(
|
||||||
1,
|
1,
|
||||||
"options --backup and --no-clobber are mutually exclusive",
|
"cannot combine --backup with -n/--no-clobber or --update=none-fail",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,9 +169,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
update: update_mode,
|
update: update_mode,
|
||||||
target_dir,
|
target_dir,
|
||||||
no_target_dir: matches.get_flag(OPT_NO_TARGET_DIRECTORY),
|
no_target_dir: matches.get_flag(OPT_NO_TARGET_DIRECTORY),
|
||||||
verbose: matches.get_flag(OPT_VERBOSE),
|
verbose: matches.get_flag(OPT_VERBOSE) || matches.get_flag(OPT_DEBUG),
|
||||||
strip_slashes: matches.get_flag(OPT_STRIP_TRAILING_SLASHES),
|
strip_slashes: matches.get_flag(OPT_STRIP_TRAILING_SLASHES),
|
||||||
progress_bar: matches.get_flag(OPT_PROGRESS),
|
progress_bar: matches.get_flag(OPT_PROGRESS),
|
||||||
|
debug: matches.get_flag(OPT_DEBUG),
|
||||||
};
|
};
|
||||||
|
|
||||||
mv(&files[..], &opts)
|
mv(&files[..], &opts)
|
||||||
|
@ -256,6 +265,12 @@ pub fn uu_app() -> Command {
|
||||||
.value_parser(ValueParser::os_string())
|
.value_parser(ValueParser::os_string())
|
||||||
.value_hint(clap::ValueHint::AnyPath),
|
.value_hint(clap::ValueHint::AnyPath),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new(OPT_DEBUG)
|
||||||
|
.long(OPT_DEBUG)
|
||||||
|
.help("explain how a file is copied. Implies -v")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode {
|
fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode {
|
||||||
|
@ -521,6 +536,9 @@ fn rename(
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.update == UpdateMode::ReplaceNone {
|
if opts.update == UpdateMode::ReplaceNone {
|
||||||
|
if opts.debug {
|
||||||
|
println!("skipped {}", to.quote());
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,11 +548,18 @@ fn rename(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
match opts.overwrite {
|
if opts.update == UpdateMode::ReplaceNoneFail {
|
||||||
OverwriteMode::NoClobber => {
|
|
||||||
let err_msg = format!("not replacing {}", to.quote());
|
let err_msg = format!("not replacing {}", to.quote());
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, err_msg));
|
return Err(io::Error::new(io::ErrorKind::Other, err_msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match opts.overwrite {
|
||||||
|
OverwriteMode::NoClobber => {
|
||||||
|
if opts.debug {
|
||||||
|
println!("skipped {}", to.quote());
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
OverwriteMode::Interactive => {
|
OverwriteMode::Interactive => {
|
||||||
if !prompt_yes!("overwrite {}?", to.quote()) {
|
if !prompt_yes!("overwrite {}?", to.quote()) {
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, ""));
|
return Err(io::Error::new(io::ErrorKind::Other, ""));
|
||||||
|
|
|
@ -59,6 +59,7 @@ pub enum UpdateMode {
|
||||||
/// --update=`older`
|
/// --update=`older`
|
||||||
/// -u
|
/// -u
|
||||||
ReplaceIfOlder,
|
ReplaceIfOlder,
|
||||||
|
ReplaceNoneFail,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod arguments {
|
pub mod arguments {
|
||||||
|
@ -76,7 +77,7 @@ pub mod arguments {
|
||||||
clap::Arg::new(OPT_UPDATE)
|
clap::Arg::new(OPT_UPDATE)
|
||||||
.long("update")
|
.long("update")
|
||||||
.help("move only when the SOURCE file is newer than the destination file or when the destination file is missing")
|
.help("move only when the SOURCE file is newer than the destination file or when the destination file is missing")
|
||||||
.value_parser(ShortcutValueParser::new(["none", "all", "older"]))
|
.value_parser(ShortcutValueParser::new(["none", "all", "older","none-fail"]))
|
||||||
.num_args(0..=1)
|
.num_args(0..=1)
|
||||||
.default_missing_value("older")
|
.default_missing_value("older")
|
||||||
.require_equals(true)
|
.require_equals(true)
|
||||||
|
@ -130,6 +131,7 @@ pub fn determine_update_mode(matches: &ArgMatches) -> UpdateMode {
|
||||||
"all" => UpdateMode::ReplaceAll,
|
"all" => UpdateMode::ReplaceAll,
|
||||||
"none" => UpdateMode::ReplaceNone,
|
"none" => UpdateMode::ReplaceNone,
|
||||||
"older" => UpdateMode::ReplaceIfOlder,
|
"older" => UpdateMode::ReplaceIfOlder,
|
||||||
|
"none-fail" => UpdateMode::ReplaceNoneFail,
|
||||||
_ => unreachable!("other args restricted by clap"),
|
_ => unreachable!("other args restricted by clap"),
|
||||||
}
|
}
|
||||||
} else if matches.get_flag(arguments::OPT_UPDATE_NO_ARG) {
|
} else if matches.get_flag(arguments::OPT_UPDATE_NO_ARG) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
// spell-checker:ignore abcdefgh abef
|
// spell-checker:ignore abcdefgh abef Strs
|
||||||
|
|
||||||
//! A parser that accepts shortcuts for values.
|
//! A parser that accepts shortcuts for values.
|
||||||
//! `ShortcutValueParser` is similar to clap's `PossibleValuesParser`
|
//! `ShortcutValueParser` is similar to clap's `PossibleValuesParser`
|
||||||
|
@ -32,6 +32,7 @@ impl ShortcutValueParser {
|
||||||
cmd: &clap::Command,
|
cmd: &clap::Command,
|
||||||
arg: Option<&clap::Arg>,
|
arg: Option<&clap::Arg>,
|
||||||
value: &str,
|
value: &str,
|
||||||
|
possible_values: &[&PossibleValue],
|
||||||
) -> clap::Error {
|
) -> clap::Error {
|
||||||
let mut err = clap::Error::new(ErrorKind::InvalidValue).with_cmd(cmd);
|
let mut err = clap::Error::new(ErrorKind::InvalidValue).with_cmd(cmd);
|
||||||
|
|
||||||
|
@ -52,10 +53,39 @@ impl ShortcutValueParser {
|
||||||
ContextValue::Strings(self.0.iter().map(|x| x.get_name().to_string()).collect()),
|
ContextValue::Strings(self.0.iter().map(|x| x.get_name().to_string()).collect()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// if `possible_values` is not empty then that means this error is because of an ambiguous value.
|
||||||
|
if !possible_values.is_empty() {
|
||||||
|
add_ambiguous_value_tip(possible_values, &mut err, value);
|
||||||
|
}
|
||||||
err
|
err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a suggestion when error is because of ambiguous values based on the provided possible values.
|
||||||
|
fn add_ambiguous_value_tip(
|
||||||
|
possible_values: &[&PossibleValue],
|
||||||
|
err: &mut clap::error::Error,
|
||||||
|
value: &str,
|
||||||
|
) {
|
||||||
|
let mut formatted_possible_values = String::new();
|
||||||
|
for (i, s) in possible_values.iter().enumerate() {
|
||||||
|
formatted_possible_values.push_str(&format!("'{}'", s.get_name()));
|
||||||
|
if i < possible_values.len() - 2 {
|
||||||
|
formatted_possible_values.push_str(", ");
|
||||||
|
} else if i < possible_values.len() - 1 {
|
||||||
|
formatted_possible_values.push_str(" or ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err.insert(
|
||||||
|
ContextKind::Suggested,
|
||||||
|
ContextValue::StyledStrs(vec![format!(
|
||||||
|
"It looks like '{}' could match several values. Did you mean {}?",
|
||||||
|
value, formatted_possible_values
|
||||||
|
)
|
||||||
|
.into()]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
impl TypedValueParser for ShortcutValueParser {
|
impl TypedValueParser for ShortcutValueParser {
|
||||||
type Value = String;
|
type Value = String;
|
||||||
|
|
||||||
|
@ -76,13 +106,13 @@ impl TypedValueParser for ShortcutValueParser {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match matched_values.len() {
|
match matched_values.len() {
|
||||||
0 => Err(self.generate_clap_error(cmd, arg, value)),
|
0 => Err(self.generate_clap_error(cmd, arg, value, &[])),
|
||||||
1 => Ok(matched_values[0].get_name().to_string()),
|
1 => Ok(matched_values[0].get_name().to_string()),
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(direct_match) = matched_values.iter().find(|x| x.get_name() == value) {
|
if let Some(direct_match) = matched_values.iter().find(|x| x.get_name() == value) {
|
||||||
Ok(direct_match.get_name().to_string())
|
Ok(direct_match.get_name().to_string())
|
||||||
} else {
|
} else {
|
||||||
Err(self.generate_clap_error(cmd, arg, value))
|
Err(self.generate_clap_error(cmd, arg, value, &matched_values))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +173,11 @@ mod tests {
|
||||||
|
|
||||||
for ambiguous_value in ambiguous_values {
|
for ambiguous_value in ambiguous_values {
|
||||||
let result = parser.parse_ref(&cmd, None, OsStr::new(ambiguous_value));
|
let result = parser.parse_ref(&cmd, None, OsStr::new(ambiguous_value));
|
||||||
assert_eq!(ErrorKind::InvalidValue, result.unwrap_err().kind());
|
assert_eq!(ErrorKind::InvalidValue, result.as_ref().unwrap_err().kind());
|
||||||
|
assert!(result.unwrap_err().to_string().contains(&format!(
|
||||||
|
"It looks like '{}' could match several values. Did you mean 'abcd' or 'abef'?",
|
||||||
|
ambiguous_value
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = parser.parse_ref(&cmd, None, OsStr::new("abc"));
|
let result = parser.parse_ref(&cmd, None, OsStr::new("abc"));
|
||||||
|
|
|
@ -324,18 +324,25 @@ fn test_cp_arg_update_interactive_error() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_arg_update_none() {
|
fn test_cp_arg_update_none() {
|
||||||
for argument in ["--update=none", "--update=non", "--update=n"] {
|
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.arg(argument)
|
.arg("--update=none")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.no_stderr()
|
.no_stderr()
|
||||||
.no_stdout();
|
.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]
|
||||||
|
fn test_cp_arg_update_none_fail() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
|
.arg("--update=none-fail")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains(format!("not replacing '{}'", TEST_HOW_ARE_YOU_SOURCE));
|
||||||
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -588,10 +595,9 @@ fn test_cp_arg_interactive_verbose_clobber() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
at.touch("a");
|
at.touch("a");
|
||||||
at.touch("b");
|
at.touch("b");
|
||||||
ucmd.args(&["-vin", "a", "b"])
|
ucmd.args(&["-vin", "--debug", "a", "b"])
|
||||||
.fails()
|
.succeeds()
|
||||||
.stderr_is("cp: not replacing 'b'\n")
|
.stdout_contains("skipped 'b'");
|
||||||
.no_stdout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -690,8 +696,9 @@ fn test_cp_arg_no_clobber() {
|
||||||
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.arg("--no-clobber")
|
.arg("--no-clobber")
|
||||||
.fails()
|
.arg("--debug")
|
||||||
.stderr_contains("not replacing");
|
.succeeds()
|
||||||
|
.stdout_contains("skipped 'how_are_you.txt'");
|
||||||
|
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
@ -702,7 +709,9 @@ fn test_cp_arg_no_clobber_inferred_arg() {
|
||||||
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.arg("--no-clob")
|
.arg("--no-clob")
|
||||||
.fails();
|
.arg("--debug")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_contains("skipped 'how_are_you.txt'");
|
||||||
|
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
@ -718,6 +727,7 @@ fn test_cp_arg_no_clobber_twice() {
|
||||||
.arg("--no-clobber")
|
.arg("--no-clobber")
|
||||||
.arg("source.txt")
|
.arg("source.txt")
|
||||||
.arg("dest.txt")
|
.arg("dest.txt")
|
||||||
|
.arg("--debug")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.no_stderr();
|
.no_stderr();
|
||||||
|
|
||||||
|
@ -729,7 +739,9 @@ fn test_cp_arg_no_clobber_twice() {
|
||||||
.arg("--no-clobber")
|
.arg("--no-clobber")
|
||||||
.arg("source.txt")
|
.arg("source.txt")
|
||||||
.arg("dest.txt")
|
.arg("dest.txt")
|
||||||
.fails();
|
.arg("--debug")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_contains("skipped 'dest.txt'");
|
||||||
|
|
||||||
assert_eq!(at.read("source.txt"), "some-content");
|
assert_eq!(at.read("source.txt"), "some-content");
|
||||||
// Should be empty as the "no-clobber" should keep
|
// Should be empty as the "no-clobber" should keep
|
||||||
|
@ -1773,11 +1785,12 @@ fn test_cp_preserve_links_case_7() {
|
||||||
|
|
||||||
ucmd.arg("-n")
|
ucmd.arg("-n")
|
||||||
.arg("--preserve=links")
|
.arg("--preserve=links")
|
||||||
|
.arg("--debug")
|
||||||
.arg("src/f")
|
.arg("src/f")
|
||||||
.arg("src/g")
|
.arg("src/g")
|
||||||
.arg("dest")
|
.arg("dest")
|
||||||
.fails()
|
.succeeds()
|
||||||
.stderr_contains("not replacing");
|
.stdout_contains("skipped");
|
||||||
|
|
||||||
assert!(at.dir_exists("dest"));
|
assert!(at.dir_exists("dest"));
|
||||||
assert!(at.plus("dest").join("f").exists());
|
assert!(at.plus("dest").join("f").exists());
|
||||||
|
|
|
@ -299,9 +299,9 @@ fn test_mv_interactive_no_clobber_force_last_arg_wins() {
|
||||||
|
|
||||||
scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.args(&[file_a, file_b, "-f", "-i", "-n"])
|
.args(&[file_a, file_b, "-f", "-i", "-n", "--debug"])
|
||||||
.fails()
|
.succeeds()
|
||||||
.stderr_is(format!("mv: not replacing '{file_b}'\n"));
|
.stdout_contains("skipped 'b.txt'");
|
||||||
|
|
||||||
scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
|
@ -352,9 +352,9 @@ fn test_mv_no_clobber() {
|
||||||
ucmd.arg("-n")
|
ucmd.arg("-n")
|
||||||
.arg(file_a)
|
.arg(file_a)
|
||||||
.arg(file_b)
|
.arg(file_b)
|
||||||
.fails()
|
.arg("--debug")
|
||||||
.code_is(1)
|
.succeeds()
|
||||||
.stderr_only(format!("mv: not replacing '{file_b}'\n"));
|
.stdout_contains("skipped 'test_mv_no_clobber_file_b");
|
||||||
|
|
||||||
assert!(at.file_exists(file_a));
|
assert!(at.file_exists(file_a));
|
||||||
assert!(at.file_exists(file_b));
|
assert!(at.file_exists(file_b));
|
||||||
|
@ -863,14 +863,16 @@ fn test_mv_backup_off() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mv_backup_no_clobber_conflicting_options() {
|
fn test_mv_backup_conflicting_options() {
|
||||||
|
for conflicting_opt in ["--no-clobber", "--update=none-fail", "--update=none"] {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.arg("--backup")
|
.arg("--backup")
|
||||||
.arg("--no-clobber")
|
.arg(conflicting_opt)
|
||||||
.arg("file1")
|
.arg("file1")
|
||||||
.arg("file2")
|
.arg("file2")
|
||||||
.fails()
|
.fails()
|
||||||
.usage_error("options --backup and --no-clobber are mutually exclusive");
|
.usage_error("cannot combine --backup with -n/--no-clobber or --update=none-fail");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1400,10 +1402,9 @@ fn test_mv_arg_interactive_skipped_vin() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
at.touch("a");
|
at.touch("a");
|
||||||
at.touch("b");
|
at.touch("b");
|
||||||
ucmd.args(&["-vin", "a", "b"])
|
ucmd.args(&["-vin", "a", "b", "--debug"])
|
||||||
.fails()
|
.succeeds()
|
||||||
.stderr_is("mv: not replacing 'b'\n")
|
.stdout_contains("skipped 'b'");
|
||||||
.no_stdout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue