Skip to content

Commit

Permalink
fix(assert): Report invalid defaults in debug asserts
Browse files Browse the repository at this point in the history
This can help people catch them via `App::debug_assert` rather than
waiting until the default is used and validated.

Fixes clap-rs#3202
  • Loading branch information
epage committed Feb 8, 2022
1 parent edf9d05 commit 9f41bb3
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 0 deletions.
56 changes: 56 additions & 0 deletions src/build/arg/debug_asserts.rs
Expand Up @@ -44,6 +44,20 @@ pub(crate) fn assert_arg(arg: &Arg) {
}

assert_arg_flags(arg);

assert_defaults(arg, "default_value", arg.default_vals.iter().copied());
assert_defaults(
arg,
"default_missing_value",
arg.default_missing_vals.iter().copied(),
);
assert_defaults(
arg,
"default_value_if",
arg.default_vals_ifs
.iter()
.filter_map(|(_, _, default)| *default),
);
}

fn assert_arg_flags(arg: &Arg) {
Expand Down Expand Up @@ -78,3 +92,45 @@ fn assert_arg_flags(arg: &Arg) {
checker!(IgnoreCase requires TakesValue);
checker!(AllowInvalidUtf8 requires TakesValue);
}

fn assert_defaults<'d>(
arg: &Arg,
field: &'static str,
defaults: impl IntoIterator<Item = &'d std::ffi::OsStr>,
) {
for default_os in defaults {
if let Some(default_s) = default_os.to_str() {
if !arg.possible_vals.is_empty() {
assert!(
arg.possible_vals.iter().any(|possible_val| {
possible_val.matches(default_s, arg.is_set(ArgSettings::IgnoreCase))
}),
"Argument `{}`'s {}={} doesn't match possible values",
arg.name,
field,
default_s
);
}

if let Some(validator) = arg.validator.as_ref() {
let mut validator = validator.lock().unwrap();
if let Err(err) = validator(default_s) {
panic!(
"Argument `{}`'s {}={} failed validation: {}",
arg.name, field, default_s, err
);
}
}
}

if let Some(validator) = arg.validator_os.as_ref() {
let mut validator = validator.lock().unwrap();
if let Err(err) = validator(default_os) {
panic!(
"Argument `{}`'s {}={:?} failed validation: {}",
arg.name, field, default_os, err
);
}
}
}
}
30 changes: 30 additions & 0 deletions tests/builder/default_missing_vals.rs
Expand Up @@ -155,3 +155,33 @@ fn default_missing_value_flag_value() {
false
);
}

#[cfg(debug_assertions)]
#[test]
#[should_panic = "Argument `arg`'s default_missing_value=value doesn't match possible values"]
fn default_missing_values_are_possible_values() {
use clap::{App, Arg};

let _ = App::new("test")
.arg(
Arg::new("arg")
.possible_values(["one", "two"])
.default_missing_value("value"),
)
.try_get_matches();
}

#[cfg(debug_assertions)]
#[test]
#[should_panic = "Argument `arg`'s default_missing_value=value failed validation: invalid digit found in string"]
fn default_missing_values_are_valid() {
use clap::{App, Arg};

let _ = App::new("test")
.arg(
Arg::new("arg")
.validator(|val| val.parse::<u32>().map_err(|e| e.to_string()))
.default_missing_value("value"),
)
.try_get_matches();
}
30 changes: 30 additions & 0 deletions tests/builder/default_vals.rs
Expand Up @@ -631,6 +631,36 @@ fn required_args_with_default_values() {
.try_get_matches();
}

#[cfg(debug_assertions)]
#[test]
#[should_panic = "Argument `arg`'s default_value=value doesn't match possible values"]
fn default_values_are_possible_values() {
use clap::{App, Arg};

let _ = App::new("test")
.arg(
Arg::new("arg")
.possible_values(["one", "two"])
.default_value("value"),
)
.try_get_matches();
}

#[cfg(debug_assertions)]
#[test]
#[should_panic = "Argument `arg`'s default_value=value failed validation: invalid digit found in string"]
fn default_values_are_valid() {
use clap::{App, Arg};

let _ = App::new("test")
.arg(
Arg::new("arg")
.validator(|val| val.parse::<u32>().map_err(|e| e.to_string()))
.default_value("value"),
)
.try_get_matches();
}

#[test]
fn with_value_delimiter() {
let app = App::new("multiple_values").arg(
Expand Down

0 comments on commit 9f41bb3

Please sign in to comment.