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