Skip to main content

bare_test_macros/
lib.rs

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}