1extern crate proc_macro;
5
6use proc_macro::TokenStream;
7use proc_macro2::TokenStream as Tokens;
8
9use quote::quote;
10
11use syn::parse_macro_input;
12use syn::Attribute;
13use syn::Error;
14use syn::ItemFn;
15use syn::Result;
16
17
18#[proc_macro_attribute]
50pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
51 let input_fn = parse_macro_input!(item as ItemFn);
52
53 try_test(attr, input_fn)
54 .unwrap_or_else(syn::Error::into_compile_error)
55 .into()
56}
57
58fn is_test_attribute(attr: &Attribute) -> bool {
63 let path = match &attr.meta {
64 syn::Meta::Path(path) => path,
65 _ => return false,
66 };
67 let candidates = [
68 ["core", "prelude", "*", "test"],
69 ["std", "prelude", "*", "test"],
70 ];
71 if path.leading_colon.is_none()
72 && path.segments.len() == 1
73 && path.segments[0].arguments.is_none()
74 && path.segments[0].ident == "test"
75 {
76 return true;
77 } else if path.segments.len() != candidates[0].len() {
78 return false;
79 }
80 candidates.into_iter().any(|segments| {
81 path.segments.iter().zip(segments).all(|(segment, path)| {
82 segment.arguments.is_none() && (path == "*" || segment.ident == path)
83 })
84 })
85}
86
87fn try_test(attr: TokenStream, input_fn: ItemFn) -> Result<Tokens> {
88 if !attr.is_empty() {
89 return Err(Error::new_spanned(
90 Tokens::from(attr),
91 "test_fork::test does not currently accept arguments",
92 ))
93 }
94
95 let has_test = input_fn.attrs.iter().any(is_test_attribute);
96 let inner_test = if has_test {
97 quote! {}
98 } else {
99 quote! { #[::core::prelude::v1::test]}
100 };
101
102 let augmented_test = quote! {
103 ::test_fork::fork_test! {
104 #inner_test
105 #input_fn
106 }
107 };
108
109 Ok(augmented_test)
110}