1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#[cfg(proc_macro)]
extern crate proc_macro;

use proc_macro2::TokenStream;
use quote::quote;
use std::path::Path;
use syn::*;

mod ignores;

#[cfg(proc_macro)]
#[proc_macro_attribute]
pub fn compiler_test(
    attrs: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    compiler_test_impl(attrs.into(), input.into()).into()
}

fn compiler_test_impl(attrs: TokenStream, input: TokenStream) -> TokenStream {
    let path: Option<ExprPath> = parse2::<ExprPath>(attrs).ok();
    let mut my_fn: ItemFn = match syn::parse2(input) {
        Ok(f) => f,
        Err(e) => return e.into_compile_error(),
    };
    let fn_name = &my_fn.sig.ident;

    // Let's build the ignores to append an `#[ignore]` macro to the
    // autogenerated tests in case the test appears in the `ignores.txt` path;

    let tests_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
        .ancestors()
        .nth(2)
        .unwrap();
    let ignores_txt_path = tests_dir.join("ignores.txt");

    let ignores = crate::ignores::Ignores::build_from_path(ignores_txt_path);

    let should_ignore = |test_name: &str, compiler_name: &str, engine_name: &str| {
        let compiler_name = compiler_name.to_lowercase();
        let engine_name = engine_name.to_lowercase();
        // We construct the path manually because we can't get the
        // source_file location from the `Span` (it's only available in nightly)
        let full_path = format!(
            "{}::{}::{}::{}",
            quote! { #path },
            test_name,
            compiler_name,
            engine_name
        )
        .replace(' ', "");

        // println!("{} -> Should ignore: {}", full_path, should_ignore);
        ignores.should_ignore_host(&engine_name, &compiler_name, &full_path)
    };
    let construct_engine_test = |func: &::syn::ItemFn,
                                 compiler_name: &str,
                                 engine_name: &str,
                                 engine_feature_name: &str|
     -> ::proc_macro2::TokenStream {
        let config_compiler = ::quote::format_ident!("{}", compiler_name);
        let test_name = ::quote::format_ident!("{}", engine_name.to_lowercase());
        let mut new_sig = func.sig.clone();
        let attrs = func
            .attrs
            .clone()
            .iter()
            .fold(quote! {}, |acc, new| quote! {#acc #new});
        new_sig.ident = test_name;
        new_sig.inputs = ::syn::punctuated::Punctuated::new();
        let f = quote! {
            #[test_log::test]
            #attrs
            #[cfg(feature = #engine_feature_name)]
            #new_sig {
                #fn_name(crate::Config::new(crate::Compiler::#config_compiler))
            }
        };
        if should_ignore(
            &func.sig.ident.to_string().replace("r#", ""),
            compiler_name,
            engine_name,
        ) && !cfg!(test)
        {
            quote! {
                #[ignore]
                #f
            }
        } else {
            f
        }
    };

    let construct_compiler_test =
        |func: &::syn::ItemFn, compiler_name: &str| -> ::proc_macro2::TokenStream {
            let mod_name = ::quote::format_ident!("{}", compiler_name.to_lowercase());
            let universal_engine_test =
                construct_engine_test(func, compiler_name, "Universal", "universal");
            let compiler_name_lowercase = compiler_name.to_lowercase();

            quote! {
                #[cfg(feature = #compiler_name_lowercase)]
                mod #mod_name {
                    use super::*;

                    #universal_engine_test
                }
            }
        };

    let singlepass_compiler_test = construct_compiler_test(&my_fn, "Singlepass");
    let cranelift_compiler_test = construct_compiler_test(&my_fn, "Cranelift");
    let llvm_compiler_test = construct_compiler_test(&my_fn, "LLVM");

    // We remove the method decorators
    my_fn.attrs = vec![];

    let x = quote! {
        #[cfg(test)]
        mod #fn_name {
            use super::*;

            #[allow(unused)]
            #my_fn

            #singlepass_compiler_test
            #cranelift_compiler_test
            #llvm_compiler_test
        }
    };

    #[allow(clippy::useless_conversion)]
    x.into()
}

#[cfg(test)]
mod tests;