1extern crate proc_macro;
2#[macro_use]
3extern crate quote;
4extern crate core;
5extern crate proc_macro2;
6extern crate syn;
7
8use proc_macro::TokenStream;
9use syn::{Item, ItemMod, parse, spanned::Spanned};
10
11#[proc_macro]
12pub fn build_test_setup(_input: TokenStream) -> TokenStream {
13 quote! {
14 println!("cargo::rustc-link-arg-tests=-Ttest_case_link.ld");
15 println!("cargo::rustc-link-arg-tests=-znostart-stop-gc");
16 }
17 .into()
18}
19
20#[proc_macro_attribute]
21pub fn tests(args: TokenStream, input: TokenStream) -> TokenStream {
22 match tests_impl(args, input) {
23 Ok(tokens) => tokens,
24 Err(e) => e.to_compile_error().into(),
25 }
26}
27
28fn tests_impl(_args: TokenStream, input: TokenStream) -> Result<TokenStream, parse::Error> {
29 let krate = format_ident!("bare_test");
30 let module: ItemMod = syn::parse(input)?;
31
32 let ident = &module.ident;
33
34 let mut untouched_tokens = vec![];
35 let mut test_functions = vec![];
36 let span = module.span();
37
38 let items = &module
39 .content
40 .ok_or(parse::Error::new(
41 span,
42 "module must be inline (e.g. `mod foo {}`)",
43 ))?
44 .1;
45
46 for item in items {
47 match item {
48 Item::Fn(f) => {
49 if f.attrs.iter().any(|attr| attr.path().is_ident("test")) {
50 let f_name = &f.sig.ident;
51 let _f_name = format_ident!("__{}", f.sig.ident);
52 let block = &f.block;
53 let static_name = format_ident!("__TEST_{}", f_name.to_string().to_uppercase());
54 let f_name_str = f_name.to_string();
55 let f_atters = &f.attrs;
56
57 let mut timeout = 0u64;
58 if let Some(attr) = f_atters.iter().find(|attr| attr.path().is_ident("timeout"))
59 {
60 if let syn::Meta::NameValue(nv) = &attr.meta
61 && let syn::Expr::Lit(lit_int) = &nv.value
62 && let syn::Lit::Int(int_lit) = &lit_int.lit
63 {
64 timeout = int_lit.base10_parse::<u64>()?;
65 } else {
66 return Err(syn::Error::new(attr.span(), "invalid timeout attribute"));
67 }
68 }
69
70 test_functions.push(quote! {
71 #(#f_atters)*
72 fn #f_name() {
73 #_f_name()
74 }
75
76 fn #_f_name() {
77 #block
78 }
79
80 #[used(linker)]
81 #[unsafe(link_section = ".test_case")]
82 static #static_name: #krate::TestCase = #krate::TestCase {
83 name: #f_name_str,
84 timeout_ms: #timeout,
85 test_fn: #_f_name,
86 };
87 });
88 } else {
89 untouched_tokens.push(item);
90 }
91 }
92 _ => {
93 untouched_tokens.push(item);
94 }
95 }
96 }
97
98 Ok(quote!(
99 #[cfg(test)]
100 mod #ident{
101 #(#untouched_tokens)*
102 #(#test_functions)*
103
104 }
105 )
106 .into())
107}