compiler_test_derive/
lib.rs

1#[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's build the ignores to append an `#[ignore]` macro to the
29    // autogenerated tests in case the test appears in the `ignores.txt` path;
30
31    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        // We construct the path manually because we can't get the
43        // source_file location from the `Span` (it's only available in nightly)
44        let full_path = format!(
45            "{}::{}::{}::{}",
46            quote! { #path },
47            test_name,
48            compiler_name,
49            engine_name
50        )
51        .replace(' ', "");
52
53        // println!("{} -> Should ignore: {}", full_path, should_ignore);
54        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    // We remove the method decorators
120    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;