1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//! Macros for use with _aktrs_.

extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;

/// Marks a function to be run as an `aktrs` unit test.
///
/// The unit test will be automatically supplied with an actor test kit.
///
/// # Examples
///
/// ```no_run
/// use aktrs::actor::{self, testkit::ActorTestKit, Behavior};
///
/// #[actor::testkit::test]
/// fn my_simple_test(mut t: ActorTestKit) -> aktrs::Result<()> {
///     let mut probe = t.spawn_anonymous_test_probe();
///     let pid = probe.pid();
///
///     let mut actor = t.spawn_anonymous(Behaviors::receive_message(move |cx, msg| {
///         let mut pid = pid.clone();
///         cx.run_side_effect(async move {
///             let _ = pid.tell(msg).await;
///         });
///         Ok(Behaviors::same())
///     }));
///
///     let _ = t.block_on(actor.tell("hello, world!"));
///     assert_eq!(probe.receive_message(), "hello, world!");
///
///     Ok(())
/// }
/// ```
#[proc_macro_attribute]
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(item as syn::ItemFn);
    let args = syn::parse_macro_input!(args as syn::AttributeArgs);

    let attrs = &input.attrs;
    let name = &input.sig.ident;
    let inputs = &input.sig.inputs;
    let ret = &input.sig.output;
    let body = &input.block;

    for attr in attrs {
        if attr.path.is_ident("test") {
            let msg = "second test attribute is supplied";
            return syn::Error::new_spanned(&attr, msg).to_compile_error().into();
        }
    }

    if !args.is_empty() {
        let msg = "the attribute does not accept arguments";
        return syn::Error::new(Span::call_site(), msg).to_compile_error().into();
    }

    let tokens = quote! {
        #[test]
        #(#attrs)*
        fn #name() {
            fn inner(#inputs) #ret {
                #body
            }

            ::aktrs::actor::testkit::__private_api_run(inner);
        }
    };

    tokens.into()
}