compiler_test_derive/
lib.rs1#[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
17macro_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 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 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 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 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;