diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index 8e737db6a7..91557dd1a6 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -197,3 +197,30 @@ pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { pub fn main_unspecified(args: TokenStream, item: TokenStream) -> TokenStream { main::run(args.into(), item.into(), &main::ARCH_UNSPECIFIED).into() } + +/// Declares an async test that can be run by `embassy-executor`. +/// +/// This macro allows you to create asynchronous tests that use Embassy's executor capabilities. +/// The test runner will spawn the test function using Embassy's executor and wait for it to complete. +/// +/// The following restrictions apply: +/// +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * The function must return a result type compatible with the standard test framework. +/// +/// ## Examples +/// +/// Creating a simple async test: +/// +/// ``` rust +/// #[embassy_executor::test] +/// async fn my_test() { +/// // Async test code +/// assert_eq!(1 + 1, 2); +/// } +/// ``` +#[proc_macro_attribute] +pub fn test(args: TokenStream, input: TokenStream) -> TokenStream { + macros::test::test(args, input) +} diff --git a/embassy-executor-macros/src/macros/mod.rs b/embassy-executor-macros/src/macros/mod.rs index 572094ca64..ee718e30e7 100644 --- a/embassy-executor-macros/src/macros/mod.rs +++ b/embassy-executor-macros/src/macros/mod.rs @@ -1,2 +1,3 @@ pub mod main; pub mod task; +pub mod test; diff --git a/embassy-executor-macros/src/macros/test.rs b/embassy-executor-macros/src/macros/test.rs new file mode 100644 index 0000000000..da98ce7680 --- /dev/null +++ b/embassy-executor-macros/src/macros/test.rs @@ -0,0 +1,30 @@ +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, ItemFn}; + +pub fn test(_args: TokenStream, input: TokenStream) -> TokenStream { + let input_fn = parse_macro_input!(input as ItemFn); + let fn_name = &input_fn.sig.ident; + + let task_name = format_ident!("__{}_task", fn_name); + + let gen = quote! { + #[::embassy_executor::task()] + #[allow(clippy::future_not_send)] + async fn #task_name() { + #input_fn + } + + + #[test] + fn #fn_name() { + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { ::core::mem::transmute::<_, &'static mut ::embassy_executor::Executor>(&mut executor) }; + executor.run(|spawner| { + spawner.spawn(#task_name()).unwrap(); + }); + } + }; + + gen.into() +} diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index d6fd3d6518..092e2d2f29 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -9,7 +9,7 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; -pub use embassy_executor_macros::task; +pub use embassy_executor_macros::{task, test}; macro_rules! check_at_most_one { (@amo [$($feats:literal)*] [] [$($res:tt)*]) => {