Skip to content

Commit

Permalink
Merge pull request #267 from marcobacis/ignore_parameter
Browse files Browse the repository at this point in the history
Add #[ignore] attribute to ignore test arguments
  • Loading branch information
la10736 authored Jul 22, 2024
2 parents fb4fa50 + cf9dd0b commit fcf732d
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

### Add

- Implemented `#[ignore]` attribute to ignore test parameters during fixtures resolution/injection. See [#228](https://github.com/la10736/rstest/issues/228) for details

### Fixed

## [0.21.0] 2024/6/1
Expand Down
28 changes: 28 additions & 0 deletions rstest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,10 @@ pub use rstest_macros::fixture;
/// in this case the `#[actix_rt::test]` attribute will replace the standard `#[test]`
/// attribute.
///
/// Some test attributes allow to inject arguments into the test function, in a similar way to rstest.
/// This can lead to compile errors when rstest is not able to resolve the additional arguments.
/// To avoid this, see [Ignoring Arguments](attr.rstest.html#ignoring-arguments).
///
/// ## Local lifetime and `#[by_ref]` attribute
///
/// In some cases you may want to use a local lifetime for some arguments of your test.
Expand Down Expand Up @@ -1182,6 +1186,30 @@ pub use rstest_macros::fixture;
/// }
/// ```
///
/// ## Ignoring Arguments
///
/// Sometimes, you may want to inject and use fixtures not managed by rstest
/// (e.g. db connection pools for sqlx tests).
///
/// In these cases, you can use the `#[ignore]` attribute to ignore the additional
/// parameter and let another crate take care of it:
///
/// ```rust, ignore
/// use rstest::*;
/// use sqlx::*;
///
/// #[fixture]
/// fn my_fixture() -> i32 { 42 }
///
/// #[rstest]
/// #[sqlx::test]
/// async fn test_db(my_fixture: i32, #[ignore] pool: PgPool) {
/// assert_eq!(42, injected);
/// // do stuff with the connection pool
/// }
/// ```
///
///
/// ## Trace Input Arguments
///
/// Sometimes can be very helpful to print all test's input arguments. To
Expand Down
16 changes: 16 additions & 0 deletions rstest/tests/resources/rstest/ignore_not_fixture_arg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use rstest::*;

use sqlx::SqlitePool;

struct FixtureStruct {}

#[fixture]
fn my_fixture() -> FixtureStruct {
FixtureStruct {}
}

#[rstest]
#[sqlx::test]
async fn test_db(my_fixture: FixtureStruct, #[ignore] pool: SqlitePool) {
assert!(true);
}
16 changes: 16 additions & 0 deletions rstest/tests/rstest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,22 @@ fn ignore_underscore_args() {
.assert(output);
}

#[test]
fn ignore_args_not_fixtures() {
let prj = prj("ignore_not_fixture_arg.rs");
prj.add_dependency(
"sqlx",
r#"{version="*", features=["sqlite","macros","runtime-tokio"]}"#,
);

let output = prj.run_tests().unwrap();

TestResults::new()
.with_contains(true)
.ok("test_db")
.assert(output);
}

#[test]
fn timeout() {
let mut prj = prj("timeout.rs");
Expand Down
30 changes: 30 additions & 0 deletions rstest_macros/src/parse/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub(crate) enum FutureArg {
pub(crate) struct ArgumentInfo {
future: FutureArg,
by_ref: bool,
ignore: bool,
}

impl ArgumentInfo {
Expand All @@ -33,6 +34,13 @@ impl ArgumentInfo {
}
}

fn ignore() -> Self {
Self {
ignore: true,
..Default::default()
}
}

fn is_future(&self) -> bool {
use FutureArg::*;

Expand All @@ -48,6 +56,10 @@ impl ArgumentInfo {
fn is_by_ref(&self) -> bool {
self.by_ref
}

fn is_ignore(&self) -> bool {
self.ignore
}
}

#[derive(PartialEq, Default, Debug)]
Expand Down Expand Up @@ -115,16 +127,34 @@ impl ArgumentsInfo {
.or_insert_with(ArgumentInfo::by_ref);
}

pub(crate) fn set_ignore(&mut self, ident: Ident) {
self.args
.entry(ident)
.and_modify(|v| v.ignore = true)
.or_insert_with(ArgumentInfo::ignore);
}

pub(crate) fn set_by_refs(&mut self, by_refs: impl Iterator<Item = Ident>) {
by_refs.for_each(|ident| self.set_by_ref(ident));
}

pub(crate) fn set_ignores(&mut self, ignores: impl Iterator<Item = Ident>) {
ignores.for_each(|ident| self.set_ignore(ident));
}

pub(crate) fn is_by_refs(&self, id: &Ident) -> bool {
self.args
.get(id)
.map(|arg| arg.is_by_ref())
.unwrap_or_default()
}

pub(crate) fn is_ignore(&self, id: &Ident) -> bool {
self.args
.get(id)
.map(|arg| arg.is_ignore())
.unwrap_or_default()
}
}

#[cfg(test)]
Expand Down
60 changes: 60 additions & 0 deletions rstest_macros/src/parse/ignore.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use syn::{visit_mut::VisitMut, Ident, ItemFn};

use crate::error::ErrorsVec;

use super::just_once::JustOnceFnArgAttributeExtractor;

pub(crate) fn extract_ignores(item_fn: &mut ItemFn) -> Result<Vec<Ident>, ErrorsVec> {
let mut extractor = JustOnceFnArgAttributeExtractor::from("ignore");
extractor.visit_item_fn_mut(item_fn);
extractor.take()
}

#[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_ignore_attribute_found(#[case] item_fn: &str) {
let mut item_fn: ItemFn = item_fn.ast();
let orig = item_fn.clone();

let by_refs = extract_ignores(&mut item_fn).unwrap();

assert_eq!(orig, item_fn);
assert!(by_refs.is_empty());
}

#[rstest]
#[case::simple("fn f(#[ignore] a: u32) {}", "fn f(a: u32) {}", &["a"])]
#[case::more_than_one(
"fn f(#[ignore] a: u32, #[ignore] b: String, #[ignore] c: std::collection::HashMap<usize, String>) {}",
r#"fn f(a: u32,
b: String,
c: std::collection::HashMap<usize, String>) {}"#,
&["a", "b", "c"])]
fn extract(#[case] item_fn: &str, #[case] expected: &str, #[case] expected_refs: &[&str]) {
let mut item_fn: ItemFn = item_fn.ast();
let expected: ItemFn = expected.ast();

let by_refs = extract_ignores(&mut item_fn).unwrap();

assert_eq!(expected, item_fn);
assert_eq!(by_refs, to_idents!(expected_refs));
}

#[test]
fn raise_error() {
let mut item_fn: ItemFn = "fn f(#[ignore] #[ignore] a: u32) {}".ast();

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

assert_in!(format!("{:?}", err), "more than once");
}
}
1 change: 1 addition & 0 deletions rstest_macros/src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub(crate) mod by_ref;
pub(crate) mod expressions;
pub(crate) mod fixture;
pub(crate) mod future;
pub(crate) mod ignore;
pub(crate) mod just_once;
pub(crate) mod rstest;
pub(crate) mod testcase;
Expand Down
7 changes: 5 additions & 2 deletions rstest_macros/src/parse/rstest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use super::{
check_timeout_attrs, extract_case_args, extract_cases, extract_excluded_trace,
extract_fixtures, extract_value_list,
future::{extract_futures, extract_global_awt},
ignore::extract_ignores,
parse_vector_trailing_till_double_comma,
testcase::TestCase,
Attribute, Attributes, ExtendWithFunctionAttrs, Fixture,
Expand Down Expand Up @@ -51,18 +52,20 @@ impl Parse for RsTestInfo {

impl ExtendWithFunctionAttrs for RsTestInfo {
fn extend_with_function_attrs(&mut self, item_fn: &mut ItemFn) -> Result<(), ErrorsVec> {
let composed_tuple!(_inner, excluded, _timeout, futures, global_awt, by_refs) = merge_errors!(
let composed_tuple!(_inner, excluded, _timeout, futures, global_awt, by_refs, ignores) = merge_errors!(
self.data.extend_with_function_attrs(item_fn),
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_by_ref(item_fn),
extract_ignores(item_fn)
)?;
self.attributes.add_notraces(excluded);
self.arguments.set_global_await(global_awt);
self.arguments.set_futures(futures.into_iter());
self.arguments.set_by_refs(by_refs.into_iter());
self.arguments.set_ignores(ignores.into_iter());
Ok(())
}
}
Expand Down
14 changes: 12 additions & 2 deletions rstest_macros/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,15 @@ fn single_test_case(
attributes.add_trace(format_ident!("trace"));
}
let generics_types = generics_types_ident(generics).cloned().collect::<Vec<_>>();
let inject = inject::resolve_aruments(args.iter(), &resolver, &generics_types);

let (injectable_args, ignored_args): (Vec<_>, Vec<_>) =
args.iter().partition(|arg| match arg.maybe_ident() {
Some(ident) => !info.arguments.is_ignore(ident),
None => true,
});

let inject = inject::resolve_aruments(injectable_args.into_iter(), &resolver, &generics_types);

let args = args
.iter()
.filter_map(MaybeIdent::maybe_ident)
Expand All @@ -273,6 +281,7 @@ fn single_test_case(
} else {
Some(resolve_default_test_attr(is_async))
};

let args = args
.into_iter()
.map(|arg| {
Expand All @@ -283,13 +292,14 @@ fn single_test_case(
}
})
.collect::<Vec<_>>();

let execute = render_test_call(testfn_name.clone().into(), &args, timeout, is_async);
let lifetimes = generics.lifetimes();

quote! {
#test_attr
#(#attrs)*
#asyncness fn #name<#(#lifetimes,)*>() #output {
#asyncness fn #name<#(#lifetimes,)*>(#(#ignored_args,)*) #output {
#test_impl
#inject
#trace_args
Expand Down

0 comments on commit fcf732d

Please sign in to comment.