1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
#[macro_export]
// Let the user format the help and parse it from that string into arguments to create the unit test
macro_rules! cli_examples {
($( [ $($description:ident)* => $command:tt $args:expr $( => $output:expr )? ] )*) => {
#[cfg(test)]
mod cli_examples {
use $crate::serial_test;
$(
$crate::paste::paste! {
#[test]
#[serial_test::serial]
#[allow(unreachable_code)]
fn [<$($description:lower _)*:snake example>] () {
let mut proc = std::process::Command::new("cargo");
proc.arg("run");
proc.arg("--bin");
proc.arg(if stringify!($command) == "forc" {
"forc".to_owned()
} else {
format!("forc-{}", stringify!($command))
});
proc.arg("--");
super::parse_args($args).into_iter().for_each(|arg| {
proc.arg(arg);
});
let path = std::path::Path::new("tests");
if path.is_dir() {
// a tests folder exists, move the cwd of the process to
// be executed there. In that folder all files needed to
// run the cmd should be stored
proc.current_dir(path);
}
let output = proc.output().expect(stringify!($command));
$(
let expected_output = $crate::Regex::new($output).expect("valid regex");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
expected_output.is_match(&stdout) ||
expected_output.is_match(&stderr),
"expected_output: {}\nStdOut:\n{}\nStdErr:\n{}\n",
expected_output,
stdout,
stderr,
);
return;
)?
// We don't know what to get or how to parse the output, all
// we care is to get a valid exit code
assert!(output.status.success(), "{}: {:?}", stringify!($($description)*), output);
}
}
)*
}
#[cfg(test)]
fn parse_args(input: &str) -> Vec<String> {
let mut chars = input.chars().peekable().into_iter();
let mut args = vec![];
loop {
let character = if let Some(c) = chars.next() { c } else { break };
match character {
' ' | '\\' | '\t' | '\n' => loop {
match chars.peek() {
Some(' ') | Some('\t') | Some('\n') => chars.next(),
_ => break,
};
},
'=' => {
args.push("=".to_string());
}
'"' | '\'' => {
let end_character = character;
let mut current_word = String::new();
loop {
match chars.peek() {
Some(character) => {
if *character == end_character {
let _ = chars.next();
args.push(current_word);
break;
} else if *character == '\\' {
let _ = chars.next();
if let Some(character) = chars.next() {
current_word.push(character);
}
} else {
current_word.push(*character);
chars.next();
}
}
None => {
break;
}
}
}
}
character => {
let mut current_word = character.to_string();
loop {
match chars.peek() {
Some(' ') | Some('\t') | Some('\n') | Some('=') | Some('\'')
| Some('"') | None => {
args.push(current_word);
break;
}
Some(character) => {
current_word.push(*character);
chars.next();
}
}
}
}
}
}
args
}
fn help() -> &'static str {
Box::leak(format!("EXAMPLES:\n{}", examples()).into_boxed_str())
}
pub fn examples() -> &'static str {
Box::leak( [
$(
$crate::paste::paste! {
if stringify!($command) == "forc" {
format!(" #{}\n forc {}\n\n", stringify!($($description)*), $args )
} else {
format!(" #{}\n forc {} {}\n\n", stringify!($($description)*), stringify!($command), $args )
}
},
)*
].concat().into_boxed_str())
}
}
}