1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

Merge pull request #2467 from Funky185540/backup_mode_determination

backup_control: Fix backup mode determination
This commit is contained in:
Terts Diepraam 2021-07-20 13:27:04 +02:00 committed by GitHub
commit eae8c72793
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 453 additions and 88 deletions

3
Cargo.lock generated
View file

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3
[[package]] [[package]]
name = "Inflector" name = "Inflector"
version = "0.11.4" version = "0.11.4"
@ -2768,6 +2770,7 @@ dependencies = [
name = "uucore" name = "uucore"
version = "0.0.9" version = "0.0.9"
dependencies = [ dependencies = [
"clap",
"data-encoding", "data-encoding",
"dns-lookup", "dns-lookup",
"dunce", "dunce",

View file

@ -98,6 +98,9 @@ quick_error! {
/// path, but those that are not implemented yet should return /// path, but those that are not implemented yet should return
/// a NotImplemented error. /// a NotImplemented error.
NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) } NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) }
/// Invalid arguments to backup
Backup(description: String) { display("{}\nTry 'cp --help' for more information.", description) }
} }
} }
@ -359,7 +362,6 @@ pub fn uu_app() -> App<'static, 'static> {
.takes_value(true) .takes_value(true)
.require_equals(true) .require_equals(true)
.min_values(0) .min_values(0)
.possible_values(backup_control::BACKUP_CONTROL_VALUES)
.value_name("CONTROL") .value_name("CONTROL")
) )
.arg(Arg::with_name(options::BACKUP_NO_ARG) .arg(Arg::with_name(options::BACKUP_NO_ARG)
@ -604,9 +606,17 @@ impl Options {
|| matches.is_present(options::ARCHIVE); || matches.is_present(options::ARCHIVE);
let backup_mode = backup_control::determine_backup_mode( let backup_mode = backup_control::determine_backup_mode(
matches.is_present(options::BACKUP_NO_ARG) || matches.is_present(options::BACKUP), matches.is_present(options::BACKUP_NO_ARG),
matches.is_present(options::BACKUP),
matches.value_of(options::BACKUP), matches.value_of(options::BACKUP),
); );
let backup_mode = match backup_mode {
Err(err) => {
return Err(Error::Backup(err));
}
Ok(mode) => mode,
};
let backup_suffix = let backup_suffix =
backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX)); backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX));

View file

@ -308,15 +308,25 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
None None
}; };
let backup_mode = backup_control::determine_backup_mode(
matches.is_present(OPT_BACKUP_NO_ARG),
matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP),
);
let backup_mode = match backup_mode {
Err(err) => {
show_usage_error!("{}", err);
return Err(1);
}
Ok(mode) => mode,
};
let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned()); let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned());
Ok(Behavior { Ok(Behavior {
main_function, main_function,
specified_mode, specified_mode,
backup_mode: backup_control::determine_backup_mode( backup_mode,
matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP),
),
suffix: backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)), suffix: backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)),
owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(), owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(),
group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(), group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(),

View file

@ -22,6 +22,7 @@ use std::os::unix::fs::symlink;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file}; use std::os::windows::fs::{symlink_dir, symlink_file};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uucore::backup_control::{self, BackupMode};
use uucore::fs::{canonicalize, CanonicalizeMode}; use uucore::fs::{canonicalize, CanonicalizeMode};
pub struct Settings { pub struct Settings {
@ -43,14 +44,6 @@ pub enum OverwriteMode {
Force, Force,
} }
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BackupMode {
NoBackup,
SimpleBackup,
NumberedBackup,
ExistingBackup,
}
fn get_usage() -> String { fn get_usage() -> String {
format!( format!(
"{0} [OPTION]... [-T] TARGET LINK_NAME (1st form) "{0} [OPTION]... [-T] TARGET LINK_NAME (1st form)
@ -78,7 +71,7 @@ fn get_long_usage() -> String {
static ABOUT: &str = "change file owner and group"; static ABOUT: &str = "change file owner and group";
mod options { mod options {
pub const B: &str = "b"; pub const BACKUP_NO_ARG: &str = "b";
pub const BACKUP: &str = "backup"; pub const BACKUP: &str = "backup";
pub const FORCE: &str = "force"; pub const FORCE: &str = "force";
pub const INTERACTIVE: &str = "interactive"; pub const INTERACTIVE: &str = "interactive";
@ -99,7 +92,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = uu_app() let matches = uu_app()
.usage(&usage[..]) .usage(&usage[..])
.after_help(&long_usage[..]) .after_help(&*format!(
"{}\n{}",
long_usage,
backup_control::BACKUP_CONTROL_LONG_HELP
))
.get_matches_from(args); .get_matches_from(args);
/* the list of files */ /* the list of files */
@ -118,33 +115,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
OverwriteMode::NoClobber OverwriteMode::NoClobber
}; };
let backup_mode = if matches.is_present(options::B) { let backup_mode = backup_control::determine_backup_mode(
BackupMode::ExistingBackup matches.is_present(options::BACKUP_NO_ARG),
} else if matches.is_present(options::BACKUP) { matches.is_present(options::BACKUP),
match matches.value_of(options::BACKUP) { matches.value_of(options::BACKUP),
None => BackupMode::ExistingBackup, );
Some(mode) => match mode { let backup_mode = match backup_mode {
"simple" | "never" => BackupMode::SimpleBackup, Err(err) => {
"numbered" | "t" => BackupMode::NumberedBackup, show_usage_error!("{}", err);
"existing" | "nil" => BackupMode::ExistingBackup, return 1;
"none" | "off" => BackupMode::NoBackup,
_ => panic!(), // cannot happen as it is managed by clap
},
} }
} else { Ok(mode) => mode,
BackupMode::NoBackup
}; };
let backup_suffix = if matches.is_present(options::SUFFIX) { let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX));
matches.value_of(options::SUFFIX).unwrap()
} else {
"~"
};
let settings = Settings { let settings = Settings {
overwrite: overwrite_mode, overwrite: overwrite_mode,
backup: backup_mode, backup: backup_mode,
suffix: backup_suffix.to_string(), suffix: backup_suffix,
symbolic: matches.is_present(options::SYMBOLIC), symbolic: matches.is_present(options::SYMBOLIC),
relative: matches.is_present(options::RELATIVE), relative: matches.is_present(options::RELATIVE),
target_dir: matches target_dir: matches
@ -162,22 +151,19 @@ pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg(Arg::with_name(options::B).short(options::B).help(
"make a backup of each file that would otherwise be overwritten or \
removed",
))
.arg( .arg(
Arg::with_name(options::BACKUP) Arg::with_name(options::BACKUP)
.long(options::BACKUP) .long(options::BACKUP)
.help( .help("make a backup of each existing destination file")
"make a backup of each file that would otherwise be overwritten \
or removed",
)
.takes_value(true) .takes_value(true)
.possible_values(&[ .require_equals(true)
"simple", "never", "numbered", "t", "existing", "nil", "none", "off", .min_values(0)
]) .value_name("CONTROL"),
.value_name("METHOD"), )
.arg(
Arg::with_name(options::BACKUP_NO_ARG)
.short(options::BACKUP_NO_ARG)
.help("like --backup but does not accept an argument"),
) )
// TODO: opts.arg( // TODO: opts.arg(
// Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \ // Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \

View file

@ -86,9 +86,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let overwrite_mode = determine_overwrite_mode(&matches); let overwrite_mode = determine_overwrite_mode(&matches);
let backup_mode = backup_control::determine_backup_mode( let backup_mode = backup_control::determine_backup_mode(
matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP), matches.is_present(OPT_BACKUP_NO_ARG),
matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP), matches.value_of(OPT_BACKUP),
); );
let backup_mode = match backup_mode {
Err(err) => {
show_usage_error!("{}", err);
return 1;
}
Ok(mode) => mode,
};
if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup {
show_usage_error!("options --backup and --no-clobber are mutually exclusive"); show_usage_error!("options --backup and --no-clobber are mutually exclusive");
@ -135,7 +143,6 @@ pub fn uu_app() -> App<'static, 'static> {
.takes_value(true) .takes_value(true)
.require_equals(true) .require_equals(true)
.min_values(0) .min_values(0)
.possible_values(backup_control::BACKUP_CONTROL_VALUES)
.value_name("CONTROL") .value_name("CONTROL")
) )
.arg( .arg(

View file

@ -30,6 +30,10 @@ time = { version="<= 0.1.43", optional=true }
data-encoding = { version="~2.1", optional=true } ## data-encoding: require v2.1; but v2.2.0 breaks the build for MinSRV v1.31.0 data-encoding = { version="~2.1", optional=true } ## data-encoding: require v2.1; but v2.2.0 breaks the build for MinSRV v1.31.0
libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0 libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0
[dev-dependencies]
clap = "2.33.3"
lazy_static = "1.3"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] } winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }

View file

@ -7,19 +7,15 @@ pub static BACKUP_CONTROL_VALUES: &[&str] = &[
"simple", "never", "numbered", "t", "existing", "nil", "none", "off", "simple", "never", "numbered", "t", "existing", "nil", "none", "off",
]; ];
pub static BACKUP_CONTROL_LONG_HELP: &str = "The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX. Here are the version control values: pub static BACKUP_CONTROL_LONG_HELP: &str =
"The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.
The version control method may be selected via the --backup option or through
the VERSION_CONTROL environment variable. Here are the values:
none, off none, off never make backups (even if --backup is given)
never make backups (even if --backup is given) numbered, t make numbered backups
existing, nil numbered if numbered backups exist, simple otherwise
numbered, t simple, never always make simple backups";
make numbered backups
existing, nil
numbered if numbered backups exist, simple otherwise
simple, never
always make simple backups";
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum BackupMode { pub enum BackupMode {
@ -37,35 +33,177 @@ pub fn determine_backup_suffix(supplied_suffix: Option<&str>) -> String {
} }
} }
/// # TODO /// Determine the "mode" for the backup operation to perform, if any.
/// ///
/// This function currently deviates slightly from how the [manual][1] describes /// Parses the backup options according to the [GNU manual][1], and converts
/// that it should work. In particular, the current implementation: /// them to an instance of `BackupMode` for further processing.
/// ///
/// 1. Doesn't strictly respect the order in which to determine the backup type, /// For an explanation of what the arguments mean, refer to the examples below.
/// which is (in order of precedence)
/// 1. Take a valid value to the '--backup' option
/// 2. Take the value of the `VERSION_CONTROL` env var
/// 3. default to 'existing'
/// 2. Doesn't accept abbreviations to the 'backup_option' parameter
/// ///
/// [1]: https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html /// [1]: https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html
pub fn determine_backup_mode(backup_opt_exists: bool, backup_opt: Option<&str>) -> BackupMode { ///
if backup_opt_exists { ///
match backup_opt.map(String::from) { /// # Errors
// default is existing, see: ///
// https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html /// If an argument supplied directly to the long `backup` option, or read in
None => BackupMode::ExistingBackup, /// through the `VERSION CONTROL` env var is ambiguous (i.e. may resolve to
Some(mode) => match &mode[..] { /// multiple backup modes) or invalid, an error is returned. The error contains
"simple" | "never" => BackupMode::SimpleBackup, /// the formatted error string which may then be passed to the
"numbered" | "t" => BackupMode::NumberedBackup, /// [`show_usage_error`] macro.
"existing" | "nil" => BackupMode::ExistingBackup, ///
"none" | "off" => BackupMode::NoBackup, ///
_ => panic!(), // cannot happen as it is managed by clap /// # Examples
}, ///
/// Here's how one would integrate the backup mode determination into an
/// application.
///
/// ```
/// #[macro_use]
/// extern crate uucore;
/// use uucore::backup_control::{self, BackupMode};
/// use clap::{App, Arg};
///
/// fn main() {
/// let OPT_BACKUP: &str = "backup";
/// let OPT_BACKUP_NO_ARG: &str = "b";
/// let matches = App::new("app")
/// .arg(Arg::with_name(OPT_BACKUP_NO_ARG)
/// .short(OPT_BACKUP_NO_ARG))
/// .arg(Arg::with_name(OPT_BACKUP)
/// .long(OPT_BACKUP)
/// .takes_value(true)
/// .require_equals(true)
/// .min_values(0))
/// .get_matches_from(vec![
/// "app", "-b", "--backup=t"
/// ]);
///
/// let backup_mode = backup_control::determine_backup_mode(
/// matches.is_present(OPT_BACKUP_NO_ARG), matches.is_present(OPT_BACKUP),
/// matches.value_of(OPT_BACKUP)
/// );
/// let backup_mode = match backup_mode {
/// Err(err) => {
/// show_usage_error!("{}", err);
/// return;
/// },
/// Ok(mode) => mode,
/// };
/// }
/// ```
///
/// This example shows an ambiguous input, as 'n' may resolve to 4 different
/// backup modes.
///
///
/// ```
/// #[macro_use]
/// extern crate uucore;
/// use uucore::backup_control::{self, BackupMode};
/// use clap::{crate_version, App, Arg, ArgMatches};
///
/// fn main() {
/// let OPT_BACKUP: &str = "backup";
/// let OPT_BACKUP_NO_ARG: &str = "b";
/// let matches = App::new("app")
/// .arg(Arg::with_name(OPT_BACKUP_NO_ARG)
/// .short(OPT_BACKUP_NO_ARG))
/// .arg(Arg::with_name(OPT_BACKUP)
/// .long(OPT_BACKUP)
/// .takes_value(true)
/// .require_equals(true)
/// .min_values(0))
/// .get_matches_from(vec![
/// "app", "-b", "--backup=n"
/// ]);
///
/// let backup_mode = backup_control::determine_backup_mode(
/// matches.is_present(OPT_BACKUP_NO_ARG), matches.is_present(OPT_BACKUP),
/// matches.value_of(OPT_BACKUP)
/// );
/// let backup_mode = match backup_mode {
/// Err(err) => {
/// show_usage_error!("{}", err);
/// return;
/// },
/// Ok(mode) => mode,
/// };
/// }
/// ```
pub fn determine_backup_mode(
short_opt_present: bool,
long_opt_present: bool,
long_opt_value: Option<&str>,
) -> Result<BackupMode, String> {
if long_opt_present {
// Use method to determine the type of backups to make. When this option
// is used but method is not specified, then the value of the
// VERSION_CONTROL environment variable is used. And if VERSION_CONTROL
// is not set, the default backup type is existing.
if let Some(method) = long_opt_value {
// Second argument is for the error string that is returned.
match_method(method, "backup type")
} else if let Ok(method) = env::var("VERSION_CONTROL") {
// Second argument is for the error string that is returned.
match_method(&method, "$VERSION_CONTROL")
} else {
Ok(BackupMode::ExistingBackup)
}
} else if short_opt_present {
// the short form of this option, -b does not accept any argument.
// Using -b is equivalent to using --backup=existing.
Ok(BackupMode::ExistingBackup)
} else {
// No option was present at all
Ok(BackupMode::NoBackup)
}
}
/// Match a backup option string to a `BackupMode`.
///
/// The GNU manual specifies that abbreviations to options are valid as long as
/// they aren't ambiguous. This function matches the given `method` argument
/// against all valid backup options (via `starts_with`), and returns a valid
/// [`BackupMode`] if exactly one backup option matches the `method` given.
///
/// `origin` is required in order to format the generated error message
/// properly, when an error occurs.
///
///
/// # Errors
///
/// If `method` is ambiguous (i.e. may resolve to multiple backup modes) or
/// invalid, an error is returned. The error contains the formatted error string
/// which may then be passed to the [`show_usage_error`] macro.
fn match_method(method: &str, origin: &str) -> Result<BackupMode, String> {
let matches: Vec<&&str> = BACKUP_CONTROL_VALUES
.iter()
.filter(|val| val.starts_with(method))
.collect();
if matches.len() == 1 {
match *matches[0] {
"simple" | "never" => Ok(BackupMode::SimpleBackup),
"numbered" | "t" => Ok(BackupMode::NumberedBackup),
"existing" | "nil" => Ok(BackupMode::ExistingBackup),
"none" | "off" => Ok(BackupMode::NoBackup),
_ => unreachable!(), // cannot happen as we must have exactly one match
// from the list above.
} }
} else { } else {
BackupMode::NoBackup let error_type = if matches.is_empty() {
"invalid"
} else {
"ambiguous"
};
Err(format!(
"{0} argument {1} for {2}
Valid arguments are:
- none, off
- simple, never
- existing, nil
- numbered, t",
error_type, method, origin
))
} }
} }
@ -82,13 +220,13 @@ pub fn get_backup_path(
} }
} }
pub fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf { fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf {
let mut p = path.to_string_lossy().into_owned(); let mut p = path.to_string_lossy().into_owned();
p.push_str(suffix); p.push_str(suffix);
PathBuf::from(p) PathBuf::from(p)
} }
pub fn numbered_backup_path(path: &Path) -> PathBuf { fn numbered_backup_path(path: &Path) -> PathBuf {
for i in 1_u64.. { for i in 1_u64.. {
let path_str = &format!("{}.~{}~", path.to_string_lossy(), i); let path_str = &format!("{}.~{}~", path.to_string_lossy(), i);
let path = Path::new(path_str); let path = Path::new(path_str);
@ -99,7 +237,7 @@ pub fn numbered_backup_path(path: &Path) -> PathBuf {
panic!("cannot create backup") panic!("cannot create backup")
} }
pub fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf { fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf {
let test_path_str = &format!("{}.~1~", path.to_string_lossy()); let test_path_str = &format!("{}.~1~", path.to_string_lossy());
let test_path = Path::new(test_path_str); let test_path = Path::new(test_path_str);
if test_path.exists() { if test_path.exists() {
@ -108,3 +246,210 @@ pub fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf {
simple_backup_path(path, suffix) simple_backup_path(path, suffix)
} }
} }
//
// Tests for this module
//
#[cfg(test)]
mod tests {
use super::*;
use std::env;
// Required to instantiate mutex in shared context
use lazy_static::lazy_static;
use std::sync::Mutex;
// The mutex is required here as by default all tests are run as separate
// threads under the same parent process. As environment variables are
// specific to processes (and thus shared among threads), data races *will*
// occur if no precautions are taken. Thus we have all tests that rely on
// environment variables lock this empty mutex to ensure they don't access
// it concurrently.
lazy_static! {
static ref TEST_MUTEX: Mutex<()> = Mutex::new(());
}
// Environment variable for "VERSION_CONTROL"
static ENV_VERSION_CONTROL: &str = "VERSION_CONTROL";
// Defaults to --backup=existing
#[test]
fn test_backup_mode_short_only() {
let short_opt_present = true;
let long_opt_present = false;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::ExistingBackup);
}
// --backup takes precedence over -b
#[test]
fn test_backup_mode_long_preferred_over_short() {
let short_opt_present = true;
let long_opt_present = true;
let long_opt_value = Some("none");
let _dummy = TEST_MUTEX.lock().unwrap();
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::NoBackup);
}
// --backup can be passed without an argument
#[test]
fn test_backup_mode_long_without_args_no_env() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::ExistingBackup);
}
// --backup can be passed with an argument only
#[test]
fn test_backup_mode_long_with_args() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = Some("simple");
let _dummy = TEST_MUTEX.lock().unwrap();
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::SimpleBackup);
}
// --backup errors on invalid argument
#[test]
fn test_backup_mode_long_with_args_invalid() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = Some("foobar");
let _dummy = TEST_MUTEX.lock().unwrap();
let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value);
assert!(result.is_err());
let text = result.unwrap_err();
assert!(text.contains("invalid argument foobar for backup type"));
}
// --backup errors on ambiguous argument
#[test]
fn test_backup_mode_long_with_args_ambiguous() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = Some("n");
let _dummy = TEST_MUTEX.lock().unwrap();
let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value);
assert!(result.is_err());
let text = result.unwrap_err();
assert!(text.contains("ambiguous argument n for backup type"));
}
// --backup accepts shortened arguments (si for simple)
#[test]
fn test_backup_mode_long_with_arg_shortened() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = Some("si");
let _dummy = TEST_MUTEX.lock().unwrap();
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::SimpleBackup);
}
// -b ignores the "VERSION_CONTROL" environment variable
#[test]
fn test_backup_mode_short_only_ignore_env() {
let short_opt_present = true;
let long_opt_present = false;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
env::set_var(ENV_VERSION_CONTROL, "none");
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::ExistingBackup);
env::remove_var(ENV_VERSION_CONTROL);
}
// --backup can be passed without an argument, but reads env var if existent
#[test]
fn test_backup_mode_long_without_args_with_env() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
env::set_var(ENV_VERSION_CONTROL, "none");
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::NoBackup);
env::remove_var(ENV_VERSION_CONTROL);
}
// --backup errors on invalid VERSION_CONTROL env var
#[test]
fn test_backup_mode_long_with_env_var_invalid() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
env::set_var(ENV_VERSION_CONTROL, "foobar");
let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value);
assert!(result.is_err());
let text = result.unwrap_err();
assert!(text.contains("invalid argument foobar for $VERSION_CONTROL"));
env::remove_var(ENV_VERSION_CONTROL);
}
// --backup errors on ambiguous VERSION_CONTROL env var
#[test]
fn test_backup_mode_long_with_env_var_ambiguous() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
env::set_var(ENV_VERSION_CONTROL, "n");
let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value);
assert!(result.is_err());
let text = result.unwrap_err();
assert!(text.contains("ambiguous argument n for $VERSION_CONTROL"));
env::remove_var(ENV_VERSION_CONTROL);
}
// --backup accepts shortened env vars (si for simple)
#[test]
fn test_backup_mode_long_with_env_var_shortened() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
env::set_var(ENV_VERSION_CONTROL, "si");
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::SimpleBackup);
env::remove_var(ENV_VERSION_CONTROL);
}
}