@@ -22,9 +22,11 @@ extern crate alloc;
2222
2323use alloc:: string:: ToString ;
2424use proc_macro:: { Delimiter , Group , TokenStream , TokenTree } ;
25+ use proc_macro2:: TokenStream as TokenStream2 ;
2526use quote:: quote;
2627use syn:: spanned:: Spanned ;
2728use syn:: { parse, ImplItemFn , Token } ;
29+ use syn:: { parse_macro_input, Item } ;
2830
2931fn add_async_method ( mut parsed : ImplItemFn ) -> TokenStream {
3032 let output = quote ! {
@@ -294,3 +296,124 @@ pub fn drop_legacy_field_definition(expr: TokenStream) -> TokenStream {
294296 let out = syn:: Expr :: Struct ( st) ;
295297 quote ! { #out } . into ( )
296298}
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