1 use arbitrary::{Arbitrary, Unstructured};
2 use expect_test::{expect, Expect};
3 use mbe::syntax_node_to_token_tree;
4 use syntax::{ast, AstNode};
5
6 use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr};
7
assert_parse_result(input: &str, expected: CfgExpr)8 fn assert_parse_result(input: &str, expected: CfgExpr) {
9 let (tt, _) = {
10 let source_file = ast::SourceFile::parse(input).ok().unwrap();
11 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
12 syntax_node_to_token_tree(tt.syntax())
13 };
14 let cfg = CfgExpr::parse(&tt);
15 assert_eq!(cfg, expected);
16 }
17
check_dnf(input: &str, expect: Expect)18 fn check_dnf(input: &str, expect: Expect) {
19 let (tt, _) = {
20 let source_file = ast::SourceFile::parse(input).ok().unwrap();
21 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
22 syntax_node_to_token_tree(tt.syntax())
23 };
24 let cfg = CfgExpr::parse(&tt);
25 let actual = format!("#![cfg({})]", DnfExpr::new(cfg));
26 expect.assert_eq(&actual);
27 }
28
check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect)29 fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) {
30 let (tt, _) = {
31 let source_file = ast::SourceFile::parse(input).ok().unwrap();
32 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
33 syntax_node_to_token_tree(tt.syntax())
34 };
35 let cfg = CfgExpr::parse(&tt);
36 let dnf = DnfExpr::new(cfg);
37 let why_inactive = dnf.why_inactive(opts).unwrap().to_string();
38 expect.assert_eq(&why_inactive);
39 }
40
41 #[track_caller]
check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str])42 fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) {
43 let (tt, _) = {
44 let source_file = ast::SourceFile::parse(input).ok().unwrap();
45 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
46 syntax_node_to_token_tree(tt.syntax())
47 };
48 let cfg = CfgExpr::parse(&tt);
49 let dnf = DnfExpr::new(cfg);
50 let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::<Vec<_>>();
51 assert_eq!(hints, expected_hints);
52 }
53
54 #[test]
test_cfg_expr_parser()55 fn test_cfg_expr_parser() {
56 assert_parse_result("#![cfg(foo)]", CfgAtom::Flag("foo".into()).into());
57 assert_parse_result("#![cfg(foo,)]", CfgAtom::Flag("foo".into()).into());
58 assert_parse_result(
59 "#![cfg(not(foo))]",
60 CfgExpr::Not(Box::new(CfgAtom::Flag("foo".into()).into())),
61 );
62 assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
63
64 // Only take the first
65 assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgAtom::Flag("foo".into()).into());
66
67 assert_parse_result(
68 r#"#![cfg(all(foo, bar = "baz"))]"#,
69 CfgExpr::All(vec![
70 CfgAtom::Flag("foo".into()).into(),
71 CfgAtom::KeyValue { key: "bar".into(), value: "baz".into() }.into(),
72 ]),
73 );
74
75 assert_parse_result(
76 r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
77 CfgExpr::Any(vec![
78 CfgExpr::Not(Box::new(CfgExpr::Invalid)),
79 CfgExpr::All(vec![]),
80 CfgExpr::Invalid,
81 CfgAtom::KeyValue { key: "bar".into(), value: "baz".into() }.into(),
82 ]),
83 );
84 }
85
86 #[test]
smoke()87 fn smoke() {
88 check_dnf("#![cfg(test)]", expect![[r#"#![cfg(test)]"#]]);
89 check_dnf("#![cfg(not(test))]", expect![[r#"#![cfg(not(test))]"#]]);
90 check_dnf("#![cfg(not(not(test)))]", expect![[r#"#![cfg(test)]"#]]);
91
92 check_dnf("#![cfg(all(a, b))]", expect![[r#"#![cfg(all(a, b))]"#]]);
93 check_dnf("#![cfg(any(a, b))]", expect![[r#"#![cfg(any(a, b))]"#]]);
94
95 check_dnf("#![cfg(not(a))]", expect![[r#"#![cfg(not(a))]"#]]);
96 }
97
98 #[test]
distribute()99 fn distribute() {
100 check_dnf("#![cfg(all(any(a, b), c))]", expect![[r#"#![cfg(any(all(a, c), all(b, c)))]"#]]);
101 check_dnf("#![cfg(all(c, any(a, b)))]", expect![[r#"#![cfg(any(all(c, a), all(c, b)))]"#]]);
102 check_dnf(
103 "#![cfg(all(any(a, b), any(c, d)))]",
104 expect![[r#"#![cfg(any(all(a, c), all(a, d), all(b, c), all(b, d)))]"#]],
105 );
106
107 check_dnf(
108 "#![cfg(all(any(a, b, c), any(d, e, f), g))]",
109 expect![[
110 r#"#![cfg(any(all(a, d, g), all(a, e, g), all(a, f, g), all(b, d, g), all(b, e, g), all(b, f, g), all(c, d, g), all(c, e, g), all(c, f, g)))]"#
111 ]],
112 );
113 }
114
115 #[test]
demorgan()116 fn demorgan() {
117 check_dnf("#![cfg(not(all(a, b)))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
118 check_dnf("#![cfg(not(any(a, b)))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
119
120 check_dnf("#![cfg(not(all(not(a), b)))]", expect![[r#"#![cfg(any(a, not(b)))]"#]]);
121 check_dnf("#![cfg(not(any(a, not(b))))]", expect![[r#"#![cfg(all(not(a), b))]"#]]);
122 }
123
124 #[test]
nested()125 fn nested() {
126 check_dnf("#![cfg(all(any(a), not(all(any(b)))))]", expect![[r#"#![cfg(all(a, not(b)))]"#]]);
127
128 check_dnf("#![cfg(any(any(a, b)))]", expect![[r#"#![cfg(any(a, b))]"#]]);
129 check_dnf("#![cfg(not(any(any(a, b))))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
130 check_dnf("#![cfg(all(all(a, b)))]", expect![[r#"#![cfg(all(a, b))]"#]]);
131 check_dnf("#![cfg(not(all(all(a, b))))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
132 }
133
134 #[test]
regression()135 fn regression() {
136 check_dnf("#![cfg(all(not(not(any(any(any()))))))]", expect![[r##"#![cfg(any())]"##]]);
137 check_dnf("#![cfg(all(any(all(any()))))]", expect![[r##"#![cfg(any())]"##]]);
138 check_dnf("#![cfg(all(all(any())))]", expect![[r##"#![cfg(any())]"##]]);
139
140 check_dnf("#![cfg(all(all(any(), x)))]", expect![[r##"#![cfg(any())]"##]]);
141 check_dnf("#![cfg(all(all(any()), x))]", expect![[r##"#![cfg(any())]"##]]);
142 check_dnf("#![cfg(all(all(any(x))))]", expect![[r##"#![cfg(x)]"##]]);
143 check_dnf("#![cfg(all(all(any(x), x)))]", expect![[r##"#![cfg(all(x, x))]"##]]);
144 }
145
146 #[test]
hints()147 fn hints() {
148 let mut opts = CfgOptions::default();
149
150 check_enable_hints("#![cfg(test)]", &opts, &["enable test"]);
151 check_enable_hints("#![cfg(not(test))]", &opts, &[]);
152
153 check_enable_hints("#![cfg(any(a, b))]", &opts, &["enable a", "enable b"]);
154 check_enable_hints("#![cfg(any(b, a))]", &opts, &["enable b", "enable a"]);
155
156 check_enable_hints("#![cfg(all(a, b))]", &opts, &["enable a and b"]);
157
158 opts.insert_atom("test".into());
159
160 check_enable_hints("#![cfg(test)]", &opts, &[]);
161 check_enable_hints("#![cfg(not(test))]", &opts, &["disable test"]);
162 }
163
164 /// Tests that we don't suggest hints for cfgs that express an inconsistent formula.
165 #[test]
hints_impossible()166 fn hints_impossible() {
167 let mut opts = CfgOptions::default();
168
169 check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
170
171 opts.insert_atom("test".into());
172
173 check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
174 }
175
176 #[test]
why_inactive()177 fn why_inactive() {
178 let mut opts = CfgOptions::default();
179 opts.insert_atom("test".into());
180 opts.insert_atom("test2".into());
181
182 check_why_inactive("#![cfg(a)]", &opts, expect![["a is disabled"]]);
183 check_why_inactive("#![cfg(not(test))]", &opts, expect![["test is enabled"]]);
184
185 check_why_inactive(
186 "#![cfg(all(not(test), not(test2)))]",
187 &opts,
188 expect![["test and test2 are enabled"]],
189 );
190 check_why_inactive("#![cfg(all(a, b))]", &opts, expect![["a and b are disabled"]]);
191 check_why_inactive(
192 "#![cfg(all(not(test), a))]",
193 &opts,
194 expect![["test is enabled and a is disabled"]],
195 );
196 check_why_inactive(
197 "#![cfg(all(not(test), test2, a))]",
198 &opts,
199 expect![["test is enabled and a is disabled"]],
200 );
201 check_why_inactive(
202 "#![cfg(all(not(test), not(test2), a))]",
203 &opts,
204 expect![["test and test2 are enabled and a is disabled"]],
205 );
206 }
207
208 #[test]
proptest()209 fn proptest() {
210 const REPEATS: usize = 512;
211
212 let mut rng = oorandom::Rand32::new(123456789);
213 let mut buf = Vec::new();
214 for _ in 0..REPEATS {
215 buf.clear();
216 while buf.len() < 512 {
217 buf.extend(rng.rand_u32().to_ne_bytes());
218 }
219
220 let mut u = Unstructured::new(&buf);
221 let cfg = CfgExpr::arbitrary(&mut u).unwrap();
222 DnfExpr::new(cfg);
223 }
224 }
225