1 use cargo_platform::{Cfg, CfgExpr, Platform};
2 use std::fmt;
3 use std::str::FromStr;
4 
5 macro_rules! c {
6     ($a:ident) => {
7         Cfg::Name(stringify!($a).to_string())
8     };
9     ($a:ident = $e:expr) => {
10         Cfg::KeyPair(stringify!($a).to_string(), $e.to_string())
11     };
12 }
13 
14 macro_rules! e {
15     (any($($t:tt),*)) => (CfgExpr::Any(vec![$(e!($t)),*]));
16     (all($($t:tt),*)) => (CfgExpr::All(vec![$(e!($t)),*]));
17     (not($($t:tt)*)) => (CfgExpr::Not(Box::new(e!($($t)*))));
18     (($($t:tt)*)) => (e!($($t)*));
19     ($($t:tt)*) => (CfgExpr::Value(c!($($t)*)));
20 }
21 
good<T>(s: &str, expected: T) where T: FromStr + PartialEq + fmt::Debug, T::Err: fmt::Display,22 fn good<T>(s: &str, expected: T)
23 where
24     T: FromStr + PartialEq + fmt::Debug,
25     T::Err: fmt::Display,
26 {
27     let c = match T::from_str(s) {
28         Ok(c) => c,
29         Err(e) => panic!("failed to parse `{}`: {}", s, e),
30     };
31     assert_eq!(c, expected);
32 }
33 
bad<T>(s: &str, err: &str) where T: FromStr + fmt::Display, T::Err: fmt::Display,34 fn bad<T>(s: &str, err: &str)
35 where
36     T: FromStr + fmt::Display,
37     T::Err: fmt::Display,
38 {
39     let e = match T::from_str(s) {
40         Ok(cfg) => panic!("expected `{}` to not parse but got {}", s, cfg),
41         Err(e) => e.to_string(),
42     };
43     assert!(
44         e.contains(err),
45         "when parsing `{}`,\n\"{}\" not contained \
46          inside: {}",
47         s,
48         err,
49         e
50     );
51 }
52 
53 #[test]
cfg_syntax()54 fn cfg_syntax() {
55     good("foo", c!(foo));
56     good("_bar", c!(_bar));
57     good(" foo", c!(foo));
58     good(" foo  ", c!(foo));
59     good(" foo  = \"bar\"", c!(foo = "bar"));
60     good("foo=\"\"", c!(foo = ""));
61     good(" foo=\"3\"      ", c!(foo = "3"));
62     good("foo = \"3 e\"", c!(foo = "3 e"));
63 }
64 
65 #[test]
cfg_syntax_bad()66 fn cfg_syntax_bad() {
67     bad::<Cfg>("", "but cfg expression ended");
68     bad::<Cfg>(" ", "but cfg expression ended");
69     bad::<Cfg>("\t", "unexpected character");
70     bad::<Cfg>("7", "unexpected character");
71     bad::<Cfg>("=", "expected identifier");
72     bad::<Cfg>(",", "expected identifier");
73     bad::<Cfg>("(", "expected identifier");
74     bad::<Cfg>("foo (", "unexpected content `(` found after cfg expression");
75     bad::<Cfg>("bar =", "expected a string");
76     bad::<Cfg>("bar = \"", "unterminated string");
77     bad::<Cfg>(
78         "foo, bar",
79         "unexpected content `, bar` found after cfg expression",
80     );
81 }
82 
83 #[test]
cfg_expr()84 fn cfg_expr() {
85     good("foo", e!(foo));
86     good("_bar", e!(_bar));
87     good(" foo", e!(foo));
88     good(" foo  ", e!(foo));
89     good(" foo  = \"bar\"", e!(foo = "bar"));
90     good("foo=\"\"", e!(foo = ""));
91     good(" foo=\"3\"      ", e!(foo = "3"));
92     good("foo = \"3 e\"", e!(foo = "3 e"));
93 
94     good("all()", e!(all()));
95     good("all(a)", e!(all(a)));
96     good("all(a, b)", e!(all(a, b)));
97     good("all(a, )", e!(all(a)));
98     good("not(a = \"b\")", e!(not(a = "b")));
99     good("not(all(a))", e!(not(all(a))));
100 }
101 
102 #[test]
cfg_expr_bad()103 fn cfg_expr_bad() {
104     bad::<CfgExpr>(" ", "but cfg expression ended");
105     bad::<CfgExpr>(" all", "expected `(`");
106     bad::<CfgExpr>("all(a", "expected `)`");
107     bad::<CfgExpr>("not", "expected `(`");
108     bad::<CfgExpr>("not(a", "expected `)`");
109     bad::<CfgExpr>("a = ", "expected a string");
110     bad::<CfgExpr>("all(not())", "expected identifier");
111     bad::<CfgExpr>(
112         "foo(a)",
113         "unexpected content `(a)` found after cfg expression",
114     );
115 }
116 
117 #[test]
cfg_matches()118 fn cfg_matches() {
119     assert!(e!(foo).matches(&[c!(bar), c!(foo), c!(baz)]));
120     assert!(e!(any(foo)).matches(&[c!(bar), c!(foo), c!(baz)]));
121     assert!(e!(any(foo, bar)).matches(&[c!(bar)]));
122     assert!(e!(any(foo, bar)).matches(&[c!(foo)]));
123     assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)]));
124     assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)]));
125     assert!(e!(not(foo)).matches(&[c!(bar)]));
126     assert!(e!(not(foo)).matches(&[]));
127     assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(bar)]));
128     assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo), c!(bar)]));
129 
130     assert!(!e!(foo).matches(&[]));
131     assert!(!e!(foo).matches(&[c!(bar)]));
132     assert!(!e!(foo).matches(&[c!(fo)]));
133     assert!(!e!(any(foo)).matches(&[]));
134     assert!(!e!(any(foo)).matches(&[c!(bar)]));
135     assert!(!e!(any(foo)).matches(&[c!(bar), c!(baz)]));
136     assert!(!e!(all(foo)).matches(&[c!(bar), c!(baz)]));
137     assert!(!e!(all(foo, bar)).matches(&[c!(bar)]));
138     assert!(!e!(all(foo, bar)).matches(&[c!(foo)]));
139     assert!(!e!(all(foo, bar)).matches(&[]));
140     assert!(!e!(not(bar)).matches(&[c!(bar)]));
141     assert!(!e!(not(bar)).matches(&[c!(baz), c!(bar)]));
142     assert!(!e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo)]));
143 }
144 
145 #[test]
bad_target_name()146 fn bad_target_name() {
147     bad::<Platform>(
148         "any(cfg(unix), cfg(windows))",
149         "failed to parse `any(cfg(unix), cfg(windows))` as a cfg expression: \
150          invalid target specifier: unexpected `(` character, \
151          cfg expressions must start with `cfg(`",
152     );
153     bad::<Platform>(
154         "!foo",
155         "failed to parse `!foo` as a cfg expression: \
156          invalid target specifier: unexpected character ! in target name",
157     );
158 }
159 
160 #[test]
round_trip_platform()161 fn round_trip_platform() {
162     fn rt(s: &str) {
163         let p = Platform::from_str(s).unwrap();
164         let s2 = p.to_string();
165         let p2 = Platform::from_str(&s2).unwrap();
166         assert_eq!(p, p2);
167     }
168     rt("x86_64-apple-darwin");
169     rt("foo");
170     rt("cfg(windows)");
171     rt("cfg(target_os = \"windows\")");
172     rt(
173         "cfg(any(all(any(target_os = \"android\", target_os = \"linux\"), \
174          any(target_arch = \"aarch64\", target_arch = \"arm\", target_arch = \"powerpc64\", \
175          target_arch = \"x86\", target_arch = \"x86_64\")), \
176          all(target_os = \"freebsd\", target_arch = \"x86_64\")))",
177     );
178 }
179 
180 #[test]
check_cfg_attributes()181 fn check_cfg_attributes() {
182     fn ok(s: &str) {
183         let p = Platform::Cfg(s.parse().unwrap());
184         let mut warnings = Vec::new();
185         p.check_cfg_attributes(&mut warnings);
186         assert!(
187             warnings.is_empty(),
188             "Expected no warnings but got: {:?}",
189             warnings,
190         );
191     }
192 
193     fn warn(s: &str, names: &[&str]) {
194         let p = Platform::Cfg(s.parse().unwrap());
195         let mut warnings = Vec::new();
196         p.check_cfg_attributes(&mut warnings);
197         assert_eq!(
198             warnings.len(),
199             names.len(),
200             "Expecter warnings about {:?} but got {:?}",
201             names,
202             warnings,
203         );
204         for (name, warning) in names.iter().zip(warnings.iter()) {
205             assert!(
206                 warning.contains(name),
207                 "Expected warning about '{}' but got: {}",
208                 name,
209                 warning,
210             );
211         }
212     }
213 
214     ok("unix");
215     ok("windows");
216     ok("any(not(unix), windows)");
217     ok("foo");
218 
219     ok("target_arch = \"abc\"");
220     ok("target_feature = \"abc\"");
221     ok("target_os = \"abc\"");
222     ok("target_family = \"abc\"");
223     ok("target_env = \"abc\"");
224     ok("target_endian = \"abc\"");
225     ok("target_pointer_width = \"abc\"");
226     ok("target_vendor = \"abc\"");
227     ok("bar = \"def\"");
228 
229     warn("test", &["test"]);
230     warn("debug_assertions", &["debug_assertions"]);
231     warn("proc_macro", &["proc_macro"]);
232     warn("feature = \"abc\"", &["feature"]);
233 
234     warn("any(not(debug_assertions), windows)", &["debug_assertions"]);
235     warn(
236         "any(not(feature = \"def\"), target_arch = \"abc\")",
237         &["feature"],
238     );
239     warn(
240         "any(not(target_os = \"windows\"), proc_macro)",
241         &["proc_macro"],
242     );
243     warn(
244         "any(not(feature = \"windows\"), proc_macro)",
245         &["feature", "proc_macro"],
246     );
247     warn(
248         "all(not(debug_assertions), any(windows, proc_macro))",
249         &["debug_assertions", "proc_macro"],
250     );
251 }
252