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 experimental_artifact: bool,
61 dynamic_memory: bool|
62 -> ::proc_macro2::TokenStream {
63 let config_compiler = ::quote::format_ident!("{}", compiler_name);
64 let test_name = ::quote::format_ident!("{}", engine_name.to_lowercase());
65 let mut config = quote! { crate::Config::new(crate::Compiler::#config_compiler) };
66 if experimental_artifact {
67 config = quote! { #config.with_experimental_artifact() };
68 }
69 if dynamic_memory {
70 config = quote! { #config.with_dynamic_memory() };
71 }
72 let experimental_artifact_cfg =
73 experimental_artifact.then(|| quote! { #[cfg(target_os = "linux")] });
74 let mut new_sig = func.sig.clone();
75 let attrs = func
76 .attrs
77 .clone()
78 .iter()
79 .fold(quote! {}, |acc, new| quote! {#acc #new});
80 new_sig.ident = test_name;
81 new_sig.inputs = ::syn::punctuated::Punctuated::new();
82 let f = quote! {
83 #[test_log::test]
84 #attrs
85 #[cfg(feature = #engine_feature_name)]
86 #experimental_artifact_cfg
87 #new_sig {
88 #fn_name(#config)
89 }
90 };
91 if should_ignore(
92 &func.sig.ident.to_string().replace("r#", ""),
93 compiler_name,
94 engine_name,
95 ) && !cfg!(test)
96 {
97 quote! {
98 #[ignore]
99 #f
100 }
101 } else {
102 f
103 }
104 };
105
106 let construct_compiler_test =
107 |func: &::syn::ItemFn, compiler_name: &str| -> ::proc_macro2::TokenStream {
108 let mod_name = ::quote::format_ident!("{}", compiler_name.to_lowercase());
109 let engine_test = construct_engine_test(
110 func,
111 compiler_name,
112 compiler_name,
113 &compiler_name.to_lowercase(),
114 false,
115 false,
116 );
117 let native_compiler = compiler_name != "V8";
118 let experimental_artifact_test = native_compiler.then(|| {
119 construct_engine_test(
120 func,
121 compiler_name,
122 &format!("{compiler_name}_exp_artifact"),
123 &compiler_name.to_lowercase(),
124 true,
125 false,
126 )
127 });
128 let dynamic_memory_experimental_artifact_test = native_compiler.then(|| {
129 construct_engine_test(
130 func,
131 compiler_name,
132 &format!("{compiler_name}_dynamic_memory_exp_artifact"),
133 &compiler_name.to_lowercase(),
134 true,
135 true,
136 )
137 });
138 let compiler_name_lowercase = compiler_name.to_lowercase();
139
140 quote! {
141 #[cfg(feature = #compiler_name_lowercase)]
142 mod #mod_name {
143 use super::*;
144
145 #engine_test
146 #experimental_artifact_test
147 #dynamic_memory_experimental_artifact_test
148 }
149 }
150 };
151
152 let singlepass_compiler_test = construct_compiler_test(&my_fn, "Singlepass");
153 let cranelift_compiler_test = construct_compiler_test(&my_fn, "Cranelift");
154 let llvm_compiler_test = construct_compiler_test(&my_fn, "LLVM");
155 let v8_compiler_test = construct_compiler_test(&my_fn, "V8");
156
157 my_fn.attrs = vec![];
159
160 let x = quote! {
161 #[cfg(test)]
162 mod #fn_name {
163 use super::*;
164
165 #[allow(unused)]
166 #my_fn
167
168 #singlepass_compiler_test
169 #cranelift_compiler_test
170 #llvm_compiler_test
171 #v8_compiler_test
172 }
173 };
174
175 #[allow(clippy::useless_conversion)]
176 x.into()
177}
178
179#[cfg(test)]
180mod tests;