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=-Tlink-test.x");
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) if f.attrs.iter().any(|attr| attr.path().is_ident("test")) => {
49                let f_name = &f.sig.ident;
50                let _f_name = format_ident!("__{}", f.sig.ident);
51                let block = &f.block;
52                let static_name = format_ident!("__TEST_{}", f_name.to_string().to_uppercase());
53                let f_name_str = f_name.to_string();
54                let f_atters = &f.attrs;
55
56                let mut timeout = 0u64;
57                if let Some(attr) = f_atters.iter().find(|attr| attr.path().is_ident("timeout")) {
58                    if let syn::Meta::NameValue(nv) = &attr.meta
59                        && let syn::Expr::Lit(lit_int) = &nv.value
60                        && let syn::Lit::Int(int_lit) = &lit_int.lit
61                    {
62                        timeout = int_lit.base10_parse::<u64>()?;
63                    } else {
64                        return Err(syn::Error::new(attr.span(), "invalid timeout attribute"));
65                    }
66                }
67
68                test_functions.push(quote! {
69                    #(#f_atters)*
70                    fn #f_name() {
71                        #_f_name()
72                    }
73
74                    fn #_f_name() {
75                        #block
76                    }
77
78                    #[used(linker)]
79                    #[unsafe(link_section = ".test_case")]
80                    static #static_name: #krate::TestCase = #krate::TestCase {
81                        name: #f_name_str,
82                        timeout_ms: #timeout,
83                        test_fn: #_f_name,
84                    };
85                });
86            }
87            _ => {
88                untouched_tokens.push(item);
89            }
90        }
91    }
92
93    Ok(quote!(
94        #[cfg(test)]
95        mod #ident{
96            #(#untouched_tokens)*
97            #(#test_functions)*
98
99        }
100    )
101    .into())
102}