@@ -22,9 +22,11 @@ extern crate alloc;
22
22
23
23
use alloc:: string:: ToString ;
24
24
use proc_macro:: { Delimiter , Group , TokenStream , TokenTree } ;
25
+ use proc_macro2:: TokenStream as TokenStream2 ;
25
26
use quote:: quote;
26
27
use syn:: spanned:: Spanned ;
27
28
use syn:: { parse, ImplItemFn , Token } ;
29
+ use syn:: { parse_macro_input, Item } ;
28
30
29
31
fn add_async_method ( mut parsed : ImplItemFn ) -> TokenStream {
30
32
let output = quote ! {
@@ -294,3 +296,124 @@ pub fn drop_legacy_field_definition(expr: TokenStream) -> TokenStream {
294
296
let out = syn:: Expr :: Struct ( st) ;
295
297
quote ! { #out } . into ( )
296
298
}
299
+
300
+ /// An exposed test. This is a test that will run locally and also be
301
+ /// made available to other crates that want to run it in their own context.
302
+ ///
303
+ /// For example:
304
+ /// ```rust
305
+ /// use lightning_macros::xtest;
306
+ ///
307
+ /// fn f1() {}
308
+ ///
309
+ /// #[xtest(feature = "_test_utils")]
310
+ /// pub fn test_f1() {
311
+ /// f1();
312
+ /// }
313
+ /// ```
314
+ ///
315
+ /// May also be applied to modules, like so:
316
+ ///
317
+ /// ```rust
318
+ /// use lightning_macros::xtest;
319
+ ///
320
+ /// pub mod tests {
321
+ /// use super::*;
322
+ ///
323
+ /// fn f1() {}
324
+ ///
325
+ /// #[xtest(feature = "_externalize_tests")]
326
+ /// pub fn test_f1() {
327
+ /// f1();
328
+ /// }
329
+ /// }
330
+ /// ```
331
+ ///
332
+ /// Which will include the module if we are testing or the `_test_utils` feature
333
+ /// is on.
334
+ #[ proc_macro_attribute]
335
+ pub fn xtest ( attrs : TokenStream , item : TokenStream ) -> TokenStream {
336
+ let attrs = parse_macro_input ! ( attrs as TokenStream2 ) ;
337
+ let input = parse_macro_input ! ( item as Item ) ;
338
+
339
+ let expanded = match input {
340
+ Item :: Fn ( item_fn) => {
341
+ let ( cfg_attr, submit_attr) = if attrs. is_empty ( ) {
342
+ ( quote ! { #[ cfg_attr( test, test) ] } , quote ! { #[ cfg( not( test) ) ] } )
343
+ } else {
344
+ (
345
+ quote ! { #[ cfg_attr( test, test) ] #[ cfg( any( test, #attrs) ) ] } ,
346
+ quote ! { #[ cfg( all( not( test) , #attrs) ) ] } ,
347
+ )
348
+ } ;
349
+
350
+ // Check that the function doesn't take args and returns nothing
351
+ if !item_fn. sig . inputs . is_empty ( )
352
+ || !matches ! ( item_fn. sig. output, syn:: ReturnType :: Default )
353
+ {
354
+ return syn:: Error :: new_spanned (
355
+ item_fn. sig ,
356
+ "xtest functions must not take arguments and must return nothing" ,
357
+ )
358
+ . to_compile_error ( )
359
+ . into ( ) ;
360
+ }
361
+
362
+ // Check for #[should_panic] attribute
363
+ let should_panic =
364
+ item_fn. attrs . iter ( ) . any ( |attr| attr. path ( ) . is_ident ( "should_panic" ) ) ;
365
+
366
+ let fn_name = & item_fn. sig . ident ;
367
+ let fn_name_str = fn_name. to_string ( ) ;
368
+ quote ! {
369
+ #cfg_attr
370
+ #item_fn
371
+
372
+ // We submit the test to the inventory only if we're not actually testing
373
+ #submit_attr
374
+ inventory:: submit! {
375
+ crate :: XTestItem {
376
+ test_fn: #fn_name,
377
+ test_name: #fn_name_str,
378
+ should_panic: #should_panic,
379
+ }
380
+ }
381
+ }
382
+ } ,
383
+ _ => {
384
+ return syn:: Error :: new_spanned (
385
+ input,
386
+ "xtest can only be applied to functions or modules" ,
387
+ )
388
+ . to_compile_error ( )
389
+ . into ( ) ;
390
+ } ,
391
+ } ;
392
+
393
+ TokenStream :: from ( expanded)
394
+ }
395
+
396
+ /// Collects all externalized tests marked with `#[xtest]`
397
+ /// into a vector of `XTestItem`s. This vector can be
398
+ /// retrieved by calling `get_xtests()`.
399
+ #[ proc_macro]
400
+ pub fn xtest_inventory ( _input : TokenStream ) -> TokenStream {
401
+ let expanded = quote ! {
402
+ /// An externalized test item, including the test function, name, and whether it is marked with `#[should_panic]`.
403
+ pub struct XTestItem {
404
+ pub test_fn: fn ( ) ,
405
+ pub test_name: & ' static str ,
406
+ pub should_panic: bool ,
407
+ }
408
+
409
+ inventory:: collect!( XTestItem ) ;
410
+
411
+ pub fn get_xtests( ) -> Vec <& ' static XTestItem > {
412
+ inventory:: iter:: <XTestItem >
413
+ . into_iter( )
414
+ . collect( )
415
+ }
416
+ } ;
417
+
418
+ TokenStream :: from ( expanded)
419
+ }
0 commit comments