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
use std::borrow::Cow; use serde_json; /// Easily construct an `Assert` with a custom command. /// /// Make sure to include the crate as `#[macro_use] extern crate assert_cli;` if /// you want to use this macro. /// /// # Examples /// /// To test that our very complex cli applications succeeds and prints some /// text to stdout that contains /// /// ```plain /// No errors whatsoever /// ``` /// /// ..., you would call it like this: /// /// ```rust /// #[macro_use] extern crate assert_cli; /// # fn main() { /// assert_cmd!(echo "Launch sequence initiated.\nNo errors whatsoever!\n") /// .succeeds() /// .stdout().contains("No errors whatsoever") /// .unwrap(); /// # } /// ``` /// /// The macro will try to convert its arguments as strings, but is limited by /// Rust's default tokenizer, e.g., you always need to quote CLI arguments /// like `"--verbose"`. #[macro_export] macro_rules! assert_cmd { ($($x:tt)+) => {{ $(__assert_single_token_expression!(@CHECK $x);)* $crate::Assert::command( &[$( $crate::flatten_escaped_string(stringify!($x)).as_ref() ),*] ) }} } /// Deserialize a JSON-encoded `String`. /// /// # Panics /// /// If `x` can not be decoded as `String`. #[doc(hidden)] fn deserialize_json_string(x: &str) -> String { serde_json::from_str(x) .expect(&format!("Unable to deserialize `{:?}` as string.", x)) } /// Deserialize a JSON-encoded `String`. /// /// # Panics /// /// If `x` can not be decoded as `String`. #[doc(hidden)] pub fn flatten_escaped_string(x: &str) -> Cow<str> { if x.starts_with('"') && x.ends_with('"') { Cow::Owned(deserialize_json_string(x)) } else { Cow::Borrowed(x) } } /// Inspect a single token and decide if it is safe to `stringify!`, without loosing /// information about whitespaces, to address [issue 22]. /// /// [issue 22]: https://github.com/killercup/assert_cli/issues/22 /// /// Call like `__assert_single_token_expression!(@CHECK x)`, where `x` can be any token to check. /// /// This macro will only accept single tokens, which parse as expressions, e.g. /// - strings "foo", r#"foo" /// - idents `foo`, `foo42` /// - numbers `42` /// - chars `'a'` /// /// Delimited token trees `{...}` and the like are rejected. Everything thats not an expression /// will also be rejected. #[doc(hidden)] #[macro_export] macro_rules! __assert_single_token_expression { // deny `{...}` (@CHECK {$( $x:tt )*}) => { assert_cmd!(@DENY {$( $x )*}) }; // deny `(...)` (@CHECK ($( $x:tt )*)) => { assert_cmd!(@DENY {$( $x )*}) }; // deny `[...]` (@CHECK [$( $x:tt )*]) => { assert_cmd!(@DENY {$( $x )*}) }; // only allow tokens that parse as expression (@CHECK $x:expr) => { }; // little helper (@DENY) => { }; } #[cfg(test)] mod test { use super::*; #[test] fn flatten_unquoted() { assert_eq!( flatten_escaped_string("hello world"), "hello world"); } #[test] fn flatten_quoted() { assert_eq!( flatten_escaped_string(r#""hello world""#), "hello world"); } #[test] fn flatten_escaped() { assert_eq!( flatten_escaped_string(r#""hello world \u0042 A""#), "hello world B A"); } }