Skip to content

Commit

Permalink
Merge pull request #250 from la10736/by_ref
Browse files Browse the repository at this point in the history
Implement `#[by_ref]` attribute
  • Loading branch information
la10736 committed May 5, 2024
2 parents 021299d + d5dcbe4 commit 0b62d04
Show file tree
Hide file tree
Showing 21 changed files with 941 additions and 480 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,10 @@

### Add

- Implemented `#[by_ref]` attribute to take get a local lifetime for test arguments.
See [#241](https://github.com/la10736/rstest/issues/241) for more details. Thanks to
@narpfel for suggesting it and useful discussions.

### Fixed

- Don't remove Lifetimes from test function if any. See [#230](https://github.com/la10736/rstest/issues/230)
Expand Down
30 changes: 30 additions & 0 deletions README.md
Expand Up @@ -342,6 +342,36 @@ fn single(once_fixture: &i32) {
}
```

## Local timeout and `#[by_ref]` attribute

In some cases you may want to use a local lifetime for some arguments of your test.
In these cases you can use the `#[by_ref]` attribute then use the reference instead
the value.

```rust
enum E<'a> {
A(bool),
B(&'a Cell<E<'a>>),
}

fn make_e_from_bool<'a>(_bump: &'a (), b: bool) -> E<'a> {
E::A(b)
}

#[fixture]
fn bump() -> () {}

#[rstest]
#[case(true, E::A(true))]
fn it_works<'a>(#[by_ref] bump: &'a (), #[case] b: bool, #[case] expected: E<'a>) {
let actual = make_e_from_bool(&bump, b);
assert_eq!(actual, expected);
}
```

You can use `#[by_ref]` attribute for all arguments of your test and not just for fixture
but also for cases, values and files.

## Complete Example

All these features can be used together with a mixture of fixture variables,
Expand Down
2 changes: 1 addition & 1 deletion rstest/src/timeout.rs
Expand Up @@ -173,7 +173,7 @@ mod tests {
|| {
panic!("inner message");
},
Duration::from_millis(30),
Duration::from_millis(100),
)
}

Expand Down
36 changes: 16 additions & 20 deletions rstest/tests/fixture/mod.rs
Expand Up @@ -236,7 +236,7 @@ mod should {
#[rstest]
fn when_cannot_resolve_fixture(errors_rs: &(Output, String)) {
let (output, name) = errors_rs.clone();

assert_in!(output.stderr.str(), "error[E0433]: ");
assert_in!(
output.stderr.str(),
Expand All @@ -254,7 +254,7 @@ mod should {
#[rstest]
fn on_mismatched_types_inner(errors_rs: &(Output, String)) {
let (output, name) = errors_rs.clone();

assert_in!(
output.stderr.str(),
format!(
Expand All @@ -273,7 +273,7 @@ mod should {
#[rstest]
fn on_mismatched_types_argument(errors_rs: &(Output, String)) {
let (output, name) = errors_rs.clone();

assert_in!(
output.stderr.str(),
format!(
Expand All @@ -285,7 +285,7 @@ mod should {
)
.unindent()
);

assert_in!(
output.stderr.str(),
"
Expand Down Expand Up @@ -335,7 +335,6 @@ mod should {
);
}


#[fixture]
#[once]
fn errors_once_rs() -> (Output, String) {
Expand All @@ -350,10 +349,9 @@ mod should {
format!(
r#"
error: Cannot apply #[once] to async fixture.
--> {}/src/lib.rs:4:3
--> {}/src/lib.rs:4:1
|
4 | #[once]
| ^^^^
"#,
name
)
Expand All @@ -364,15 +362,15 @@ mod should {
#[rstest]
fn once_generic_type(errors_once_rs: &(Output, String)) {
let (output, name) = errors_once_rs.clone();

assert_in!(
output.stderr.str(),
format!(
r#"
error: Cannot apply #[once] on generic fixture.
--> {}/src/lib.rs:9:3
--> {}/src/lib.rs:9:1
|
9 | #[once]
| ^^^^
"#,
name
)
Expand All @@ -384,22 +382,20 @@ mod should {
fn once_generic_impl(errors_once_rs: &(Output, String)) {
let (output, name) = errors_once_rs.clone();
assert_in!(
output.stderr.str(),
format!(
r#"
output.stderr.str(),
format!(
r#"
error: Cannot apply #[once] on generic fixture.
--> {}/src/lib.rs:15:3
--> {}/src/lib.rs:15:1
|
15 | #[once]
| ^^^^
"#,
name
)
.unindent()
name
)
.unindent()
);

}

#[rstest]
fn once_on_not_sync_type(errors_once_rs: &(Output, String)) {
let (output, name) = errors_once_rs.clone();
Expand All @@ -418,5 +414,5 @@ mod should {
.unindent(),
);
}
}
}
}
38 changes: 38 additions & 0 deletions rstest/tests/resources/rstest/by_ref.rs
@@ -0,0 +1,38 @@
use rstest::*;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;

#[rstest]
fn start_with_name(
#[files("files/**/*.txt")]
#[by_ref]
path: &PathBuf,
) {
let name = path.file_name().unwrap();
let mut f = File::open(&path).unwrap();
let mut contents = String::new();
f.read_to_string(&mut contents).unwrap();

assert!(contents.starts_with(name.to_str().unwrap()))
}

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

#[rstest]
#[case(42)]
fn test(
#[by_ref] f: &u32,
#[case]
#[by_ref]
c: &u32,
#[values(42, 142)]
#[by_ref]
v: &u32,
) {
assert_eq!(f, c);
assert_eq!(*c, *v % 100);
}
28 changes: 28 additions & 0 deletions rstest/tests/resources/rstest/local_lifetime.rs
@@ -0,0 +1,28 @@
use std::cell::Cell;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum E<'a> {
A(bool),
B(&'a Cell<E<'a>>),
}

fn make_e_from_bool<'a>(_bump: &'a (), b: bool) -> E<'a> {
E::A(b)
}

#[cfg(test)]
mod tests {
use rstest::*;

use super::*;

#[fixture]
fn bump() -> () {}

#[rstest]
#[case(true, E::A(true))]
fn it_works<'a>(#[by_ref] bump: &'a (), #[case] b: bool, #[case] expected: E<'a>) {
let actual = make_e_from_bool(&bump, b);
assert_eq!(actual, expected);
}
}
26 changes: 26 additions & 0 deletions rstest/tests/rstest/mod.rs
Expand Up @@ -1052,6 +1052,32 @@ fn timeout() {
.assert(output);
}

#[test]
fn local_lifetime() {
let (output, _) = run_test("local_lifetime.rs");

TestResults::new()
.ok("tests::it_works::case_1")
.assert(output);
}

#[test]
fn by_ref() {
let prj = prj("by_ref.rs");
let files_path = prj.path().join("files");
std::fs::create_dir(&files_path).unwrap();
let name = "my_name.txt";
let mut out = File::create(files_path.join(name)).unwrap();
out.write_all(name.as_bytes()).unwrap();
let output = prj.run_tests().unwrap();

TestResults::new()
.ok("test::case_1::v_1_42")
.ok("test::case_1::v_2_142")
.ok("start_with_name::path_1_files_my_name_txt")
.assert(output);
}

mod async_timeout_feature {
use super::*;

Expand Down
2 changes: 1 addition & 1 deletion rstest_macros/src/error.rs
Expand Up @@ -79,7 +79,7 @@ fn generics_once<'a>(test: &'a ItemFn, info: &FixtureInfo) -> Errors<'a> {
}

#[derive(Debug, Default)]
pub(crate) struct ErrorsVec(Vec<syn::Error>);
pub struct ErrorsVec(Vec<syn::Error>);

pub(crate) fn _merge_errors<R1, R2>(
r1: Result<R1, ErrorsVec>,
Expand Down
35 changes: 33 additions & 2 deletions rstest_macros/src/lib.rs
Expand Up @@ -6,8 +6,6 @@ extern crate proc_macro;
// Test utility module
#[cfg(test)]
pub(crate) mod test;
#[cfg(test)]
use rstest_reuse;

#[macro_use]
mod error;
Expand Down Expand Up @@ -886,6 +884,39 @@ pub fn fixture(
/// in this case the `#[actix_rt::test]` attribute will replace the standard `#[test]`
/// attribute.
///
/// ## Local timeout and `#[by_ref]` attribute
///
/// In some cases you may want to use a local lifetime for some arguments of your test.
/// In these cases you can use the `#[by_ref]` attribute then use the reference instead
/// the value.
///
/// ```rust
/// # use std::cell::Cell;
/// # use rstest::*;
/// # #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// enum E<'a> {
/// A(bool),
/// B(&'a Cell<E<'a>>),
/// }
///
/// fn make_e_from_bool<'a>(_bump: &'a (), b: bool) -> E<'a> {
/// E::A(b)
/// }
///
/// #[fixture]
/// fn bump() -> () {}
///
/// #[rstest]
/// #[case(true, E::A(true))]
/// fn it_works<'a>(#[by_ref] bump: &'a (), #[case] b: bool, #[case] expected: E<'a>) {
/// let actual = make_e_from_bool(&bump, b);
/// assert_eq!(actual, expected);
/// }
/// ```
///
/// You can use `#[by_ref]` attribute for all arguments of your test and not just for fixture
/// but also for cases, values and files.
///
/// ## Putting all Together
///
/// All these features can be used together with a mixture of fixture variables,
Expand Down

0 comments on commit 0b62d04

Please sign in to comment.