compiler_test_derive/
lib.rs

1#[cfg(not(test))]
2extern crate proc_macro;
3#[cfg(not(test))]
4use proc_macro::TokenStream;
5#[cfg(test)]
6use proc_macro2::TokenStream;
7use quote::quote;
8use std::path::PathBuf;
9#[cfg(not(test))]
10use syn::parse;
11#[cfg(test)]
12use syn::parse2 as parse;
13use syn::*;
14
15mod ignores;
16
17// Reimplement parse_macro_input to use the imported `parse`
18// function. This way parse_macro_input will parse a TokenStream2 when
19// unit-testing.
20macro_rules! parse_macro_input {
21    (
22        $token_stream:ident as $T:ty
23    ) => {
24        match parse::<$T>($token_stream) {
25            Ok(data) => data,
26            Err(err) => {
27                return TokenStream::from(err.to_compile_error());
28            }
29        }
30    };
31
32    (
33        $token_stream:ident
34    ) => {
35        parse_macro_input!($token_stream as _)
36    };
37}
38
39#[proc_macro_attribute]
40pub fn compiler_test(attrs: TokenStream, input: TokenStream) -> TokenStream {
41    let path: Option<ExprPath> = parse::<ExprPath>(attrs).ok();
42    let mut my_fn: ItemFn = parse_macro_input!(input as ItemFn);
43    let fn_name = my_fn.sig.ident.clone();
44
45    // Let's build the ignores to append an `#[ignore]` macro to the
46    // autogenerated tests in case the test appears in the `ignores.txt` path;
47
48    let mut ignores_txt_path = PathBuf::new();
49    ignores_txt_path.push(env!("CARGO_MANIFEST_DIR"));
50    ignores_txt_path.push("../../ignores.txt");
51
52    let ignores = crate::ignores::Ignores::build_from_path(ignores_txt_path);
53
54    let should_ignore = |test_name: &str, compiler_name: &str, engine_name: &str| {
55        let compiler_name = compiler_name.to_lowercase();
56        let engine_name = engine_name.to_lowercase();
57        // We construct the path manually because we can't get the
58        // source_file location from the `Span` (it's only available in nightly)
59        let full_path = format!(
60            "{}::{}::{}::{}",
61            quote! { #path },
62            test_name,
63            compiler_name,
64            engine_name
65        )
66        .replace(" ", "");
67        let should_ignore = ignores.should_ignore_host(&engine_name, &compiler_name, &full_path);
68        // println!("{} -> Should ignore: {}", full_path, should_ignore);
69        return should_ignore;
70    };
71    let construct_engine_test = |func: &::syn::ItemFn,
72                                 compiler_name: &str,
73                                 engine_name: &str,
74                                 engine_feature_name: &str|
75     -> ::proc_macro2::TokenStream {
76        let config_compiler = ::quote::format_ident!("{}", compiler_name);
77        let config_engine = ::quote::format_ident!("{}", engine_name);
78        let test_name = ::quote::format_ident!("{}", engine_name.to_lowercase());
79        let mut new_sig = func.sig.clone();
80        let attrs = func
81            .attrs
82            .clone()
83            .iter()
84            .fold(quote! {}, |acc, new| quote! {#acc #new});
85        new_sig.ident = test_name;
86        new_sig.inputs = ::syn::punctuated::Punctuated::new();
87        let f = quote! {
88            #[test_log::test]
89            #attrs
90            #[cfg(feature = #engine_feature_name)]
91            #new_sig {
92                #fn_name(crate::Config::new(crate::Engine::#config_engine, crate::Compiler::#config_compiler))
93            }
94        };
95        if should_ignore(
96            &func.sig.ident.to_string().replace("r#", ""),
97            compiler_name,
98            engine_name,
99        ) && !cfg!(test)
100        {
101            quote! {
102                #[ignore]
103                #f
104            }
105        } else {
106            f
107        }
108    };
109
110    let construct_compiler_test =
111        |func: &::syn::ItemFn, compiler_name: &str| -> ::proc_macro2::TokenStream {
112            let mod_name = ::quote::format_ident!("{}", compiler_name.to_lowercase());
113            let universal_engine_test =
114                construct_engine_test(func, compiler_name, "Universal", "universal");
115            let dylib_engine_test = construct_engine_test(func, compiler_name, "Dylib", "dylib");
116            let compiler_name_lowercase = compiler_name.to_lowercase();
117
118            quote! {
119                #[cfg(feature = #compiler_name_lowercase)]
120                mod #mod_name {
121                    use super::*;
122
123                    #universal_engine_test
124                    #dylib_engine_test
125                }
126            }
127        };
128
129    let singlepass_compiler_test = construct_compiler_test(&my_fn, "Singlepass");
130    let cranelift_compiler_test = construct_compiler_test(&my_fn, "Cranelift");
131    let llvm_compiler_test = construct_compiler_test(&my_fn, "LLVM");
132
133    // We remove the method decorators
134    my_fn.attrs = vec![];
135
136    let x = quote! {
137        #[cfg(test)]
138        mod #fn_name {
139            use super::*;
140
141            #[allow(unused)]
142            #my_fn
143
144            #singlepass_compiler_test
145            #cranelift_compiler_test
146            #llvm_compiler_test
147        }
148    };
149    x.into()
150}
151
152#[cfg(test)]
153mod tests;