Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async test function attribute #2409

Merged
merged 5 commits into from May 7, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions 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)*
ibraheemdev marked this conversation as resolved.
Show resolved Hide resolved
#vis #sig {
::futures_test::__private::block_on(async move #body)
}
};

gen.into()
}
7 changes: 7 additions & 0 deletions futures-macro/src/lib.rs
Expand Up @@ -14,6 +14,7 @@ extern crate proc_macro;

use proc_macro::TokenStream;

mod executor;
mod join;
mod select;

Expand Down Expand Up @@ -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)
}
1 change: 1 addition & 0 deletions futures-test/Cargo.toml
Expand Up @@ -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"

Expand Down
14 changes: 14 additions & 0 deletions futures-test/src/lib.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -49,3 +50,16 @@ 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);
/// }
/// ```
ibraheemdev marked this conversation as resolved.
Show resolved Hide resolved
ibraheemdev marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(feature = "std")]
pub use futures_macro::test_internal as test;
2 changes: 1 addition & 1 deletion futures-util/src/sink/mod.rs
Expand Up @@ -243,7 +243,7 @@ pub trait SinkExt<Item>: Sink<Item> {
/// 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
Expand Down
18 changes: 18 additions & 0 deletions futures/tests/test_macro.rs
@@ -0,0 +1,18 @@
#[cfg(test)]
mod tests {
ibraheemdev marked this conversation as resolved.
Show resolved Hide resolved
#[futures_test::test]
async fn it_works() {
let fut = async { true };
assert!(fut.await);

let fut = async { false };
assert!(!fut.await);
}

#[futures_test::test]
#[should_panic]
async fn it_is_being_run() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if there is a reliable way to test if the generate test actually gets run. If the macro regresses and omits the attrs and #[test], this will silently fail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good as-is for now.
macrotest is a crate for that use case (example), but it's difficult to use with #[futures_test::test], as it will show other code generated by #[test].

let fut = async { false };
assert!(fut.await);
}
}