use crate::command::{Command, CommandCallback, CommandResult};
use crate::error::*;
use crate::parse_and_execute::{parse_and_execute, parse_and_execute_single_command};
use crate::spec::{Spec, Specs};
use flaggy_values::value::{Value, Values};
use std::sync::Mutex;
struct FnInstrumentation {
call_count: Mutex<u64>,
}
impl FnInstrumentation {
fn new() -> FnInstrumentation {
FnInstrumentation {
call_count: Mutex::new(0),
}
}
fn record_call(&self) {
let mut data = self.call_count.lock().unwrap();
*data += 1;
}
fn get_call_count(&self) -> u64 {
*self.call_count.lock().unwrap()
}
}
fn into_expected_values(values: Vec<(&'static str, Value)>) -> Values {
values
.into_iter()
.map(|tuple| (tuple.0.to_owned(), tuple.1))
.collect()
}
fn build_trivial_test_command(name: &str) -> Command<()> {
Command::new(
name,
name,
Specs::new(vec![]).unwrap(),
Box::new(|vs| {
assert_eq!(into_expected_values(vec![]), vs);
Ok(())
}),
)
}
fn build_test_command(name: &str, specs: Specs, expected_vs: Values) -> Command<()> {
Command::new(
name,
name,
specs,
Box::new(move |vs| {
assert_eq!(expected_vs, vs);
Ok(())
}),
)
}
fn assert_results_match(
expected: Result<Option<CommandResult<()>>>,
actual: Result<Option<CommandResult<()>>>,
) {
fn stringify(r: &::std::result::Result<Option<CommandResult<()>>, String>) -> String {
match r {
Ok(r) => match r {
None => "internally-handled error".to_owned(),
Some(r) => format!("{:?} from command execution", r),
},
Err(e) => format!("internal error {}", e),
}
}
let expected = expected.map_err(|e| format!("{:?}", e));
let actual = actual.map_err(|e| format!("{:?}", e));
assert!(
expected == actual,
"Expected {}, got {}",
stringify(&expected),
stringify(&actual)
);
}
fn parse_and_execute_result_test_impl(
args: Vec<&'static str>,
mut commands: Vec<Command<()>>,
expected_command_name: &'static str,
expected_result: Result<Option<CommandResult<()>>>,
) {
let instrumentation = FnInstrumentation::new();
let expected_command: Option<Command<()>> = commands
.iter()
.position(|c| c.name == expected_command_name)
.map(|idx| commands.remove(idx));
let expected_command_metadata: Option<(String, String, Specs)> = expected_command
.as_ref()
.map(|c| (c.name.clone(), c.help.clone(), c.flags.clone()));
let mut real_callback: Option<CommandCallback<()>> = expected_command.map(|c| c.callback);
let callback: CommandCallback<()> = Box::new(|vs| {
instrumentation.record_call();
if let Some(real_callback) = real_callback.as_mut() {
real_callback(vs)
} else {
Ok(())
}
});
let mut modified_commands: Vec<Command<()>> = vec![];
if let Some(m) = expected_command_metadata {
modified_commands.push(Command {
name: m.0,
help: m.1,
flags: m.2,
callback: callback,
});
}
let commands: Vec<Command<()>> = commands
.into_iter()
.chain(modified_commands.into_iter())
.collect();
assert!(instrumentation.get_call_count() == 0);
let args: Vec<String> = args.into_iter().map(|arg| arg.to_owned()).collect();
let res =
parse_and_execute::<(), ::std::io::Stderr>("program", args.as_slice(), commands, None);
let expected_call_count = match res {
Err(_) => 0,
Ok(r) => match r {
None => 0,
Some(_) => 1,
},
};
assert_results_match(expected_result, res);
assert_eq!(expected_call_count, instrumentation.get_call_count());
}
fn parse_and_execute_test_impl(
args: Vec<&'static str>,
commands: Vec<Command<()>>,
expected_command_name: &'static str,
) {
parse_and_execute_result_test_impl(args, commands, expected_command_name, Ok(Some(Ok(()))));
}
#[test]
fn test_parse_and_execute_single_command() {
let expected_vs = into_expected_values(vec![
("flaga", Value::Single("quuz".to_owned())),
("flagb", Value::Single("baz".to_owned())),
("boola", Value::Boolean(false.to_string())),
("boolb", Value::Boolean(false.to_string())),
]);
let instrumentation = FnInstrumentation::new();
let callback: CommandCallback<()> = Box::new(|vs| {
instrumentation.record_call();
assert_eq!(expected_vs, vs);
Ok(())
});
let program = "program".to_owned();
let args = vec![
"--flaga=quuz".to_owned(),
"--flagb".to_owned(),
"baz".to_owned(),
];
let command = Command::new(
"foobar",
"foobar",
Specs::new(vec![
Spec::required("flaga", "flaga", None, None),
Spec::required("flagb", "flagb", None, Some("oof")),
Spec::boolean("boola", "boola", None),
Spec::boolean("boolb", "boolb", None),
Spec::positional("posa", "posa", None, false).unwrap(),
])
.unwrap(),
callback,
);
assert!(instrumentation.get_call_count() == 0);
assert!(parse_and_execute_single_command::<(), ::std::io::Stderr>(
program.as_ref(),
&args,
command,
None
)
.is_ok());
assert!(instrumentation.get_call_count() == 1);
}
#[test]
fn test_parse_invalid_command() {
parse_and_execute_result_test_impl(
vec!["biff", "foo", "bar", "baz"],
vec![
build_trivial_test_command("foo"),
build_trivial_test_command("bar"),
build_trivial_test_command("baz"),
],
"biff",
Ok(None),
);
}
#[test]
fn test_parse_command_no_arguments() {
parse_and_execute_test_impl(
vec!["bar"],
vec![
build_trivial_test_command("foo"),
build_trivial_test_command("bar"),
build_trivial_test_command("baz"),
],
"bar",
);
}
#[test]
fn test_parse_command_with_unused_arguments() {
parse_and_execute_test_impl(
vec!["baz", "foo", "bar", "baz"],
vec![
build_trivial_test_command("foo"),
build_trivial_test_command("bar"),
build_trivial_test_command("baz"),
],
"baz",
);
}
#[test]
fn test_default_values() {
let expected_vs = into_expected_values(vec![
("a", Value::Single("a".to_owned())),
("b", Value::Single("b".to_owned())),
("e", Value::Boolean(false.to_string())),
("f", Value::Boolean(false.to_string())),
]);
parse_and_execute_test_impl(
vec!["foo"],
vec![build_test_command(
"foo",
Specs::new(vec![
Spec::required("a", "a", None, Some("a")),
Spec::required("b", "b", Some('b'), Some("b")),
Spec::optional("c", "c", None),
Spec::optional("d", "d", Some('d')),
Spec::boolean("e", "e", None),
Spec::boolean("f", "f", Some('f')),
])
.unwrap(),
expected_vs,
)],
"foo",
);
}
#[test]
fn test_missing_required_flag() {
parse_and_execute_result_test_impl(
vec!["foo"],
vec![build_test_command(
"foo",
Specs::new(vec![
Spec::required("a", "a", None, None),
Spec::required("b", "b", Some('b'), Some("b")),
Spec::optional("c", "c", None),
Spec::optional("d", "d", Some('d')),
Spec::boolean("e", "e", None),
Spec::boolean("f", "f", Some('f')),
])
.unwrap(),
into_expected_values(vec![]),
)],
"foo",
Ok(None),
);
}
#[test]
fn test_parse_invalid_flag() {
parse_and_execute_result_test_impl(
vec!["foo", "--foo=bar"],
vec![build_test_command(
"foo",
Specs::new(vec![]).unwrap(),
into_expected_values(vec![]),
)],
"foo",
Ok(None),
);
}
#[test]
fn test_parse_missing_flag_value() {
parse_and_execute_result_test_impl(
vec!["foo", "--foobar", "--barbaz"],
vec![build_test_command(
"foo",
Specs::new(vec![
Spec::required("foobar", "foobar", None, None),
Spec::required("barbaz", "barbaz", None, None),
])
.unwrap(),
into_expected_values(vec![]),
)],
"foo",
Ok(None),
);
}
#[test]
fn test_parse_flag_format_variations() {
let expected_vs = into_expected_values(vec![
("flaga", Value::Single("a".to_owned())),
("flagb", Value::Single("b".to_owned())),
("flagc", Value::Single("c".to_owned())),
("flagd", Value::Single("d".to_owned())),
]);
parse_and_execute_test_impl(
vec!["foo", "--flaga=a", "--b=b", "-flagc", "c", "-d", "d"],
vec![build_test_command(
"foo",
Specs::new(vec![
Spec::required("flaga", "flaga", Some('a'), None),
Spec::required("flagb", "flagb", Some('b'), None),
Spec::required("flagc", "flagc", Some('c'), None),
Spec::required("flagd", "flagd", Some('d'), None),
])
.unwrap(),
expected_vs,
)],
"foo",
);
}
#[test]
fn test_parse_boolean_flag_format_variations() {
let expected_vs = into_expected_values(vec![
("boola", Value::Boolean(true.to_string())),
("boolb", Value::Boolean(true.to_string())),
("boolc", Value::Boolean(true.to_string())),
("boold", Value::Boolean(false.to_string())),
]);
parse_and_execute_test_impl(
vec!["foo", "--boola", "-b", "--boolc=true", "-d=false"],
vec![build_test_command(
"foo",
Specs::new(vec![
Spec::boolean("boola", "boola", Some('a')),
Spec::boolean("boolb", "boolb", Some('b')),
Spec::boolean("boolc", "boolc", Some('c')),
Spec::boolean("boold", "boold", Some('d')),
])
.unwrap(),
expected_vs,
)],
"foo",
);
}
#[test]
fn test_parse_named_flags() {
let expected_vs = into_expected_values(vec![
("flaga", Value::Single("foo".to_owned())),
("flagb", Value::Single("defaultb".to_owned())),
("flagc", Value::Single("bar".to_owned())),
("flagd", Value::Single("baz".to_owned())),
("flagf", Value::Boolean(false.to_string())),
("flagg", Value::Boolean(true.to_string())),
("flagh", Value::Boolean(false.to_string())),
]);
parse_and_execute_test_impl(
vec![
"foobar",
"--flaga",
"foo",
"--flagc=bar",
"--flagd",
"baz",
"-g",
"--h=false",
],
vec![build_test_command(
"foobar",
Specs::new(vec![
Spec::required("flaga", "flaga", Some('a'), None),
Spec::required("flagb", "flagb", Some('b'), Some("defaultb")),
Spec::required("flagc", "flagc", Some('c'), None),
Spec::optional("flagd", "flagd", Some('d')),
Spec::optional("flage", "flage", Some('e')),
Spec::boolean("flagf", "flagf", Some('f')),
Spec::boolean("flagg", "flagg", Some('g')),
Spec::boolean("flagh", "flagh", Some('h')),
])
.unwrap(),
expected_vs,
)],
"foobar",
);
}
#[test]
fn test_parse_positional_flags() {
let expected_vs = into_expected_values(vec![
("flaga", Value::Single("oof".to_owned())),
("flagb", Value::Single("rab".to_owned())),
("flagc", Value::Boolean(true.to_string())),
("flagd", Value::Boolean(false.to_string())),
("posa", Value::Repeated(vec!["foo".to_owned()])),
("posb", Value::Repeated(vec!["bar".to_owned()])),
("posc", Value::Repeated(vec!["baz".to_owned()])),
]);
parse_and_execute_test_impl(
vec![
"foobar",
"--flaga=oof",
"--flagb",
"rab",
"--flagc",
"--flagd=false",
"foo",
"bar",
"baz",
],
vec![build_test_command(
"foobar",
Specs::new(vec![
Spec::required("flaga", "flaga", Some('a'), None),
Spec::required("flagb", "flagb", Some('b'), None),
Spec::boolean("flagc", "flagc", Some('c')),
Spec::boolean("flagd", "flagd", Some('d')),
Spec::positional("posa", "posa", None, false).unwrap(),
Spec::positional("posb", "posb", None, false).unwrap(),
Spec::positional("posc", "posc", None, false).unwrap(),
])
.unwrap(),
expected_vs,
)],
"foobar",
);
}
#[test]
fn test_parse_variadic_flag_empty() {
let expected_vs = into_expected_values(vec![
("flaga", Value::Single("oof".to_owned())),
("posa", Value::Repeated(vec!["foo".to_owned()])),
("posb", Value::Repeated(vec!["bar".to_owned()])),
("posc", Value::Repeated(vec![])),
]);
parse_and_execute_test_impl(
vec!["foobar", "--flaga=oof", "foo", "bar"],
vec![build_test_command(
"foobar",
Specs::new(vec![
Spec::required("flaga", "flaga", Some('a'), None),
Spec::positional("posa", "posa", None, false).unwrap(),
Spec::positional("posb", "posb", None, false).unwrap(),
Spec::positional("posc", "posc", None, true).unwrap(),
])
.unwrap(),
expected_vs,
)],
"foobar",
);
}
#[test]
fn test_parse_variadic_flag_many() {
let expected_vs = into_expected_values(vec![
("flaga", Value::Single("oof".to_owned())),
("posa", Value::Repeated(vec!["foo".to_owned()])),
("posb", Value::Repeated(vec!["bar".to_owned()])),
(
"posc",
Value::Repeated(vec!["baz".to_owned(), "quux".to_owned()]),
),
]);
parse_and_execute_test_impl(
vec!["foobar", "--flaga=oof", "foo", "bar", "baz", "quux"],
vec![build_test_command(
"foobar",
Specs::new(vec![
Spec::required("flaga", "flaga", Some('a'), None),
Spec::positional("posa", "posa", None, false).unwrap(),
Spec::positional("posb", "posb", None, false).unwrap(),
Spec::positional("posc", "posc", None, true).unwrap(),
])
.unwrap(),
expected_vs,
)],
"foobar",
);
}
#[test]
fn test_parse_default_positional_values() {
let expected_vs = into_expected_values(vec![
("flaga", Value::Single("oof".to_owned())),
("posa", Value::Repeated(vec!["foo".to_owned()])),
("posb", Value::Repeated(vec!["dvb".to_owned()])),
("posc", Value::Repeated(vec!["dvc".to_owned()])),
]);
parse_and_execute_test_impl(
vec!["foobar", "--flaga=oof", "foo"],
vec![build_test_command(
"foobar",
Specs::new(vec![
Spec::required("flaga", "flaga", Some('a'), None),
Spec::positional("posa", "posa", Some(&["dva"]), false).unwrap(),
Spec::positional("posb", "posb", Some(&["dvb"]), false).unwrap(),
Spec::positional("posc", "posc", Some(&["dvc"]), false).unwrap(),
])
.unwrap(),
expected_vs,
)],
"foobar",
);
}
#[test]
fn test_parse_default_variadic_values() {
let expected_vs = into_expected_values(vec![
("flaga", Value::Single("oof".to_owned())),
("posa", Value::Repeated(vec!["foo".to_owned()])),
("posb", Value::Repeated(vec!["dvb".to_owned()])),
(
"posc",
Value::Repeated(vec!["dvc1".to_owned(), "dvc2".to_owned()]),
),
]);
parse_and_execute_test_impl(
vec!["foobar", "--flaga=oof", "foo"],
vec![build_test_command(
"foobar",
Specs::new(vec![
Spec::required("flaga", "flaga", Some('a'), None),
Spec::positional("posa", "posa", Some(&["dva"]), false).unwrap(),
Spec::positional("posb", "posb", Some(&["dvb"]), false).unwrap(),
Spec::positional("posc", "posc", Some(&["dvc1", "dvc2"]), true).unwrap(),
])
.unwrap(),
expected_vs,
)],
"foobar",
);
}