| use arbitrary::{Arbitrary, Unstructured}; | |
| use expect_test::{Expect, expect}; | |
| use intern::Symbol; | |
| use syntax::{AstNode, Edition, ast}; | |
| use syntax_bridge::{ | |
| DocCommentDesugarMode, | |
| dummy_test_span_utils::{DUMMY, DummyTestSpanMap}, | |
| syntax_node_to_token_tree, | |
| }; | |
| use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr}; | |
| #[track_caller] | |
| fn parse_ast_cfg(pred: ast::CfgPredicate) -> CfgExpr { | |
| CfgExpr::parse_from_ast(pred) | |
| } | |
| #[track_caller] | |
| fn assert_parse_result(input: &str, expected: CfgExpr) { | |
| let source_file = ast::SourceFile::parse(input, Edition::CURRENT).syntax_node(); | |
| let pred_ast = source_file.descendants().find_map(ast::CfgPredicate::cast).unwrap(); | |
| let tt = syntax_node_to_token_tree( | |
| pred_ast.syntax(), | |
| DummyTestSpanMap, | |
| DUMMY, | |
| DocCommentDesugarMode::ProcMacro, | |
| ); | |
| let cfg = CfgExpr::parse(&tt); | |
| assert_eq!(cfg, expected); | |
| let cfg = parse_ast_cfg(pred_ast); | |
| assert_eq!(cfg, expected); | |
| } | |
| #[track_caller] | |
| fn check_dnf(input: &str, expect: Expect) { | |
| let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); | |
| let pred_ast = source_file.syntax().descendants().find_map(ast::CfgPredicate::cast).unwrap(); | |
| let tt = syntax_node_to_token_tree( | |
| pred_ast.syntax(), | |
| DummyTestSpanMap, | |
| DUMMY, | |
| DocCommentDesugarMode::ProcMacro, | |
| ); | |
| let cfg = CfgExpr::parse(&tt); | |
| let actual = format!("#![cfg({})]", DnfExpr::new(&cfg)); | |
| expect.assert_eq(&actual); | |
| let cfg = parse_ast_cfg(pred_ast); | |
| let actual = format!("#![cfg({})]", DnfExpr::new(&cfg)); | |
| expect.assert_eq(&actual); | |
| } | |
| #[track_caller] | |
| fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) { | |
| let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); | |
| let pred_ast = source_file.syntax().descendants().find_map(ast::CfgPredicate::cast).unwrap(); | |
| let tt = syntax_node_to_token_tree( | |
| pred_ast.syntax(), | |
| DummyTestSpanMap, | |
| DUMMY, | |
| DocCommentDesugarMode::ProcMacro, | |
| ); | |
| let cfg = CfgExpr::parse(&tt); | |
| let dnf = DnfExpr::new(&cfg); | |
| let why_inactive = dnf.why_inactive(opts).unwrap().to_string(); | |
| expect.assert_eq(&why_inactive); | |
| let cfg = parse_ast_cfg(pred_ast); | |
| let dnf = DnfExpr::new(&cfg); | |
| let why_inactive = dnf.why_inactive(opts).unwrap().to_string(); | |
| expect.assert_eq(&why_inactive); | |
| } | |
| #[track_caller] | |
| fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) { | |
| let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); | |
| let pred_ast = source_file.syntax().descendants().find_map(ast::CfgPredicate::cast).unwrap(); | |
| let tt = syntax_node_to_token_tree( | |
| pred_ast.syntax(), | |
| DummyTestSpanMap, | |
| DUMMY, | |
| DocCommentDesugarMode::ProcMacro, | |
| ); | |
| let cfg = CfgExpr::parse(&tt); | |
| let dnf = DnfExpr::new(&cfg); | |
| let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::<Vec<_>>(); | |
| assert_eq!(hints, expected_hints); | |
| let cfg = parse_ast_cfg(pred_ast); | |
| let dnf = DnfExpr::new(&cfg); | |
| let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::<Vec<_>>(); | |
| assert_eq!(hints, expected_hints); | |
| } | |
| #[test] | |
| fn test_cfg_expr_parser() { | |
| assert_parse_result("#![cfg(foo)]", CfgAtom::Flag(Symbol::intern("foo")).into()); | |
| assert_parse_result("#![cfg(foo,)]", CfgAtom::Flag(Symbol::intern("foo")).into()); | |
| assert_parse_result( | |
| "#![cfg(not(foo))]", | |
| CfgExpr::Not(Box::new(CfgAtom::Flag(Symbol::intern("foo")).into())), | |
| ); | |
| assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid); | |
| // Only take the first | |
| assert_parse_result( | |
| r#"#![cfg(foo, bar = "baz")]"#, | |
| CfgAtom::Flag(Symbol::intern("foo")).into(), | |
| ); | |
| assert_parse_result( | |
| r#"#![cfg(all(foo, bar = "baz"))]"#, | |
| CfgExpr::All( | |
| vec![ | |
| CfgAtom::Flag(Symbol::intern("foo")).into(), | |
| CfgAtom::KeyValue { key: Symbol::intern("bar"), value: Symbol::intern("baz") } | |
| .into(), | |
| ] | |
| .into_boxed_slice(), | |
| ), | |
| ); | |
| } | |
| #[test] | |
| fn smoke() { | |
| check_dnf("#![cfg(test)]", expect![[r#"#![cfg(test)]"#]]); | |
| check_dnf("#![cfg(not(test))]", expect![[r#"#![cfg(not(test))]"#]]); | |
| check_dnf("#![cfg(not(not(test)))]", expect![[r#"#![cfg(test)]"#]]); | |
| check_dnf("#![cfg(all(a, b))]", expect![[r#"#![cfg(all(a, b))]"#]]); | |
| check_dnf("#![cfg(any(a, b))]", expect![[r#"#![cfg(any(a, b))]"#]]); | |
| check_dnf("#![cfg(not(a))]", expect![[r#"#![cfg(not(a))]"#]]); | |
| } | |
| #[test] | |
| fn distribute() { | |
| check_dnf("#![cfg(all(any(a, b), c))]", expect![[r#"#![cfg(any(all(a, c), all(b, c)))]"#]]); | |
| check_dnf("#![cfg(all(c, any(a, b)))]", expect![[r#"#![cfg(any(all(c, a), all(c, b)))]"#]]); | |
| check_dnf( | |
| "#![cfg(all(any(a, b), any(c, d)))]", | |
| expect![[r#"#![cfg(any(all(a, c), all(a, d), all(b, c), all(b, d)))]"#]], | |
| ); | |
| check_dnf( | |
| "#![cfg(all(any(a, b, c), any(d, e, f), g))]", | |
| expect![[ | |
| 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)))]"# | |
| ]], | |
| ); | |
| } | |
| #[test] | |
| fn demorgan() { | |
| check_dnf("#![cfg(not(all(a, b)))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]); | |
| check_dnf("#![cfg(not(any(a, b)))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]); | |
| check_dnf("#![cfg(not(all(not(a), b)))]", expect![[r#"#![cfg(any(a, not(b)))]"#]]); | |
| check_dnf("#![cfg(not(any(a, not(b))))]", expect![[r#"#![cfg(all(not(a), b))]"#]]); | |
| } | |
| #[test] | |
| fn nested() { | |
| check_dnf("#![cfg(all(any(a), not(all(any(b)))))]", expect![[r#"#![cfg(all(a, not(b)))]"#]]); | |
| check_dnf("#![cfg(any(any(a, b)))]", expect![[r#"#![cfg(any(a, b))]"#]]); | |
| check_dnf("#![cfg(not(any(any(a, b))))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]); | |
| check_dnf("#![cfg(all(all(a, b)))]", expect![[r#"#![cfg(all(a, b))]"#]]); | |
| check_dnf("#![cfg(not(all(all(a, b))))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]); | |
| } | |
| #[test] | |
| fn regression() { | |
| check_dnf("#![cfg(all(not(not(any(any(any()))))))]", expect![[r##"#![cfg(any())]"##]]); | |
| check_dnf("#![cfg(all(any(all(any()))))]", expect![[r##"#![cfg(any())]"##]]); | |
| check_dnf("#![cfg(all(all(any())))]", expect![[r##"#![cfg(any())]"##]]); | |
| check_dnf("#![cfg(all(all(any(), x)))]", expect![[r##"#![cfg(any())]"##]]); | |
| check_dnf("#![cfg(all(all(any()), x))]", expect![[r##"#![cfg(any())]"##]]); | |
| check_dnf("#![cfg(all(all(any(x))))]", expect![[r##"#![cfg(x)]"##]]); | |
| check_dnf("#![cfg(all(all(any(x), x)))]", expect![[r##"#![cfg(all(x, x))]"##]]); | |
| } | |
| #[test] | |
| fn hints() { | |
| let mut opts = CfgOptions::default(); | |
| check_enable_hints("#![cfg(test)]", &opts, &["enable test"]); | |
| check_enable_hints("#![cfg(not(test))]", &opts, &[]); | |
| check_enable_hints("#![cfg(any(a, b))]", &opts, &["enable a", "enable b"]); | |
| check_enable_hints("#![cfg(any(b, a))]", &opts, &["enable b", "enable a"]); | |
| check_enable_hints("#![cfg(all(a, b))]", &opts, &["enable a and b"]); | |
| opts.insert_atom(Symbol::intern("test")); | |
| check_enable_hints("#![cfg(test)]", &opts, &[]); | |
| check_enable_hints("#![cfg(not(test))]", &opts, &["disable test"]); | |
| opts.insert_atom(Symbol::intern("a")); | |
| opts.insert_atom(Symbol::intern("b")); | |
| check_enable_hints("#![cfg(all(not(a), not(b)))]", &opts, &["disable a and b"]); | |
| } | |
| /// Tests that we don't suggest hints for cfgs that express an inconsistent formula. | |
| #[test] | |
| fn hints_impossible() { | |
| let mut opts = CfgOptions::default(); | |
| check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]); | |
| opts.insert_atom(Symbol::intern("test")); | |
| check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]); | |
| } | |
| #[test] | |
| fn why_inactive() { | |
| let mut opts = CfgOptions::default(); | |
| opts.insert_atom(Symbol::intern("test")); | |
| opts.insert_atom(Symbol::intern("test2")); | |
| check_why_inactive("#![cfg(a)]", &opts, expect![["a is disabled"]]); | |
| check_why_inactive("#![cfg(not(test))]", &opts, expect![["test is enabled"]]); | |
| check_why_inactive( | |
| "#![cfg(all(not(test), not(test2)))]", | |
| &opts, | |
| expect![["test and test2 are enabled"]], | |
| ); | |
| check_why_inactive("#![cfg(all(a, b))]", &opts, expect![["a and b are disabled"]]); | |
| check_why_inactive( | |
| "#![cfg(all(not(test), a))]", | |
| &opts, | |
| expect![["test is enabled and a is disabled"]], | |
| ); | |
| check_why_inactive( | |
| "#![cfg(all(not(test), test2, a))]", | |
| &opts, | |
| expect![["test is enabled and a is disabled"]], | |
| ); | |
| check_why_inactive( | |
| "#![cfg(all(not(test), not(test2), a))]", | |
| &opts, | |
| expect![["test and test2 are enabled and a is disabled"]], | |
| ); | |
| } | |
| #[test] | |
| fn proptest() { | |
| const REPEATS: usize = 512; | |
| let mut rng = oorandom::Rand32::new(123456789); | |
| let mut buf = Vec::new(); | |
| for _ in 0..REPEATS { | |
| buf.clear(); | |
| while buf.len() < 512 { | |
| buf.extend(rng.rand_u32().to_ne_bytes()); | |
| } | |
| let mut u = Unstructured::new(&buf); | |
| let cfg = CfgExpr::arbitrary(&mut u).unwrap(); | |
| DnfExpr::new(&cfg); | |
| } | |
| } |