use cargo_platform::{Cfg, CfgExpr, Platform}; use std::fmt; use std::str::FromStr; macro_rules! c { ($a:ident) => { Cfg::Name(stringify!($a).to_string()) }; ($a:ident = $e:expr) => { Cfg::KeyPair(stringify!($a).to_string(), $e.to_string()) }; } macro_rules! e { (any($($t:tt),*)) => (CfgExpr::Any(vec![$(e!($t)),*])); (all($($t:tt),*)) => (CfgExpr::All(vec![$(e!($t)),*])); (not($($t:tt)*)) => (CfgExpr::Not(Box::new(e!($($t)*)))); (($($t:tt)*)) => (e!($($t)*)); ($($t:tt)*) => (CfgExpr::Value(c!($($t)*))); } fn good(s: &str, expected: T) where T: FromStr + PartialEq + fmt::Debug, T::Err: fmt::Display, { let c = match T::from_str(s) { Ok(c) => c, Err(e) => panic!("failed to parse `{}`: {}", s, e), }; assert_eq!(c, expected); } fn bad(s: &str, err: &str) where T: FromStr + fmt::Display, T::Err: fmt::Display, { let e = match T::from_str(s) { Ok(cfg) => panic!("expected `{}` to not parse but got {}", s, cfg), Err(e) => e.to_string(), }; assert!( e.contains(err), "when parsing `{}`,\n\"{}\" not contained \ inside: {}", s, err, e ); } #[test] fn cfg_syntax() { good("foo", c!(foo)); good("_bar", c!(_bar)); good(" foo", c!(foo)); good(" foo ", c!(foo)); good(" foo = \"bar\"", c!(foo = "bar")); good("foo=\"\"", c!(foo = "")); good(" foo=\"3\" ", c!(foo = "3")); good("foo = \"3 e\"", c!(foo = "3 e")); } #[test] fn cfg_syntax_bad() { bad::("", "but cfg expression ended"); bad::(" ", "but cfg expression ended"); bad::("\t", "unexpected character"); bad::("7", "unexpected character"); bad::("=", "expected identifier"); bad::(",", "expected identifier"); bad::("(", "expected identifier"); bad::("foo (", "unexpected content `(` found after cfg expression"); bad::("bar =", "expected a string"); bad::("bar = \"", "unterminated string"); bad::( "foo, bar", "unexpected content `, bar` found after cfg expression", ); } #[test] fn cfg_expr() { good("foo", e!(foo)); good("_bar", e!(_bar)); good(" foo", e!(foo)); good(" foo ", e!(foo)); good(" foo = \"bar\"", e!(foo = "bar")); good("foo=\"\"", e!(foo = "")); good(" foo=\"3\" ", e!(foo = "3")); good("foo = \"3 e\"", e!(foo = "3 e")); good("all()", e!(all())); good("all(a)", e!(all(a))); good("all(a, b)", e!(all(a, b))); good("all(a, )", e!(all(a))); good("not(a = \"b\")", e!(not(a = "b"))); good("not(all(a))", e!(not(all(a)))); } #[test] fn cfg_expr_bad() { bad::(" ", "but cfg expression ended"); bad::(" all", "expected `(`"); bad::("all(a", "expected `)`"); bad::("not", "expected `(`"); bad::("not(a", "expected `)`"); bad::("a = ", "expected a string"); bad::("all(not())", "expected identifier"); bad::( "foo(a)", "unexpected content `(a)` found after cfg expression", ); } #[test] fn cfg_matches() { assert!(e!(foo).matches(&[c!(bar), c!(foo), c!(baz)])); assert!(e!(any(foo)).matches(&[c!(bar), c!(foo), c!(baz)])); assert!(e!(any(foo, bar)).matches(&[c!(bar)])); assert!(e!(any(foo, bar)).matches(&[c!(foo)])); assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)])); assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)])); assert!(e!(not(foo)).matches(&[c!(bar)])); assert!(e!(not(foo)).matches(&[])); assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(bar)])); assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo), c!(bar)])); assert!(!e!(foo).matches(&[])); assert!(!e!(foo).matches(&[c!(bar)])); assert!(!e!(foo).matches(&[c!(fo)])); assert!(!e!(any(foo)).matches(&[])); assert!(!e!(any(foo)).matches(&[c!(bar)])); assert!(!e!(any(foo)).matches(&[c!(bar), c!(baz)])); assert!(!e!(all(foo)).matches(&[c!(bar), c!(baz)])); assert!(!e!(all(foo, bar)).matches(&[c!(bar)])); assert!(!e!(all(foo, bar)).matches(&[c!(foo)])); assert!(!e!(all(foo, bar)).matches(&[])); assert!(!e!(not(bar)).matches(&[c!(bar)])); assert!(!e!(not(bar)).matches(&[c!(baz), c!(bar)])); assert!(!e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo)])); } #[test] fn bad_target_name() { bad::( "any(cfg(unix), cfg(windows))", "failed to parse `any(cfg(unix), cfg(windows))` as a cfg expression: \ invalid target specifier: unexpected `(` character, \ cfg expressions must start with `cfg(`", ); bad::( "!foo", "failed to parse `!foo` as a cfg expression: \ invalid target specifier: unexpected character ! in target name", ); } #[test] fn round_trip_platform() { fn rt(s: &str) { let p = Platform::from_str(s).unwrap(); let s2 = p.to_string(); let p2 = Platform::from_str(&s2).unwrap(); assert_eq!(p, p2); } rt("x86_64-apple-darwin"); rt("foo"); rt("cfg(windows)"); rt("cfg(target_os = \"windows\")"); rt( "cfg(any(all(any(target_os = \"android\", target_os = \"linux\"), \ any(target_arch = \"aarch64\", target_arch = \"arm\", target_arch = \"powerpc64\", \ target_arch = \"x86\", target_arch = \"x86_64\")), \ all(target_os = \"freebsd\", target_arch = \"x86_64\")))", ); } #[test] fn check_cfg_attributes() { fn ok(s: &str) { let p = Platform::Cfg(s.parse().unwrap()); let mut warnings = Vec::new(); p.check_cfg_attributes(&mut warnings); assert!( warnings.is_empty(), "Expected no warnings but got: {:?}", warnings, ); } fn warn(s: &str, names: &[&str]) { let p = Platform::Cfg(s.parse().unwrap()); let mut warnings = Vec::new(); p.check_cfg_attributes(&mut warnings); assert_eq!( warnings.len(), names.len(), "Expecter warnings about {:?} but got {:?}", names, warnings, ); for (name, warning) in names.iter().zip(warnings.iter()) { assert!( warning.contains(name), "Expected warning about '{}' but got: {}", name, warning, ); } } ok("unix"); ok("windows"); ok("any(not(unix), windows)"); ok("foo"); ok("target_arch = \"abc\""); ok("target_feature = \"abc\""); ok("target_os = \"abc\""); ok("target_family = \"abc\""); ok("target_env = \"abc\""); ok("target_endian = \"abc\""); ok("target_pointer_width = \"abc\""); ok("target_vendor = \"abc\""); ok("bar = \"def\""); warn("test", &["test"]); warn("debug_assertions", &["debug_assertions"]); warn("proc_macro", &["proc_macro"]); warn("feature = \"abc\"", &["feature"]); warn("any(not(debug_assertions), windows)", &["debug_assertions"]); warn( "any(not(feature = \"def\"), target_arch = \"abc\")", &["feature"], ); warn( "any(not(target_os = \"windows\"), proc_macro)", &["proc_macro"], ); warn( "any(not(feature = \"windows\"), proc_macro)", &["feature", "proc_macro"], ); warn( "all(not(debug_assertions), any(windows, proc_macro))", &["debug_assertions", "proc_macro"], ); }