Skip to content

Commit 047d4a4

Browse files
committed
Add support for async tests
1 parent f96b351 commit 047d4a4

File tree

2 files changed

+41
-8
lines changed

2 files changed

+41
-8
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,14 @@ Multiple attributes can optionally be applied to each test, for example:
123123
test_each_file! { #[ignore, cfg(target_os = "linux")] in "./resources" => test }
124124
```
125125

126+
You can specify that each test is async, for example:
127+
128+
```rust
129+
test_each_file! { #[tokio::test] async in "./resources" => test }
130+
```
131+
126132
All the options above can be combined, for example:
127133

128134
```rust
129-
test_each_file! { #[ignore, cfg(target_os = "linux")] for ["in", "out"] in "./resources" as example => |[a, b]: [&str; 2]| assert_eq!(a, b) }
135+
test_each_file! { #[tokio::test, ignore, cfg(target_os = "linux")] async for ["in", "out"] in "./resources" as example => |[a, b]: [&str; 2]| assert_eq!(a, b) }
130136
```

src/lib.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![doc = include_str!("../README.md")]
22
use proc_macro2::{Ident, Span, TokenStream};
33
use quote::{format_ident, quote};
4+
use syn::token::Async;
45
use std::collections::{HashMap, HashSet};
56
use std::ffi::OsString;
67
use std::path::{Path, PathBuf};
@@ -15,6 +16,7 @@ struct TestEachArgs {
1516
function: Expr,
1617
extensions: Vec<String>,
1718
attributes: Vec<Meta>,
19+
async_fn: Option<Async>,
1820
}
1921

2022
macro_rules! abort {
@@ -32,7 +34,7 @@ macro_rules! abort_token_stream {
3234
impl Parse for TestEachArgs {
3335
fn parse(input: ParseStream) -> syn::Result<Self> {
3436
// Optionally parse attributes if `#` is used. Aborts if none are given.
35-
let attributes = input
37+
let attributes: Vec<Meta> = input
3638
.parse::<Token![#]>()
3739
.and_then(|_| {
3840
let content;
@@ -45,6 +47,19 @@ impl Parse for TestEachArgs {
4547
})
4648
.unwrap_or_default();
4749

50+
// Optionally mark as async.
51+
// The async keyword is the error span if we did not specify an attribute.
52+
let async_span = input.span();
53+
let async_fn = match input.parse::<Token![async]>() {
54+
Ok(token) => {
55+
if attributes.is_empty() {
56+
abort!(async_span, "Expected at least one attribute (e.g., `#[tokio::test]`) when `async` is given.");
57+
}
58+
Some(token)
59+
},
60+
Err(_) => None,
61+
};
62+
4863
// Optionally parse extensions if the keyword `for` is used. Aborts if none are given.
4964
let extensions = input
5065
.parse::<Token![for]>()
@@ -97,6 +112,7 @@ impl Parse for TestEachArgs {
97112
function,
98113
extensions,
99114
attributes,
115+
async_fn,
100116
})
101117
}
102118
}
@@ -248,12 +264,23 @@ fn generate_from_tree(
248264
});
249265
}
250266

251-
stream.extend(quote! {
252-
#[test]
253-
fn #file_name() {
254-
(#function)(#arguments)
255-
}
256-
});
267+
if let Some(async_keyword) = &parsed.async_fn {
268+
// For async functions, we'd need something like `#[tokio::test]` instead of `#[test]`.
269+
// Here we assume the user will have already provided that in the list of attributes.
270+
stream.extend(quote! {
271+
#async_keyword fn #file_name() {
272+
(#function)(#arguments).await
273+
}
274+
});
275+
} else {
276+
// Default, non-async test.
277+
stream.extend(quote! {
278+
#[test]
279+
fn #file_name() {
280+
(#function)(#arguments)
281+
}
282+
});
283+
}
257284
}
258285

259286
Ok(())

0 commit comments

Comments
 (0)