Add #[test] macro
This commit is contained in:
parent
530ead5fde
commit
4a5a856496
@ -62,6 +62,35 @@ pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
task::run(&args.meta, f).unwrap_or_else(|x| x).into()
|
||||
}
|
||||
|
||||
/// Declares a unit test which uses the embassy executor to run the test on std.
|
||||
///
|
||||
/// The following restrictions apply:
|
||||
///
|
||||
/// * The function may accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
|
||||
/// * The function must be declared `async`.
|
||||
/// * The function must not use generics.
|
||||
/// * The function must not return anything
|
||||
///
|
||||
/// ## Examples
|
||||
/// Creating a testcase:
|
||||
///
|
||||
/// ``` rust
|
||||
///#[cfg(test)]
|
||||
/// mod tests
|
||||
/// {
|
||||
//// #[embassy_executor::test]
|
||||
/// async fn test1() {
|
||||
/// // Test case body
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(_args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
|
||||
test::run(f).unwrap_or_else(|x| x).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
|
||||
///
|
||||
/// The following restrictions apply:
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub mod main;
|
||||
pub mod task;
|
||||
pub mod test;
|
||||
|
66
embassy-executor-macros/src/macros/test.rs
Normal file
66
embassy-executor-macros/src/macros/test.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{self, ReturnType};
|
||||
|
||||
use crate::util::ctxt::Ctxt;
|
||||
|
||||
pub fn run(f: syn::ItemFn) -> Result<TokenStream, TokenStream> {
|
||||
let ctxt = Ctxt::new();
|
||||
|
||||
if f.sig.asyncness.is_none() {
|
||||
ctxt.error_spanned_by(&f.sig, "test functions must be async");
|
||||
}
|
||||
if !f.sig.generics.params.is_empty() {
|
||||
ctxt.error_spanned_by(&f.sig, "test functions must not be generic");
|
||||
}
|
||||
|
||||
let args = f.sig.inputs.clone();
|
||||
|
||||
if args.len() > 1 {
|
||||
ctxt.error_spanned_by(f.sig.inputs, "test function must take zero or one (spawner) arguments");
|
||||
}
|
||||
|
||||
if f.sig.output != ReturnType::Default {
|
||||
ctxt.error_spanned_by(&f.sig.output, "test functions must not return a value");
|
||||
}
|
||||
|
||||
ctxt.check()?;
|
||||
|
||||
let test_name = f.sig.ident;
|
||||
let task_fn_body = f.block;
|
||||
let embassy_test_name = format_ident!("__embassy_test_{}", test_name);
|
||||
let embassy_test_launcher = format_ident!("__embassy_test_launcher_{}", test_name);
|
||||
|
||||
let invocation = if args.len() == 1 {
|
||||
quote! { #embassy_test_name(spawner).await }
|
||||
} else {
|
||||
quote! { #embassy_test_name().await }
|
||||
};
|
||||
|
||||
let result = quote! {
|
||||
|
||||
#[::embassy_executor::task]
|
||||
async fn #embassy_test_launcher(spawner: ::embassy_executor::Spawner, runner: &'static mut ::embassy_executor::_export_testutils::TestRunner) {
|
||||
#invocation;
|
||||
runner.done();
|
||||
}
|
||||
|
||||
async fn #embassy_test_name(#args) {
|
||||
#task_fn_body
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn #test_name() {
|
||||
let r = ::embassy_executor::_export_testutils::TestRunner::default();
|
||||
|
||||
let r1: &'static mut ::embassy_executor::_export_testutils::TestRunner = unsafe { core::mem::transmute(&r) };
|
||||
|
||||
r1.initialize(|spawner| {
|
||||
let r2: &'static mut ::embassy_executor::_export_testutils::TestRunner = unsafe { core::mem::transmute(&r) };
|
||||
spawner.spawn(#embassy_test_launcher(spawner, r2)).unwrap();
|
||||
});
|
||||
r1.run_until_done();
|
||||
}
|
||||
};
|
||||
Ok(result.into())
|
||||
}
|
@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
- Added #[test] macro for std.
|
||||
|
||||
## 0.4.0 - 2023-12-05
|
||||
|
||||
|
@ -62,6 +62,22 @@ mod thread {
|
||||
self.signaler.wait()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the spawner of the executor
|
||||
pub(crate) fn spawner(&'static self) -> Spawner {
|
||||
self.inner.spawner()
|
||||
}
|
||||
|
||||
/// Run the executor until the closure returns true.
|
||||
pub(crate) fn run_until(&'static self, f: impl Fn() -> bool) {
|
||||
loop {
|
||||
unsafe { self.inner.poll() };
|
||||
if f() {
|
||||
break;
|
||||
}
|
||||
self.signaler.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Signaler {
|
||||
|
@ -7,6 +7,8 @@
|
||||
pub(crate) mod fmt;
|
||||
|
||||
pub use embassy_executor_macros::task;
|
||||
#[cfg(feature = "arch-std")]
|
||||
pub use embassy_executor_macros::test;
|
||||
|
||||
macro_rules! check_at_most_one {
|
||||
(@amo [$($feats:literal)*] [] [$($res:tt)*]) => {
|
||||
@ -38,6 +40,15 @@ pub mod raw;
|
||||
mod spawner;
|
||||
pub use spawner::*;
|
||||
|
||||
#[cfg(feature = "arch-std")]
|
||||
mod testutils;
|
||||
|
||||
#[cfg(feature = "arch-std")]
|
||||
#[doc(hidden)]
|
||||
pub mod _export_testutils {
|
||||
pub use crate::testutils::*;
|
||||
}
|
||||
|
||||
mod config {
|
||||
#![allow(unused)]
|
||||
include!(concat!(env!("OUT_DIR"), "/config.rs"));
|
||||
|
35
embassy-executor/src/testutils.rs
Normal file
35
embassy-executor/src/testutils.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::{Executor, Spawner};
|
||||
|
||||
/// Test runner that will be used by the #[test] macro (only supported for the `arch-std`)
|
||||
pub struct TestRunner {
|
||||
inner: Executor,
|
||||
done: AtomicBool,
|
||||
}
|
||||
|
||||
impl Default for TestRunner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Executor::new(),
|
||||
done: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRunner {
|
||||
/// Call the closure with a spawner that can be used to spawn tasks.
|
||||
pub fn initialize(&'static self, init: impl FnOnce(Spawner)) {
|
||||
init(self.inner.spawner());
|
||||
}
|
||||
|
||||
/// Run the executor until the test is done
|
||||
pub fn run_until_done(&'static self) {
|
||||
self.inner.run_until(|| self.done.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
/// Mark the test as done
|
||||
pub fn done(&'static self) {
|
||||
self.done.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user