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()
|
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.
|
/// 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:
|
/// The following restrictions apply:
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
pub mod main;
|
pub mod main;
|
||||||
pub mod task;
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
- Added #[test] macro for std.
|
||||||
|
|
||||||
## 0.4.0 - 2023-12-05
|
## 0.4.0 - 2023-12-05
|
||||||
|
|
||||||
|
@ -62,6 +62,22 @@ mod thread {
|
|||||||
self.signaler.wait()
|
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 {
|
struct Signaler {
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
pub(crate) mod fmt;
|
pub(crate) mod fmt;
|
||||||
|
|
||||||
pub use embassy_executor_macros::task;
|
pub use embassy_executor_macros::task;
|
||||||
|
#[cfg(feature = "arch-std")]
|
||||||
|
pub use embassy_executor_macros::test;
|
||||||
|
|
||||||
macro_rules! check_at_most_one {
|
macro_rules! check_at_most_one {
|
||||||
(@amo [$($feats:literal)*] [] [$($res:tt)*]) => {
|
(@amo [$($feats:literal)*] [] [$($res:tt)*]) => {
|
||||||
@ -38,6 +40,15 @@ pub mod raw;
|
|||||||
mod spawner;
|
mod spawner;
|
||||||
pub use 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 {
|
mod config {
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
include!(concat!(env!("OUT_DIR"), "/config.rs"));
|
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