compiler_test_derive/
lib.rs1#[cfg(proc_macro)]
2extern crate proc_macro;
3
4use proc_macro2::TokenStream;
5use quote::quote;
6use std::path::Path;
7use syn::*;
8
9mod ignores;
10
11#[cfg(proc_macro)]
12#[proc_macro_attribute]
13pub fn compiler_test(
14 attrs: proc_macro::TokenStream,
15 input: proc_macro::TokenStream,
16) -> proc_macro::TokenStream {
17 compiler_test_impl(attrs.into(), input.into()).into()
18}
19
20fn compiler_test_impl(attrs: TokenStream, input: TokenStream) -> TokenStream {
21 let path: Option<ExprPath> = parse2::<ExprPath>(attrs).ok();
22 let mut my_fn: ItemFn = match syn::parse2(input) {
23 Ok(f) => f,
24 Err(e) => return e.into_compile_error(),
25 };
26 let fn_name = &my_fn.sig.ident;
27
28 let tests_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
32 .ancestors()
33 .nth(2)
34 .unwrap();
35 let ignores_txt_path = tests_dir.join("ignores.txt");
36
37 let ignores = crate::ignores::Ignores::build_from_path(ignores_txt_path);
38
39 let should_ignore = |test_name: &str, compiler_name: &str, engine_name: &str| {
40 let compiler_name = compiler_name.to_lowercase();
41 let engine_name = engine_name.to_lowercase();
42 let full_path = format!(
45 "{}::{}::{}::{}",
46 quote! { #path },
47 test_name,
48 compiler_name,
49 engine_name
50 )
51 .replace(' ', "");
52
53 ignores.should_ignore_host(&engine_name, &compiler_name, &full_path)
55 };
56 let construct_engine_test = |func: &::syn::ItemFn,
57 compiler_name: &str,
58 engine_name: &str,
59 engine_feature_name: &str|
60 -> ::proc_macro2::TokenStream {
61 let config_compiler = ::quote::format_ident!("{}", compiler_name);
62 let test_name = ::quote::format_ident!("{}", engine_name.to_lowercase());
63 let mut new_sig = func.sig.clone();
64 let attrs = func
65 .attrs
66 .clone()
67 .iter()
68 .fold(quote! {}, |acc, new| quote! {#acc #new});
69 new_sig.ident = test_name;
70 new_sig.inputs = ::syn::punctuated::Punctuated::new();
71 let f = quote! {
72 #[test_log::test]
73 #attrs
74 #[cfg(feature = #engine_feature_name)]
75 #new_sig {
76 #fn_name(crate::Config::new(crate::Compiler::#config_compiler))
77 }
78 };
79 if should_ignore(
80 &func.sig.ident.to_string().replace("r#", ""),
81 compiler_name,
82 engine_name,
83 ) && !cfg!(test)
84 {
85 quote! {
86 #[ignore]
87 #f
88 }
89 } else {
90 f
91 }
92 };
93
94 let construct_compiler_test =
95 |func: &::syn::ItemFn, compiler_name: &str| -> ::proc_macro2::TokenStream {
96 let mod_name = ::quote::format_ident!("{}", compiler_name.to_lowercase());
97 let universal_engine_test = construct_engine_test(
98 func,
99 compiler_name,
100 compiler_name,
101 &compiler_name.to_lowercase(),
102 );
103 let compiler_name_lowercase = compiler_name.to_lowercase();
104
105 quote! {
106 #[cfg(feature = #compiler_name_lowercase)]
107 mod #mod_name {
108 use super::*;
109
110 #universal_engine_test
111 }
112 }
113 };
114
115 let singlepass_compiler_test = construct_compiler_test(&my_fn, "Singlepass");
116 let cranelift_compiler_test = construct_compiler_test(&my_fn, "Cranelift");
117 let llvm_compiler_test = construct_compiler_test(&my_fn, "LLVM");
118
119 my_fn.attrs = vec![];
121
122 let x = quote! {
123 #[cfg(test)]
124 mod #fn_name {
125 use super::*;
126
127 #[allow(unused)]
128 #my_fn
129
130 #singlepass_compiler_test
131 #cranelift_compiler_test
132 #llvm_compiler_test
133 }
134 };
135
136 #[allow(clippy::useless_conversion)]
137 x.into()
138}
139
140#[cfg(test)]
141mod tests;