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