1 use syn;
2 use proc_macro::TokenStream;
3 use proc_macro2::{self, Span};
4 
5 #[cfg(test)]
6 use testutils::assert_code_eq;
7 
check_return_type( return_type: &syn::ReturnType ) -> bool8 fn check_return_type( return_type: &syn::ReturnType ) -> bool {
9     match return_type {
10         &syn::ReturnType::Default => true,
11         &syn::ReturnType::Type( _, ref ty ) => {
12             match **ty {
13                 syn::Type::Tuple( ref tuple ) if tuple.elems.is_empty() => true,
14                 _ => false
15             }
16         }
17     }
18 }
19 
20 enum TestKind {
21     Simple {
22         callback_ident: syn::Ident
23     },
24     Fallible {
25         callback_ident: syn::Ident,
26         result_ty: syn::Type
27     },
28     Synchronous
29 }
30 
31 // TODO: There must be a cleaner way to do this.
check_decl( decl: &syn::Signature ) -> TestKind32 fn check_decl( decl: &syn::Signature ) -> TestKind {
33     assert!( decl.generics.lifetimes().next().is_none(), "Lifetimes are yet not supported" );
34     assert!( decl.generics.where_clause.is_none(), "`where` clauses are not supported" );
35     assert!( decl.variadic.is_none(), "Variadic functions are not supported" );
36 
37     if !check_return_type( &decl.output ) {
38         panic!( "The function should not return anything!" );
39     }
40 
41     let mut type_params: Vec< _ > = decl.generics.type_params().collect();
42     if type_params.is_empty() && decl.inputs.is_empty() {
43         // Exactly like a normal #[test].
44         //   fn test_foo() {}
45         return TestKind::Synchronous;
46     }
47 
48     assert_eq!( type_params.len(), 1, "The function should have a single type parameter" );
49 
50     let type_param = type_params.pop().unwrap();
51     assert!( type_param.attrs.is_empty(), "Type param attributes are not supported" );
52     assert!( type_param.default.is_none(), "Type param defaults are not supported" );
53     assert!( type_param.eq_token.is_none() );
54     assert_eq!( type_param.bounds.len(), 1, "The type param should have only one bound" );
55     let bound = match type_param.bounds[ 0 ].clone() {
56         syn::TypeParamBound::Lifetime( .. ) => panic!( "Lifetime type param bounds are not supported" ),
57         syn::TypeParamBound::Trait( bound ) => bound
58     };
59     match bound.modifier {
60         syn::TraitBoundModifier::None => {},
61         syn::TraitBoundModifier::Maybe( _ ) => panic!( "'?Trait' type bounds are not supported" )
62     }
63     assert!( bound.lifetimes.is_none(), "Lifetimes in type param bounds are not supported" );
64 
65     if !bound.path.leading_colon.is_none() ||
66         bound.path.segments.len() != 1 ||
67         bound.path.segments[ 0 ].ident != "FnOnce"
68     {
69         panic!( "Unsupported type bound" );
70     }
71 
72     enum Kind {
73         Simple,
74         Fallible {
75             result_ty: syn::Type
76         }
77     }
78 
79     let kind: Kind = match bound.path.segments[ 0 ].arguments {
80         syn::PathArguments::Parenthesized( syn::ParenthesizedGenericArguments { ref inputs, ref output, .. } ) => {
81             match output {
82                 syn::ReturnType::Default => {},
83                 _ => panic!( "Unsupported type bound" )
84             }
85 
86             let inputs: Vec< _ > = inputs.iter().collect();
87             match *inputs {
88                 [] => {
89                     // A test which can only succeed, or timeout.
90                     //  fn test_foo< F: FnOnce() >( cb: F ) {}
91                     Kind::Simple
92                 },
93                 [
94                     syn::Type::Path(
95                         syn::TypePath {
96                             qself: None,
97                             path: syn::Path {
98                                 leading_colon: None,
99                                 segments
100                             }
101                         }
102                     )
103                 ] if segments.len() == 1 => {
104                     let segment = &segments[ 0 ];
105                     if segment.ident != "Result" {
106                         panic!( "Unsupported type bound" );
107                     }
108 
109                     match segment.arguments {
110                         syn::PathArguments::AngleBracketed( ref args ) => {
111                             if args.args.len() != 2 {
112                                 panic!( "Unsupported type bound" );
113                             }
114                             match args.args[ 0 ] {
115                                 syn::GenericArgument::Type(
116                                     syn::Type::Tuple( syn::TypeTuple { ref elems, .. } )
117                                 ) if elems.is_empty() => {
118                                     // A test which can suceed, fail or timeout.
119                                     //  fn test_foo< F: FnOnce( Result< (), E > ) >( cb: F ) {}
120                                     Kind::Fallible {
121                                         result_ty: inputs[ 0 ].clone()
122                                     }
123                                 },
124                                 _ => panic!( "Unsupported type bound" )
125                             }
126                         },
127                         _ => panic!( "Unsupported type bound" )
128                     }
129                 },
130                 _ => panic!( "Unsupported type bound" )
131             }
132         },
133         _ => panic!( "Unsupported type bound" )
134     };
135 
136     if decl.inputs.len() != 1 {
137         panic!( "Expected a function with a single argument!" );
138     }
139 
140     let arg = decl.inputs.last().unwrap();
141     match *arg {
142         syn::FnArg::Receiver( .. ) => panic!( "`self` is not supported" ),
143         syn::FnArg::Typed( syn::PatType { ref pat, ref ty, .. } ) => {
144             match **pat {
145                 syn::Pat::Ident( ref pat ) => {
146                     assert!( pat.by_ref.is_none(), "`ref` bindings are not supported" );
147                     assert!( pat.mutability.is_none(), "`mut` bindings are not supported" );
148                     assert!( pat.subpat.is_none(), "Subpatterns are not supported" );
149 
150                     match **ty {
151                         syn::Type::Path(
152                             syn::TypePath {
153                                 qself: None,
154                                 path: syn::Path {
155                                     leading_colon: None,
156                                     ref segments
157                                 }
158                             }
159                         ) if segments.len() == 1 => {
160                             if type_param.ident != segments[ 0 ].ident {
161                                 panic!( "Unsupported argument type" );
162                             }
163                         },
164                         _ => panic!( "Unsupported argument type" )
165                     }
166 
167                     let callback_ident = pat.ident.clone();
168                     match kind {
169                         Kind::Simple => TestKind::Simple { callback_ident },
170                         Kind::Fallible { result_ty } => TestKind::Fallible {
171                             callback_ident,
172                             result_ty
173                         }
174                     }
175                 },
176                 _ => panic!( "Argument patterns are not supported" )
177             }
178         }
179     }
180 }
181 
async_test_impl( item: syn::Item ) -> proc_macro2::TokenStream182 fn async_test_impl( item: syn::Item ) -> proc_macro2::TokenStream {
183     let (ident, block, test_kind) = match item {
184         syn::Item::Fn( function ) => {
185             let test_kind = check_decl( &function.sig );
186             (function.sig.ident.clone(), function.block, test_kind)
187         },
188         _ => panic!( "`#[async_test]` attached to an unsupported element!" )
189     };
190 
191     let inner;
192     match test_kind {
193         TestKind::Simple { callback_ident } => {
194             let prelude = quote! {
195                 let #callback_ident = {
196                     let resolve = js!( return ASYNC_TEST_PRIVATE.resolve; );
197                     move || {
198                         js!(
199                             @{resolve}();
200                         );
201                     }
202                 };
203             };
204 
205             inner = quote! {
206                 #prelude
207                 #block
208             };
209         },
210         TestKind::Fallible { callback_ident, result_ty } => {
211             let prelude = quote! {
212                 let #callback_ident = {
213                     let resolve = js!( return ASYNC_TEST_PRIVATE.resolve; );
214                     let reject = js!( return ASYNC_TEST_PRIVATE.reject; );
215                     move |result: #result_ty| {
216                         match result {
217                             Ok(()) => js! {
218                                 @{resolve}();
219                             },
220                             Err( error ) => js! {
221                                 @{reject}(@{format!( "{:?}", error )});
222                             }
223                         };
224                     }
225                 };
226             };
227 
228             inner = quote! {
229                 #prelude
230                 #block
231             };
232         },
233         TestKind::Synchronous => {
234             inner = quote! {
235                 (move || {
236                     #block
237                 })();
238 
239                 js! {
240                     ASYNC_TEST_PRIVATE.resolve();
241                 };
242             }
243         }
244     }
245 
246     let symbol = syn::Ident::new( &format!( "__async_test__{}", ident ), Span::call_site() );
247     let output = quote! {
248         #[cfg(test)]
249         #[linkage = "external"]
250         #[no_mangle]
251         #[allow(dead_code)]
252         #[allow(non_snake_case)]
253         fn #symbol() {
254             #inner
255         }
256     };
257 
258     output
259 }
260 
async_test( attrs: TokenStream, input: TokenStream ) -> TokenStream261 pub fn async_test( attrs: TokenStream, input: TokenStream ) -> TokenStream {
262     if !attrs.is_empty() {
263         panic!( "Extra attributes are not supported in `#[async_test]`!" );
264     }
265 
266     let input: proc_macro2::TokenStream = input.into();
267     let item: syn::Item = syn::parse2( input ).unwrap();
268 
269     async_test_impl( item ).into()
270 }
271 
272 #[test]
test_async_test_simple()273 fn test_async_test_simple() {
274     let input = quote! {
275         fn foobar< F: FnOnce() >( done: F ) {
276             if true {
277                 done();
278             }
279         }
280     };
281 
282     let expected = quote! {
283         #[cfg(test)]
284         #[linkage = "external"]
285         #[no_mangle]
286         #[allow(dead_code)]
287         #[allow(non_snake_case)]
288         fn __async_test__foobar() {
289             let done = {
290                 let resolve = js ! ( return ASYNC_TEST_PRIVATE . resolve ; );
291                 move || {
292                     js ! ( @ { resolve } ( ) ; );
293                 }
294             };
295             {
296                 if true {
297                     done();
298                 }
299             }
300         }
301     };
302 
303     let output = async_test_impl( syn::parse2( input ).unwrap() );
304     assert_code_eq( output, expected );
305 }
306 
307 #[test]
test_async_test_fallible()308 fn test_async_test_fallible() {
309     let input = quote! {
310         #[async_test]
311         fn foobar< F: FnOnce( Result< (), i32 > ) >( done: F ) {
312             done( Ok(()) );
313         }
314     };
315 
316     let expected = quote! {
317         #[cfg(test)]
318         #[linkage = "external"]
319         #[no_mangle]
320         #[allow(dead_code)]
321         #[allow(non_snake_case)]
322         fn __async_test__foobar() {
323             let done = {
324                 let resolve = js ! ( return ASYNC_TEST_PRIVATE . resolve ; );
325                 let reject = js ! ( return ASYNC_TEST_PRIVATE . reject ; );
326                 move |result: Result<(), i32>| { match result {
327                     Ok(()) => js ! { @ { resolve } ( ) ; },
328                     Err(error) => js ! { @ { reject } ( @ { format ! ( "{:?}" , error ) } ) ; }
329                 };}
330             };
331             {
332                 done(Ok(()));
333             }
334         }
335     };
336 
337     let output = async_test_impl( syn::parse2( input ).unwrap() );
338     assert_code_eq( output, expected );
339 }
340 
341 #[test]
test_async_test_synchronous()342 fn test_async_test_synchronous() {
343     let input = quote! {
344         fn foobar() {
345             body();
346         }
347     };
348 
349     let expected = quote! {
350         #[cfg(test)]
351         #[linkage = "external"]
352         #[no_mangle]
353         #[allow(dead_code)]
354         #[allow(non_snake_case)]
355         fn __async_test__foobar() {
356             (move || {{
357                 body();
358             }})();
359             js ! { ASYNC_TEST_PRIVATE . resolve ( ) ; };
360         }
361     };
362 
363     let output = async_test_impl( syn::parse2( input ).unwrap() );
364     assert_code_eq( output, expected );
365 }
366