diff --git a/futures-macro/src/executor.rs b/futures-macro/src/executor.rs new file mode 100644 index 0000000000..1efb48c7c7 --- /dev/null +++ b/futures-macro/src/executor.rs @@ -0,0 +1,32 @@ +use proc_macro::TokenStream; +use quote::quote; + +pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { + if !args.is_empty() { + return syn::Error::new_spanned(proc_macro2::TokenStream::from(args), "invalid argument") + .to_compile_error() + .into(); + } + + let mut input = syn::parse_macro_input!(item as syn::ItemFn); + let attrs = &input.attrs; + let vis = &input.vis; + let sig = &mut input.sig; + let body = &input.block; + + if sig.asyncness.take().is_none() { + return syn::Error::new_spanned(sig.fn_token, "Only async functions are supported") + .to_compile_error() + .into(); + } + + let gen = quote! { + #[::core::prelude::v1::test] + #(#attrs)* + #vis #sig { + ::futures_test::__private::block_on(async move #body) + } + }; + + gen.into() +} diff --git a/futures-macro/src/lib.rs b/futures-macro/src/lib.rs index 98408ebfe6..f3cc774142 100644 --- a/futures-macro/src/lib.rs +++ b/futures-macro/src/lib.rs @@ -14,6 +14,7 @@ extern crate proc_macro; use proc_macro::TokenStream; +mod executor; mod join; mod select; @@ -44,3 +45,9 @@ pub fn select_internal(input: TokenStream) -> TokenStream { pub fn select_biased_internal(input: TokenStream) -> TokenStream { crate::select::select_biased(input) } + +/// The `test` attribute. +#[proc_macro_attribute] +pub fn test_internal(input: TokenStream, item: TokenStream) -> TokenStream { + crate::executor::test(input, item) +} diff --git a/futures-test/Cargo.toml b/futures-test/Cargo.toml index 87baba18ab..f5f9a93e48 100644 --- a/futures-test/Cargo.toml +++ b/futures-test/Cargo.toml @@ -18,6 +18,7 @@ futures-io = { version = "0.3.14", path = "../futures-io", default-features = fa futures-util = { version = "=0.4.0-alpha.0", path = "../futures-util", default-features = false } futures-executor = { version = "=0.4.0-alpha.0", path = "../futures-executor", default-features = false } futures-sink = { version = "=0.4.0-alpha.0", path = "../futures-sink", default-features = false } +futures-macro = { version = "=0.4.0-alpha.0", path = "../futures-macro", default-features = false } pin-utils = { version = "0.1.0", default-features = false } pin-project = "1.0.1" diff --git a/futures-test/src/lib.rs b/futures-test/src/lib.rs index 4c26a56987..1117bb36cf 100644 --- a/futures-test/src/lib.rs +++ b/futures-test/src/lib.rs @@ -16,6 +16,7 @@ compile_error!( #[cfg(feature = "std")] pub mod __private { pub use futures_core::{future, stream, task}; + pub use futures_executor::block_on; pub use std::{ option::Option::{None, Some}, pin::Pin, @@ -49,3 +50,28 @@ pub mod io; mod assert_unmoved; mod interleave_pending; mod track_closed; + +/// Enables an `async` test function. The generated future will be run to completion with +/// [`futures_executor::block_on`](futures_executor::block_on). +/// +/// ``` +/// #[futures_test::test] +/// async fn my_test() { +/// let fut = async { true }; +/// assert!(fut.await); +/// } +/// ``` +/// +/// This is equivalent to the following code: +/// +/// ``` +/// #[test] +/// fn my_test() { +/// futures::executor::block_on(async move { +/// let fut = async { true }; +/// assert!(fut.await); +/// }) +/// } +/// ``` +#[cfg(feature = "std")] +pub use futures_macro::test_internal as test; diff --git a/futures-util/src/sink/mod.rs b/futures-util/src/sink/mod.rs index bb3c5c46f2..6ac5bd3146 100644 --- a/futures-util/src/sink/mod.rs +++ b/futures-util/src/sink/mod.rs @@ -243,7 +243,7 @@ pub trait SinkExt: Sink { /// This future will drive the stream to keep producing items until it is /// exhausted, sending each item to the sink. It will complete once both the /// stream is exhausted, the sink has received all items, and the sink has - /// been flushed. Note that the sink is **not** closed. If the stream produces + /// been flushed. Note that the sink is **not** closed. If the stream produces /// an error, that error will be returned by this future without flushing the sink. /// /// Doing `sink.send_all(stream)` is roughly equivalent to diff --git a/futures/tests/test_macro.rs b/futures/tests/test_macro.rs new file mode 100644 index 0000000000..2f391997ea --- /dev/null +++ b/futures/tests/test_macro.rs @@ -0,0 +1,15 @@ +#[futures_test::test] +async fn it_works() { + let fut = async { true }; + assert!(fut.await); + + let fut = async { false }; + assert!(!fut.await); +} + +#[should_panic] +#[futures_test::test] +async fn it_is_being_run() { + let fut = async { false }; + assert!(fut.await); +}