Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions rstest/tests/resources/rstest/cases/default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use rstest::*;

#[rstest]
#[default]
#[case(42, 42)]
#[case(0)]
fn my_default_test(#[case] expected: u32, #[case] value: u32) {
assert_eq!(expected, value);
}

#[rstest]
#[case()]
#[case(false)]
#[case(false, 0)]
#[case(false, 0, None)]
#[default]
fn multiple_default_value(#[case] bin: bool, #[case] int: i32, #[case] opt: Option<f64>) {
assert!(!bin);
assert_eq!(int, 0);
assert_eq!(opt, None);
}

#[rstest]
#[case()]
#[case(false)]
#[case(true, true)]
#[default]
fn different_default_value(#[case] expected: bool, #[case] value: bool) {
assert_eq!(expected, value);
}

#[rstest]
#[case()]
#[case(false)]
#[case(true, Some(false))]
#[case(true, Some(true))]
#[default]
fn option_default_value(#[case] expected: bool, #[case] value: Option<bool>) {
assert_eq!(expected, value.is_some());
}
23 changes: 22 additions & 1 deletion rstest/tests/rstest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,28 @@ mod cases {
.assert(output);
}

#[test]
fn should_get_default_values() {
let prj = prj(res("default.rs"));
let output = prj.run_tests().unwrap();

TestResults::new()
.ok("my_default_test::case_1")
.ok("my_default_test::case_2")
.ok("multiple_default_value::case_1")
.ok("multiple_default_value::case_2")
.ok("multiple_default_value::case_3")
.ok("multiple_default_value::case_4")
.ok("different_default_value::case_1")
.ok("different_default_value::case_2")
.ok("different_default_value::case_3")
.ok("option_default_value::case_1")
.ok("option_default_value::case_2")
.ok("option_default_value::case_3")
.ok("option_default_value::case_4")
.assert(output);
}

#[test]
fn should_use_injected_test_attr() {
let prj = prj(res("inject.rs"));
Expand Down Expand Up @@ -1303,7 +1325,6 @@ fn should_works_with_smol_async_runtime() {
.assert(output);
}


mod async_timeout_feature {
use super::*;

Expand Down
15 changes: 10 additions & 5 deletions rstest_macros/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ pub mod messages {
}

pub(crate) fn rstest(test: &ItemFn, info: &RsTestInfo) -> TokenStream {
missed_arguments(test, info.data.items.iter())
let err = missed_arguments(test, info.data.items.iter())
.chain(duplicate_arguments(info.data.items.iter()))
.chain(invalid_cases(&info.data))
.chain(case_args_without_cases(&info.data))
.chain(destruct_fixture_without_from(test, info))
.chain(test_attributes(test, &info.arguments))
.map(|e| e.to_compile_error())
.collect()
.chain(test_attributes(test, &info.arguments));

if info.arguments.is_default() {
err.map(|e| e.to_compile_error()).collect()
} else {
err.chain(invalid_cases(&info.data))
.map(|e| e.to_compile_error())
.collect()
}
}

pub(crate) fn fixture(test: &ItemFn, info: &FixtureInfo) -> TokenStream {
Expand Down
9 changes: 9 additions & 0 deletions rstest_macros/src/parse/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ impl From<syn::Attribute> for TestAttr {
pub(crate) struct ArgumentsInfo {
args: Args,
is_global_await: bool,
is_default: bool,
once: Option<syn::Attribute>,
contexts: HashSet<Pat>,
test_attr: Option<TestAttr>,
Expand Down Expand Up @@ -135,6 +136,10 @@ impl ArgumentsInfo {
self.is_global_await = is_global_await;
}

pub(crate) fn set_default(&mut self, is_default: bool) {
self.is_default = is_default;
}

pub(crate) fn set_test_attr(&mut self, test_attr: Option<TestAttr>) {
self.test_attr = test_attr;
}
Expand Down Expand Up @@ -162,6 +167,10 @@ impl ArgumentsInfo {
self.is_global_await
}

pub(crate) fn is_default(&self) -> bool {
self.is_default
}

pub(crate) fn set_once(&mut self, once: Option<syn::Attribute>) {
self.once = once
}
Expand Down
108 changes: 108 additions & 0 deletions rstest_macros/src/parse/default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use syn::{visit_mut::VisitMut, ItemFn};

use crate::error::ErrorsVec;

use super::just_once::{AttrBuilder, JustOnceFnAttributeExtractor, Validator};

pub(crate) fn extract_default(item_fn: &mut ItemFn) -> Result<bool, ErrorsVec> {
let mut extractor = JustOnceFnAttributeExtractor::<DefaultBuilder>::new("default");

extractor.visit_item_fn_mut(item_fn);
extractor.take().map(|inner| inner.is_some())
}

struct DefaultBuilder;

impl AttrBuilder<ItemFn> for DefaultBuilder {
type Out = ();

fn build(_attr: syn::Attribute, _ident: &ItemFn) -> syn::Result<Self::Out> {
Ok(())
}
}

impl Validator<ItemFn> for DefaultBuilder {
// Maybe check if all items in the fn has default or not, or let compiler make that error
}

#[cfg(test)]
mod should {
use super::*;
use crate::test::{assert_eq, *};
use rstest_test::assert_in;

#[rstest]
#[case("fn simple(a: u32) {}")]
#[case("fn more(a: u32, b: &str) {}")]
#[case("fn gen<S: AsRef<str>>(a: u32, b: S) {}")]
#[case("fn attr(#[case] a: u32, #[values(1,2)] b: i32) {}")]
fn not_change_anything_if_no_default_attribute_found(#[case] item_fn: &str) {
let mut item_fn: ItemFn = item_fn.ast();
let orig = item_fn.clone();

let composed_tuple!(default) = extract_default(&mut item_fn).unwrap();

assert_eq!(orig, item_fn);
assert!(!default);
}

#[rstest]
#[case::simple("fn f(a: u32) {}", "fn f(a: u32) {}", false)]
#[case::global_awt("#[default] fn f(a: u32) {}", "fn f(a: u32) {}", true)]
#[case::global_awt_with_inner_function(
"#[default] fn f(a: u32) { fn g(){} }",
"fn f(a: u32) { fn g(){} }",
true
)]
fn extract(#[case] item_fn: &str, #[case] expected: &str, #[case] expected_default: bool) {
let mut item_fn: ItemFn = item_fn.ast();
let expected: ItemFn = expected.ast();

let default = extract_default(&mut item_fn).unwrap();

assert_eq!(expected, item_fn);
assert_eq!(expected_default, default);
}

#[rstest]
#[case::base(r#"#[default] fn f(a: u32) {}"#, r#"fn f(a: u32) {}"#)]
#[case::two(
r#"
#[default]
#[default]
fn f(a: u32) {}
"#,
r#"fn f(a: u32) {}"#
)]
#[case::inner(
r#"
#[one]
#[default]
#[two]
fn f(a: u32) {}
"#,
r#"
#[one]
#[two]
fn f(a: u32) {}
"#
)]
fn remove_all_default_attributes(#[case] item_fn: &str, #[case] expected: &str) {
let mut item_fn: ItemFn = item_fn.ast();
let expected: ItemFn = expected.ast();

let _ = extract_default(&mut item_fn);

assert_eq!(item_fn, expected);
}

#[rstest]
#[case::no_more_than_one("#[default] #[default] fn f(a: u32) {}", "more than once")]
fn raise_error(#[case] item_fn: &str, #[case] message: &str) {
let mut item_fn: ItemFn = item_fn.ast();

let err = extract_default(&mut item_fn).unwrap_err();

assert_in!(format!("{:?}", err), message);
}
}
1 change: 1 addition & 0 deletions rstest_macros/src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub(crate) mod macros;
pub(crate) mod arguments;
pub(crate) mod by_ref;
pub(crate) mod context;
pub(crate) mod default;
pub(crate) mod expressions;
pub(crate) mod fixture;
pub(crate) mod future;
Expand Down
38 changes: 22 additions & 16 deletions rstest_macros/src/parse/rstest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use super::{
by_ref::extract_by_ref,
check_timeout_attrs,
context::extract_context,
default::extract_default,
extract_case_args, extract_cases, extract_excluded_trace, extract_fixtures, extract_value_list,
future::{extract_futures, extract_global_awt},
ignore::extract_ignores,
Expand Down Expand Up @@ -49,27 +50,32 @@ impl Parse for RsTestInfo {

impl ExtendWithFunctionAttrs for RsTestInfo {
fn extend_with_function_attrs(&mut self, item_fn: &mut ItemFn) -> Result<(), ErrorsVec> {
let (composed_tuple!(
excluded, _timeout, futures, global_awt, by_refs, ignores, contexts, test_attr
// Append here new extraction
), _inner) = merge_errors!(
merge_errors!(
extract_excluded_trace(item_fn),
check_timeout_attrs(item_fn),
extract_futures(item_fn),
extract_global_awt(item_fn),
extract_by_ref(item_fn),
extract_ignores(item_fn),
extract_context(item_fn),
extract_test_attr(item_fn),
// Append here new extraction
),
let (
composed_tuple!(
excluded, _timeout, futures, global_awt, default, by_refs, ignores, contexts,
test_attr // Append here new extraction
),
_inner,
) = merge_errors!(
merge_errors!(
extract_excluded_trace(item_fn),
check_timeout_attrs(item_fn),
extract_futures(item_fn),
extract_global_awt(item_fn),
extract_default(item_fn),
extract_by_ref(item_fn),
extract_ignores(item_fn),
extract_context(item_fn),
extract_test_attr(item_fn),
// Append here new extraction
),
// This one should be always the last one!
self.data.extend_with_function_attrs(item_fn)
)?;

self.attributes.add_notraces(excluded);
self.arguments.set_global_await(global_awt);
self.arguments.set_default(default);
self.arguments.set_futures(futures.into_iter());
self.arguments.set_by_refs(by_refs.into_iter());
self.arguments.set_ignores(ignores.into_iter());
Expand Down
36 changes: 19 additions & 17 deletions rstest_macros/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,26 @@ use crate::{
};
use wrapper::WrapByModule;

pub(crate) use fixture::render as fixture;
use crate::parse::arguments::TestAttr;
use self::apply_arguments::ApplyArguments;
use self::crate_resolver::crate_name;
use crate::parse::arguments::TestAttr;
pub(crate) use fixture::render as fixture;
pub(crate) mod apply_arguments;
pub(crate) mod inject;

pub(crate) fn single(mut test: ItemFn, mut info: RsTestInfo) -> TokenStream {
test.apply_arguments(&mut info.arguments, &mut ());

let resolver = resolver::fixtures::get(&info.arguments, info.data.fixtures());

let args = test.sig.inputs.iter().cloned().collect::<Vec<_>>();
let attrs = std::mem::take(&mut test.attrs);
let asyncness = test.sig.asyncness;
let resolver = resolver::fixtures::get(&info.arguments, info.data.fixtures());

let resolver: Box<dyn Resolver> = if info.arguments.is_default() {
Box::new((resolver, crate::resolver::DefaultResolver))
} else {
Box::new(resolver)
};
single_test_case(
&test.sig.ident,
&test.sig.ident,
Expand Down Expand Up @@ -208,20 +212,14 @@ pub(crate) fn matrix(mut test: ItemFn, mut info: RsTestInfo) -> TokenStream {
test_group(test, rendered_cases)
}

fn resolve_test_attr(
test_attr: Option<&TestAttr>,
) -> Option<TokenStream> {
fn resolve_test_attr(test_attr: Option<&TestAttr>) -> Option<TokenStream> {
match test_attr {
Some(TestAttr::Explicit(attr)) => {
Some(quote! { #attr })
}
Some(TestAttr::Explicit(attr)) => Some(quote! { #attr }),
Some(TestAttr::InAttrs) => {
// test attr is already in the attributes; we don't need to re-inject it
None
}
None => {
Some(quote! { #[test] })
}
None => Some(quote! { #[test] }),
}
}

Expand Down Expand Up @@ -376,10 +374,9 @@ fn single_test_case(
let lifetimes = if lifetimes.peek().is_some() {
Some(quote! {<#(#lifetimes,)*>})
} else {
None
None
};



quote! {
#(#attrs)*
#test_attr
Expand Down Expand Up @@ -509,10 +506,15 @@ fn cases_data(info: &RsTestInfo, name_span: Span) -> impl Iterator<Item = CaseDa
.map(|arg| info.arguments.inner_pat(&arg).clone())
.zip(case.args.iter())
.collect::<HashMap<_, _>>();
let resolver: Box<dyn Resolver> = if info.arguments.is_default() {
Box::new((resolver_case, crate::resolver::DefaultResolver))
} else {
Box::new(resolver_case)
};
CaseDataValues::new(
Ident::new(&format_case_name(case, n + 1, display_len), name_span),
case.attrs.as_slice(),
Box::new(resolver_case),
resolver,
Some(CaseInfo::new(case.description.clone(), n)),
)
}
Expand Down
Loading