Skip to main content

libtest_mimic_collect_macro/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Ident, Span};
3use quote::quote;
4use syn::{
5    parse_macro_input, spanned::Spanned, AngleBracketedGenericArguments, GenericArgument, ItemFn,
6    LitStr, PathArguments, ReturnType, Type, TypePath, TypeTuple,
7};
8
9/// This macro automatically adds tests marked with #[test] to the test collection.
10/// Tests then can be run with libtest_mimic_collect::TestCollection::run().
11#[proc_macro_attribute]
12pub fn test(_args: TokenStream, input: TokenStream) -> TokenStream {
13    let ItemFn { sig, block, .. } = parse_macro_input!(input as ItemFn);
14
15    let ident = &sig.ident;
16    let test_name = ident.to_string();
17    let test_name_str = LitStr::new(&test_name, Span::call_site());
18    let ctor_name = format!("__{}_add_test", test_name);
19    let ctor_ident = Ident::new(&ctor_name, Span::call_site());
20
21    let ret_type_unit = quote! { Result<(), ::libtest_mimic_collect::libtest_mimic::Failed> };
22    let ret_type_completion = quote! { Result<::libtest_mimic_collect::libtest_mimic::Completion, ::libtest_mimic_collect::libtest_mimic::Failed> };
23
24    let trial = match &sig.output {
25        ReturnType::Default => {
26            quote! {
27                ::libtest_mimic_collect::libtest_mimic::Trial::test(#test_name_str, || -> #ret_type_unit {
28                    #ident();
29                    Ok(())
30                })
31            }
32        }
33        ReturnType::Type(_, ty) => {
34            let result_segment = if let Type::Path(TypePath { path, qself: None }) = ty.as_ref() {
35                path.segments
36                    .last()
37                    .filter(|segment| segment.ident == "Result")
38            } else {
39                None
40            };
41
42            match result_segment {
43                Some(segment) => {
44                    let is_unit_result = match &segment.arguments {
45                        PathArguments::None => false,
46                        PathArguments::AngleBracketed(AngleBracketedGenericArguments {
47                            args,
48                            ..
49                        }) => {
50                            matches!(
51                                args.first(),
52                                Some(GenericArgument::Type(Type::Tuple(TypeTuple { elems, .. }))) if elems.is_empty()
53                            )
54                        }
55                        PathArguments::Parenthesized(args) => {
56                            return syn::Error::new(args.span(), "unexpected return type")
57                                .to_compile_error()
58                                .into();
59                        }
60                    };
61
62                    if is_unit_result {
63                        quote! {
64                            ::libtest_mimic_collect::libtest_mimic::Trial::test(#test_name_str, || -> #ret_type_unit {
65                                Ok(#ident()?.into())
66                            })
67                        }
68                    } else {
69                        quote! {
70                            ::libtest_mimic_collect::libtest_mimic::Trial::ignorable_test(#test_name_str, || -> #ret_type_completion {
71                                Ok(#ident()?.into())
72                            })
73                        }
74                    }
75                }
76                None => {
77                    quote! {
78                        ::libtest_mimic_collect::libtest_mimic::Trial::test(#test_name_str, || -> #ret_type_unit {
79                            ::libtest_mimic_collect::TestCollection::convert_result(#ident())
80                        })
81                    }
82                }
83            }
84        }
85    };
86
87    (quote! {
88        #sig #block
89
90        #[::libtest_mimic_collect::ctor]
91        fn #ctor_ident() {
92            ::libtest_mimic_collect::TestCollection::add_test(#trial);
93        }
94    })
95    .into()
96}