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