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

Allow awaiting futures automatically in parameters #170

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
8 changes: 5 additions & 3 deletions rstest/tests/fixture/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@ mod should {
}
}

#[test]
fn resolve_async_fixture() {
let prj = prj("async_fixture.rs");
#[rstest]
#[case::future("async_fixture.rs")]
#[case::await_future("await_fixture.rs")]
fn resolve_async_fixture(#[case] file: &str) {
let prj = prj(file);
prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);

let output = prj.run_tests().unwrap();
Expand Down
73 changes: 73 additions & 0 deletions rstest/tests/resources/fixture/await_fixture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::io::prelude::*;

use rstest::*;

#[fixture]
async fn async_u32() -> u32 {
42
}

#[fixture]
async fn nest_fixture(#[await_future] async_u32: u32) -> u32 {
async_u32
}

#[fixture(fortytwo = async { 42 })]
async fn nest_fixture_with_default(#[await_future] fortytwo: u32) -> u32 {
fortytwo
}

#[rstest]
async fn default_is_async() {
assert_eq!(42, async_u32::default().await);
}

#[rstest]
async fn use_async_nest_fixture_default(#[await_future] nest_fixture: u32) {
assert_eq!(42, nest_fixture);
}

#[rstest(nest_fixture(async { 24 }))]
async fn use_async_nest_fixture_injected(#[await_future] nest_fixture: u32) {
assert_eq!(24, nest_fixture);
}

#[rstest]
async fn use_async_nest_fixture_with_default(#[await_future] nest_fixture_with_default: u32) {
assert_eq!(42, nest_fixture_with_default);
}

#[rstest]
async fn use_async_fixture(#[await_future] async_u32: u32) {
assert_eq!(42, async_u32);
}

#[fixture]
async fn async_impl_output() -> impl Read {
std::io::Cursor::new(vec![1, 2, 3, 4, 5])
}

#[rstest]
async fn use_async_impl_output<T: Read>(#[await_future] async_impl_output: T) {
let reader = async_impl_output;
}

#[fixture(four = async { 4 }, two = 2)]
async fn two_args_mix_fixture(#[await_future] four: u32, two: u32) -> u32 {
four * 10 + two
}

#[rstest]
async fn use_two_args_mix_fixture(#[await_future] two_args_mix_fixture: u32) {
assert_eq!(42, two_args_mix_fixture);
}

#[rstest(two_args_mix_fixture(async { 5 }))]
async fn use_two_args_mix_fixture_inject_first(#[await_future] two_args_mix_fixture: u32) {
assert_eq!(52, two_args_mix_fixture);
}

#[rstest(two_args_mix_fixture(async { 3 }, 1))]
async fn use_two_args_mix_fixture_inject_both(#[await_future] two_args_mix_fixture: u32) {
assert_eq!(31, two_args_mix_fixture);
}
28 changes: 28 additions & 0 deletions rstest/tests/resources/rstest/cases/await.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use rstest::*;

#[rstest]
#[case::pass(42, async { 42 })]
#[case::fail(42, async { 41 })]
#[should_panic]
#[case::pass_panic(42, async { 41 })]
#[should_panic]
#[case::fail_panic(42, async { 42 })]
async fn my_async_test(
#[case] expected: u32,
#[case]
#[await_future]
value: u32,
) {
assert_eq!(expected, value);
}

#[rstest]
#[case::pass(42, async { 42 })]
async fn my_async_test_revert(
#[case] expected: u32,
#[await_future]
#[case]
value: u32,
) {
assert_eq!(expected, value);
}
11 changes: 11 additions & 0 deletions rstest/tests/resources/rstest/matrix/await.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use rstest::*;

#[rstest]
async fn my_async_test(
#[await_future]
#[values(async { 1 }, async { 2 })]
first: u32,
#[values(42, 21)] second: u32,
) {
assert_eq!(42, first * second);
}
28 changes: 28 additions & 0 deletions rstest/tests/resources/rstest/single/await.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use rstest::*;

#[fixture]
async fn fixture() -> u32 {
42
}

#[rstest]
async fn should_pass(#[await_future] fixture: u32) {
assert_eq!(fixture, 42);
}

#[rstest]
async fn should_fail(#[await_future] fixture: u32) {
assert_ne!(fixture, 42);
}

#[rstest]
#[should_panic]
async fn should_panic_pass(#[await_future] fixture: u32) {
panic!(format!("My panic -> fixture = {}", fixture));
}

#[rstest]
#[should_panic]
async fn should_panic_fail(#[await_future] fixture: u32) {
assert_eq!(fixture, 42);
}
24 changes: 15 additions & 9 deletions rstest/tests/rstest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,11 @@ mod single {
.assert(output);
}

#[test]
fn should_run_async_function() {
let prj = prj(res("async.rs"));
#[rstest]
#[case::future("async.rs")]
#[case::await_future("await.rs")]
fn should_run_async_function(#[case] file: &str) {
let prj = prj(res(file));
prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);

let output = prj.run_tests().unwrap();
Expand Down Expand Up @@ -456,9 +458,11 @@ mod cases {
.assert(output);
}

#[test]
fn should_run_async_function() {
let prj = prj(res("async.rs"));
#[rstest]
#[case::future("async.rs")]
#[case::await_future("await.rs")]
fn should_run_async_function(#[case] file: &str) {
let prj = prj(res(file));
prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);

let output = prj.run_tests().unwrap();
Expand Down Expand Up @@ -775,9 +779,11 @@ mod matrix {
.assert(output);
}

#[test]
fn should_run_async_function() {
let prj = prj(res("async.rs"));
#[rstest]
#[case::future("async.rs")]
#[case::await_future("await.rs")]
fn should_run_async_function(#[case] file: &str) {
let prj = prj(res(file));
prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);

let output = prj.run_tests().unwrap();
Expand Down
4 changes: 2 additions & 2 deletions rstest_macros/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ macro_rules! merge_errors {
}

macro_rules! composed_tuple {
($i:ident) => {
($i:pat) => {
$i
};
($i:ident, $($is:ident), +) => {
($i:pat, $($is:pat), +) => {
($i, composed_tuple!($($is),*))
};
}
Expand Down
5 changes: 2 additions & 3 deletions rstest_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ pub fn fixture(
///
/// ```
/// use rstest::rstest;
///
///
/// fn sum(a: usize, b: usize) -> usize { a + b }
///
/// #[rstest]
Expand Down Expand Up @@ -845,8 +845,7 @@ pub fn fixture(
/// fn should_be_invalid_query_error(
/// repository: impl Repository,
/// #[case] user: User,
/// #[values(" ", "^%$some#@invalid!chars", ".n.o.d.o.t.s.")] query: &str,
/// query: &str
/// #[values(" ", "^%$some#@invalid!chars", ".n.o.d.o.t.s.")] query: &str
/// ) {
/// repository.find_items(&user, query).unwrap();
/// }
Expand Down
76 changes: 65 additions & 11 deletions rstest_macros/src/parse/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use syn::{
};

use super::{
extract_argument_attrs, extract_default_return_type, extract_defaults, extract_fixtures,
extract_partials_return_type, parse_vector_trailing_till_double_comma, Attributes,
ExtendWithFunctionAttrs, Fixture,
extract_argument_attrs, extract_awaits, extract_default_return_type, extract_defaults,
extract_fixtures, extract_partials_return_type, parse_vector_trailing_till_double_comma,
Attributes, ExtendWithFunctionAttrs, Fixture,
};
use crate::{
error::ErrorsVec,
Expand All @@ -21,10 +21,21 @@ use crate::{parse::Attribute, utils::attr_in};
use proc_macro2::TokenStream;
use quote::{format_ident, ToTokens};

#[derive(PartialEq, Debug, Default)]
#[derive(PartialEq, Debug)]
pub(crate) struct FixtureInfo {
pub(crate) data: FixtureData,
pub(crate) attributes: FixtureModifiers,
pub(crate) await_args: Vec<Ident>,
}

impl Default for FixtureInfo {
fn default() -> Self {
Self {
data: Default::default(),
attributes: Default::default(),
await_args: Default::default(),
}
}
}

impl Parse for FixtureModifiers {
Expand All @@ -44,6 +55,7 @@ impl Parse for FixtureInfo {
.parse::<Token![::]>()
.or_else(|_| Ok(Default::default()))
.and_then(|_| input.parse())?,
..Default::default()
}
})
}
Expand All @@ -56,12 +68,23 @@ impl ExtendWithFunctionAttrs for FixtureInfo {
) -> std::result::Result<(), ErrorsVec> {
let composed_tuple!(
fixtures,
await_args,
defaults,
default_return_type,
partials_return_type,
once
) = merge_errors!(
extract_fixtures(item_fn),
// Keep this first as it relies on the fact that attributes were not removed on arguments
extract_fixtures(
item_fn,
&self
.data
.items
.iter()
.map(|i| Some(i.ident()))
.collect::<Vec<_>>()
),
extract_awaits(item_fn),
extract_defaults(item_fn),
extract_default_return_type(item_fn),
extract_partials_return_type(item_fn),
Expand All @@ -73,6 +96,7 @@ impl ExtendWithFunctionAttrs for FixtureInfo {
.map(|f| f.into())
.chain(defaults.into_iter().map(|d| d.into())),
);
self.await_args = await_args;
if let Some(return_type) = default_return_type {
self.attributes.set_default_return_type(return_type);
}
Expand Down Expand Up @@ -117,25 +141,53 @@ fn parse_attribute_args_just_once<'a, T: Parse>(
/// Simple struct used to visit function attributes and extract Fixtures and
/// eventualy parsing errors
#[derive(Default)]
pub(crate) struct FixturesFunctionExtractor(pub(crate) Vec<Fixture>, pub(crate) Vec<syn::Error>);
pub(crate) struct FixturesFunctionExtractor<'a>(
pub(crate) Vec<Fixture>,
pub(crate) Vec<syn::Error>,
pub(crate) &'a [Option<&'a Ident>],
);

impl VisitMut for FixturesFunctionExtractor {
impl<'a> VisitMut for FixturesFunctionExtractor<'a> {
fn visit_fn_arg_mut(&mut self, node: &mut FnArg) {
if let FnArg::Typed(ref mut arg) = node {
let name = match arg.pat.as_ref() {
syn::Pat::Ident(ident) => ident.ident.clone(),
_ => return,
};
let resolved_name = {
let id_str = name.to_string();
if id_str.starts_with('_') && !id_str.starts_with("__") {
Some(Ident::new(&id_str[1..], name.span()))
} else {
None
}
};
let (extracted, remain): (Vec<_>, Vec<_>) = std::mem::take(&mut arg.attrs)
.into_iter()
.partition(|attr| attr_in(attr, &["with", "from"]));
arg.attrs = remain;

let is_fixture_by_function_attr = self
.2
.contains(&Some(resolved_name.as_ref().unwrap_or(&name)));
let is_fixture_by_default = !is_fixture_by_function_attr
&& !arg
.attrs
.iter()
.any(|attr| attr_in(attr, &["case", "default", "values"]));

let (pos, errors) = parse_attribute_args_just_once(extracted.iter(), "with");
self.1.extend(errors.into_iter());
let (resolve, errors) = parse_attribute_args_just_once(extracted.iter(), "from");
self.1.extend(errors.into_iter());
if pos.is_some() || resolve.is_some() {
let resolve = resolve.or_else(|| {
if is_fixture_by_default {
resolved_name
} else {
None
}
});
if pos.is_some() || resolve.is_some() || is_fixture_by_default {
self.0
.push(Fixture::new(name, resolve, pos.unwrap_or_default()))
}
Expand Down Expand Up @@ -352,6 +404,7 @@ mod should {
],
}
.into(),
await_args: vec![],
};

assert_eq!(expected, data);
Expand Down Expand Up @@ -447,10 +500,10 @@ mod extend {

#[test]
fn rename_with_attributes() {
let mut item_fn = r#"
let mut item_fn: ItemFn = r#"
fn test_fn(
#[from(long_fixture_name)]
#[with(42, "other")] short: u32,
#[from(long_fixture_name)]
#[with(42, "other")] short: u32,
#[from(simple)]
s: &str,
no_change: i32) {
Expand All @@ -464,6 +517,7 @@ mod extend {
.with_resolve("long_fixture_name")
.into(),
fixture("s", &[]).with_resolve("simple").into(),
fixture("no_change", &[]).into(),
]
.into(),
..Default::default()
Expand Down