-
Notifications
You must be signed in to change notification settings - Fork 584
/
lib.rs
126 lines (110 loc) · 4.31 KB
/
lib.rs
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
use proc_macro::TokenStream;
use quote::quote;
use std::path::PathBuf;
use syn::parse;
use syn::*;
mod ignores;
// Reimplement parse_macro_input to use the imported `parse`
// function. This way parse_macro_input will parse a TokenStream2 when
// unit-testing.
macro_rules! parse_macro_input {
(
$token_stream:ident as $T:ty
) => {
match parse::<$T>($token_stream) {
Ok(data) => data,
Err(err) => {
return TokenStream::from(err.to_compile_error());
}
}
};
(
$token_stream:ident
) => {
parse_macro_input!($token_stream as _)
};
}
#[proc_macro_attribute]
pub fn compiler_test(attrs: TokenStream, input: TokenStream) -> TokenStream {
let path: Option<ExprPath> = parse::<ExprPath>(attrs).ok();
let mut my_fn: ItemFn = parse_macro_input!(input as ItemFn);
let fn_name = my_fn.sig.ident.clone();
// 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 mut ignores_txt_path = PathBuf::new();
ignores_txt_path.push(env!("CARGO_MANIFEST_DIR"));
ignores_txt_path.push("../../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(" ", "");
let should_ignore = ignores.should_ignore_host(&engine_name, &compiler_name, &full_path);
// println!("{} -> Should ignore: {}", full_path, should_ignore);
return should_ignore;
};
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 config_engine = ::quote::format_ident!("{}", engine_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::Engine::#config_engine, 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 dylib_engine_test = construct_engine_test(func, compiler_name, "Dylib", "dylib");
let compiler_name_lowercase = compiler_name.to_lowercase();
quote! {
#[cfg(feature = #compiler_name_lowercase)]
mod #mod_name {
use super::*;
#universal_engine_test
#dylib_engine_test
}
}
};
let singlepass_compiler_test = construct_compiler_test(&my_fn, "Singlepass");
// 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
}
};
x.into()
}