mod common;
use common::*;
#[allow(unused_imports)]
use common::{extract_section_lines, strip_ansi_inline};
use nanoargs::{ArgBuilder, Pos};
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop1_builder_to_def_default_round_trip(
name in arb_identifier(),
default in prop::option::of(arb_value_string()),
) {
let mut pos = Pos::new(&name);
if let Some(ref d) = default {
pos = pos.default(d);
}
let parser = ArgBuilder::new().positional(pos).build().unwrap();
let positionals = parser.positionals();
prop_assert_eq!(positionals.len(), 1);
prop_assert_eq!(&positionals[0].default, &default);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop2_builder_to_def_multi_round_trip(
name in arb_identifier(),
multi in any::<bool>(),
) {
let mut pos = Pos::new(&name);
if multi {
pos = pos.multi();
}
let parser = ArgBuilder::new().positional(pos).build().unwrap();
let positionals = parser.positionals();
prop_assert_eq!(positionals.len(), 1);
prop_assert_eq!(positionals[0].multi, multi);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop3_required_conflicts_are_rejected(
name in arb_identifier(),
default in arb_value_string(),
conflict_type in prop_oneof![Just("default"), Just("multi")],
) {
let pos = match &*conflict_type {
"default" => Pos::new(&name).required().default(&default),
"multi" => Pos::new(&name).required().multi(),
_ => unreachable!(),
};
let result = ArgBuilder::new().positional(pos).build();
prop_assert!(result.is_err());
match result.unwrap_err() {
nanoargs::ParseError::InvalidFormat(msg) => {
prop_assert!(msg.contains(&name));
}
other => prop_assert!(false, "expected InvalidFormat, got {:?}", other),
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop4_multi_positional_must_be_last(
names in prop::collection::vec(arb_identifier(), 2..=4)
.prop_filter("need distinct names", |v| {
let set: std::collections::HashSet<_> = v.iter().collect();
set.len() == v.len()
}),
multi_idx in 0usize..3,
) {
let multi_idx = multi_idx % (names.len() - 1); let mut builder = ArgBuilder::new();
for (i, name) in names.iter().enumerate() {
let pos = if i == multi_idx {
Pos::new(name).multi()
} else {
Pos::new(name)
};
builder = builder.positional(pos);
}
let result = builder.build();
prop_assert!(result.is_err());
match result.unwrap_err() {
nanoargs::ParseError::InvalidFormat(msg) => {
prop_assert!(msg.contains(&names[multi_idx]));
}
other => prop_assert!(false, "expected InvalidFormat, got {:?}", other),
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop5_parser_default_fallback(
name in arb_identifier(),
default in arb_value_string(),
provided in prop::option::of(arb_value_string()),
) {
let pos = Pos::new(&name).default(&default);
let parser = ArgBuilder::new().positional(pos).build().unwrap();
let args: Vec<String> = match &provided {
Some(val) => vec![val.clone()],
None => vec![],
};
let result = parser.parse(args).unwrap();
let positionals = result.get_positionals();
prop_assert_eq!(positionals.len(), 1);
match &provided {
Some(val) => prop_assert_eq!(&positionals[0], val),
None => prop_assert_eq!(&positionals[0], &default),
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop6_help_text_default_annotation(
name in arb_identifier(),
desc in arb_description(),
default in arb_value_string(),
) {
let pos = Pos::new(&name).desc(&desc).default(&default);
let parser = ArgBuilder::new().positional(pos).build().unwrap();
let help = parser.help_text();
let section_lines = extract_section_lines(&help, "Positional arguments:");
let section_plain: String = section_lines.iter().map(|l| strip_ansi_inline(l)).collect::<Vec<_>>().join("\n");
prop_assert!(
section_plain.contains(&format!("[default: {}]", default)),
"expected [default: {}] in positional section, got:\n{}",
default,
section_plain
);
let pos_no_default = Pos::new(&name).desc(&desc);
let parser2 = ArgBuilder::new().positional(pos_no_default).build().unwrap();
let help2 = parser2.help_text();
let plain2 = strip_ansi_inline(&help2);
prop_assert!(
!plain2.contains("[default:"),
"unexpected default annotation in help without default:\n{}",
plain2
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop7_help_text_multi_suffix(
name in arb_identifier(),
desc in arb_description(),
) {
let pos = Pos::new(&name).desc(&desc).multi();
let parser = ArgBuilder::new().positional(pos).build().unwrap();
let help = parser.help_text();
let plain = strip_ansi_inline(&help);
let usage_line = plain.lines().find(|l| l.starts_with("Usage:")).unwrap();
prop_assert!(
usage_line.contains(&format!("[{}]...", name)),
"expected [{}]... in usage line, got: {}",
name,
usage_line
);
let section_lines = extract_section_lines(&help, "Positional arguments:");
let section_plain: String = section_lines.iter().map(|l| strip_ansi_inline(l)).collect::<Vec<_>>().join("\n");
prop_assert!(
section_plain.contains(&format!("{}...", name)),
"expected {}... in positional section, got:\n{}",
name,
section_plain
);
let pos2 = Pos::new(&name).desc(&desc);
let parser2 = ArgBuilder::new().positional(pos2).build().unwrap();
let help2 = parser2.help_text();
let plain2 = strip_ansi_inline(&help2);
let usage2 = plain2.lines().find(|l| l.starts_with("Usage:")).unwrap();
prop_assert!(
!usage2.contains(&format!("{}...", name)),
"unexpected ... suffix in usage for non-multi positional: {}",
usage2
);
}
}