tesults-test-macros 1.0.1

Proc macros for tesults-test
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

/// Replaces `#[test]` to add Tesults reporting. Timing, pass/fail, and panic
/// reason are captured automatically. Use `tesults_test::description()`,
/// `tesults_test::custom()`, `tesults_test::step()`, and `tesults_test::file()`
/// inside the test body for enhanced reporting.
#[proc_macro_attribute]
pub fn test(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let fn_name = &input.sig.ident;
    let fn_name_str = fn_name.to_string();
    let fn_body = &input.block;
    let attrs = &input.attrs;
    let vis = &input.vis;

    let should_panic = attrs.iter().any(|a| a.path().is_ident("should_panic"));

    // For should_panic tests: passed = test DID panic (result is Err).
    // For normal tests:       passed = test did NOT panic (result is Ok).
    let passed_expr = if should_panic {
        quote! { __tt_result.is_err() }
    } else {
        quote! { __tt_result.is_ok() }
    };

    // Extract a human-readable failure reason from the panic payload.
    // Uses `ref` pattern to borrow without consuming __tt_result.
    let reason_expr = if should_panic {
        quote! {
            if __tt_result.is_ok() {
                ::std::string::String::from("Test was expected to panic but did not")
            } else {
                ::std::string::String::new()
            }
        }
    } else {
        quote! {
            match __tt_result {
                ::std::result::Result::Err(ref __tt_err) => {
                    if let ::std::option::Option::Some(s) =
                        __tt_err.downcast_ref::<::std::string::String>()
                    {
                        s.clone()
                    } else if let ::std::option::Option::Some(s) =
                        __tt_err.downcast_ref::<&str>()
                    {
                        s.to_string()
                    } else {
                        ::std::string::String::from("Test panicked")
                    }
                }
                _ => ::std::string::String::new(),
            }
        }
    };

    let expanded = quote! {
        #[test]
        #(#attrs)*
        #vis fn #fn_name() {
            let __tt_start = ::tesults_test::__private::now_ms();
            let __tt_result = ::std::panic::catch_unwind(
                ::std::panic::AssertUnwindSafe(|| { #fn_body })
            );
            let __tt_end = ::tesults_test::__private::now_ms();
            let __tt_passed = #passed_expr;
            let __tt_reason = #reason_expr;
            ::tesults_test::__private::record_result(
                #fn_name_str,
                module_path!(),
                __tt_passed,
                __tt_reason,
                __tt_start,
                __tt_end,
            );
            if let ::std::result::Result::Err(__tt_err) = __tt_result {
                ::std::panic::resume_unwind(__tt_err);
            }
        }
    };

    expanded.into()
}