1#![warn(rust_2018_idioms, missing_docs)]
9
10use proc_macro::TokenStream;
11use quote::{format_ident, quote};
12use syn::{
13 parse_macro_input, punctuated::Punctuated, token::Comma, AttributeArgs, Error, FnArg, ItemFn,
14 NestedMeta, Pat, Type,
15};
16
17struct Arguments {
18 ids: Punctuated<Pat, Comma>,
19 tys: Punctuated<Type, Comma>,
20}
21
22fn parse_args(fn_item: &ItemFn) -> Result<Arguments, TokenStream> {
23 let mut args = Arguments {
24 ids: Punctuated::new(),
25 tys: Punctuated::new(),
26 };
27
28 for pt in fn_item.sig.inputs.iter() {
29 match pt {
30 FnArg::Receiver(_) => {
31 return Err(
32 Error::new_spanned(&fn_item, "test fn cannot take a receiver")
33 .to_compile_error()
34 .into(),
35 )
36 }
37
38 FnArg::Typed(pt) => {
39 args.ids.push(*pt.pat.clone());
40 args.tys.push(*pt.ty.clone());
41 }
42 }
43 }
44
45 Ok(args)
46}
47
48#[proc_macro_attribute]
73pub fn tokio(args: TokenStream, item: TokenStream) -> TokenStream {
74 let fn_item = parse_macro_input!(item as ItemFn);
75
76 for attr in &fn_item.attrs {
77 if attr.path.is_ident("test") {
78 return Error::new_spanned(&fn_item, "multiple #[test] attributes were supplied")
79 .to_compile_error()
80 .into();
81 }
82 }
83
84 if fn_item.sig.asyncness.is_none() {
85 return Error::new_spanned(&fn_item, "test fn must be async")
86 .to_compile_error()
87 .into();
88 }
89
90 let p_args = parse_macro_input!(args as AttributeArgs);
91 let attrib: Punctuated<NestedMeta, Comma> = p_args.into_iter().collect();
92
93 let call_by = format_ident!("{}", fn_item.sig.ident);
94
95 let Arguments { ids, tys } = match parse_args(&fn_item) {
96 Err(e) => return e,
97 Ok(ts) => ts,
98 };
99
100 let ret = &fn_item.sig.output;
101
102 quote! (
103 #[::tokio::test(#attrib)]
104 async fn #call_by() {
105 #fn_item
106
107 let test_fn: fn(#tys) #ret = |#ids| {
108 ::futures::executor::block_on(#call_by(#ids))
109 };
110
111 ::tokio::task::spawn_blocking(move || {
112 ::quickcheck::quickcheck(test_fn)
113 })
114 .await
115 .unwrap()
116 }
117 )
118 .into()
119}
120
121#[proc_macro_attribute]
134pub fn async_std(args: TokenStream, item: TokenStream) -> TokenStream {
135 let fn_item = parse_macro_input!(item as ItemFn);
136
137 for attr in &fn_item.attrs {
138 if attr.path.is_ident("test") {
139 return Error::new_spanned(&fn_item, "multiple #[test] attributes were supplied")
140 .to_compile_error()
141 .into();
142 }
143 }
144
145 if fn_item.sig.asyncness.is_none() {
146 return Error::new_spanned(&fn_item, "test fn must be async")
147 .to_compile_error()
148 .into();
149 }
150
151 let p_args = parse_macro_input!(args as AttributeArgs);
152 let attrib: Punctuated<NestedMeta, Comma> = p_args.into_iter().collect();
153
154 let call_by = format_ident!("{}", fn_item.sig.ident);
155
156 let Arguments { ids, tys } = match parse_args(&fn_item) {
157 Err(e) => return e,
158 Ok(ts) => ts,
159 };
160
161 let ret = &fn_item.sig.output;
162
163 quote! (
164 #[::async_std::test(#attrib)]
165 async fn #call_by() {
166 #fn_item
167
168 let test_fn: fn(#tys) #ret = |#ids| {
169 ::futures::executor::block_on(#call_by(#ids))
170 };
171
172 ::quickcheck::quickcheck(test_fn);
173 }
174 )
175 .into()
176}