Giter VIP home page Giter VIP logo

rstest's Introduction

Crate Docs Status Apache 2.0 Licensed MIT Licensed

Fixture-based test framework for Rust

Introduction

rstest uses procedural macros to help you on writing fixtures and table-based tests. To use it, add the following lines to your Cargo.toml file:

[dev-dependencies]
rstest = "0.19.0"

Fixture

The core idea is that you can inject your test dependencies by passing them as test arguments. In the following example, a fixture is defined and then used in two tests, simply providing it as an argument:

use rstest::*;

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

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

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

Parametrize

You can also inject values in some other ways. For instance, you can create a set of tests by simply providing the injected values for each case: rstest will generate an independent test for each case.

use rstest::rstest;

#[rstest]
#[case(0, 0)]
#[case(1, 1)]
#[case(2, 1)]
#[case(3, 2)]
#[case(4, 3)]
fn fibonacci_test(#[case] input: u32, #[case] expected: u32) {
    assert_eq!(expected, fibonacci(input))
}

Running cargo test in this case executes five tests:

running 5 tests
test fibonacci_test::case_1 ... ok
test fibonacci_test::case_2 ... ok
test fibonacci_test::case_3 ... ok
test fibonacci_test::case_4 ... ok
test fibonacci_test::case_5 ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

If you need to just providing a bunch of values for which you need to run your test, you can use #[values(list, of, values)] argument attribute:

use rstest::rstest;

#[rstest]
fn should_be_invalid(
    #[values(None, Some(""), Some("    "))]
    value: Option<&str>
) {
    assert!(!valid(value))
}

Or create a matrix test by using list of values for some variables that will generate the cartesian product of all the values.

Use Parametrize definition in more tests

If you need to use a test list for more than one test you can use rstest_reuse crate. With this helper crate you can define a template and use it everywhere.

use rstest::rstest;
use rstest_reuse::{self, *};

#[template]
#[rstest]
#[case(2, 2)]
#[case(4/2, 2)]
fn two_simple_cases(#[case] a: u32, #[case] b: u32) {}

#[apply(two_simple_cases)]
fn it_works(#[case] a: u32, #[case] b: u32) {
    assert!(a == b);
}

See rstest_reuse for more details.

Feature flagged cases

In case you want certain test cases to only be present if a certain feature is enabled, use #[cfg_attr(feature = …, case(…))]:

use rstest::rstest;

#[rstest]
#[case(2, 2)]
#[cfg_attr(feature = "frac", case(4/2, 2))]
#[case(4/2, 2)]
fn it_works(#[case] a: u32, #[case] b: u32) {
    assert!(a == b);
}

This also works with rstest_reuse.

Magic Conversion

If you need a value where its type implement FromStr() trait you can use a literal string to build it:

# use rstest::rstest;
# use std::net::SocketAddr;
#[rstest]
#[case("1.2.3.4:8080", 8080)]
#[case("127.0.0.1:9000", 9000)]
fn check_port(#[case] addr: SocketAddr, #[case] expected: u16) {
    assert_eq!(expected, addr.port());
}

You can use this feature also in value list and in fixture default value.

Async

rstest provides out of the box async support. Just mark your test function as async, and it'll use #[async-std::test] to annotate it. This feature can be really useful to build async parametric tests using a tidy syntax:

use rstest::*;

#[rstest]
#[case(5, 2, 3)]
#[should_panic]
#[case(42, 40, 1)]
async fn my_async_test(#[case] expected: u32, #[case] a: u32, #[case] b: u32) {
    assert_eq!(expected, async_sum(a, b).await);
}

Currently, only async-std is supported out of the box. But if you need to use another runtime that provide its own test attribute (i.e. tokio::test or actix_rt::test) you can use it in your async test like described in Inject Test Attribute.

To use this feature, you need to enable attributes in the async-std features list in your Cargo.toml:

async-std = { version = "1.5", features = ["attributes"] }

If your test input is an async value (fixture or test parameter) you can use #[future] attribute to remove impl Future<Output = T> boilerplate and just use T:

use rstest::*;
#[fixture]
async fn base() -> u32 { 42 }

#[rstest]
#[case(21, async { 2 })]
#[case(6, async { 7 })]
async fn my_async_test(#[future] base: u32, #[case] expected: u32, #[future] #[case] div: u32) {
    assert_eq!(expected, base.await / div.await);
}

As you noted you should .await all future values and this sometimes can be really boring. In this case you can use #[future(awt)] to awaiting an input or annotating your function with #[awt] attributes to globally .await all your future inputs. Previous code can be simplified like follow:

use rstest::*;
# #[fixture]
# async fn base() -> u32 { 42 }
#[rstest]
#[case(21, async { 2 })]
#[case(6, async { 7 })]
#[awt]
async fn global(#[future] base: u32, #[case] expected: u32, #[future] #[case] div: u32) {
    assert_eq!(expected, base / div);
}
#[rstest]
#[case(21, async { 2 })]
#[case(6, async { 7 })]
async fn single(#[future] base: u32, #[case] expected: u32, #[future(awt)] #[case] div: u32) {
    assert_eq!(expected, base.await / div);
}

Files path as input arguments

If you need to create a test for each file in a given location you can use #[files("glob path syntax")] attribute to generate a test for each file that satisfy the given glob path.

#[rstest]
fn for_each_file(#[files("src/**/*.rs")] #[exclude("test")] path: PathBuf) {
    assert!(check_file(&path))
}

The default behavior is to ignore the files that start with ".", but you can modify this by use #[include_dot_files] attribute. The files attribute can be used more than once on the same variable, and you can also create some custom exclusion rules with the #[exclude("regex")] attributes that filter out all paths that verify the regular expression.

Default timeout

You can set a default timeout for test using the RSTEST_TIMEOUT environment variable. The value is in seconds and is evaluated on test compile time.

Test #[timeout()]

You can define an execution timeout for your tests with #[timeout(<duration>)] attribute. Timeout works both for sync and async tests and is runtime agnostic. #[timeout(<duration>)] take an expression that should return a std::time::Duration. Follow a simple async example:

use rstest::*;
use std::time::Duration;

async fn delayed_sum(a: u32, b: u32,delay: Duration) -> u32 {
    async_std::task::sleep(delay).await;
    a + b
}

#[rstest]
#[timeout(Duration::from_millis(80))]
async fn single_pass() {
    assert_eq!(4, delayed_sum(2, 2, ms(10)).await);
}

In this case test pass because the delay is just 10 milliseconds and timeout is 80 milliseconds.

You can use timeout attribute like any other attribute in your tests, and you can override a group timeout with a case specific one. In the follow example we have 3 tests where first and third use 100 milliseconds but the second one use 10 milliseconds. Another valuable point in this example is to use an expression to compute the duration.

fn ms(ms: u32) -> Duration {
    Duration::from_millis(ms.into())
}

#[rstest]
#[case::pass(ms(1), 4)]
#[timeout(ms(10))]
#[case::fail_timeout(ms(60), 4)]
#[case::fail_value(ms(1), 5)]
#[timeout(ms(100))]
async fn group_one_timeout_override(#[case] delay: Duration, #[case] expected: u32) {
    assert_eq!(expected, delayed_sum(2, 2, delay).await);
}

If you want to use timeout for async test you need to use async-timeout feature (enabled by default).

Inject Test Attribute

If you would like to use another test attribute for your test you can simply indicate it in your test function's attributes. For instance if you want to test some async function with use actix_rt::test attribute you can just write:

use rstest::*;
use actix_rt;
use std::future::Future;

#[rstest]
#[case(2, async { 4 })]
#[case(21, async { 42 })]
#[actix_rt::test]
async fn my_async_test(#[case] a: u32, #[case] #[future] result: u32) {
    assert_eq!(2 * a, result.await);
}

Just the attributes that ends with test (last path segment) can be injected.

Use #[once] Fixture

If you need to a fixture that should be initialized just once for all tests you can use #[once] attribute. rstest call your fixture function just once and return a reference to your function result to all your tests:

#[fixture]
#[once]
fn once_fixture() -> i32 { 42 }

#[rstest]
fn single(once_fixture: &i32) {
    // All tests that use once_fixture will share the same reference to once_fixture() 
    // function result.
    assert_eq!(&42, once_fixture)
}

Complete Example

All these features can be used together with a mixture of fixture variables, fixed cases and a bunch of values. For instance, you might need two test cases which test for panics, one for a logged-in user and one for a guest user.

use rstest::*;

#[fixture]
fn repository() -> InMemoryRepository {
    let mut r = InMemoryRepository::default();
    // fill repository with some data
    r
}

#[fixture]
fn alice() -> User {
    User::logged("Alice", "2001-10-04", "London", "UK")
}

#[rstest]
#[case::authorized_user(alice())] // We can use `fixture` also as standard function
#[case::guest(User::Guest)]   // We can give a name to every case : `guest` in this case
                              // and `authorized_user`
#[should_panic(expected = "Invalid query error")] // We would test a panic
fn should_be_invalid_query_error(
    repository: impl Repository,
    #[case] user: User,
    #[values("     ", "^%$some#@invalid!chars", ".n.o.d.o.t.s.")] query: &str,
) {
    repository.find_items(&user, query).unwrap();
}

This example will generate exactly 6 tests grouped by 2 different cases:

running 6 tests
test should_be_invalid_query_error::case_1_authorized_user::query_1_____ - should panic ... ok
test should_be_invalid_query_error::case_2_guest::query_2_____someinvalid_chars__ - should panic ... ok
test should_be_invalid_query_error::case_1_authorized_user::query_2_____someinvalid_chars__ - should panic ... ok
test should_be_invalid_query_error::case_2_guest::query_3____n_o_d_o_t_s___ - should panic ... ok
test should_be_invalid_query_error::case_1_authorized_user::query_3____n_o_d_o_t_s___ - should panic ... ok
test should_be_invalid_query_error::case_2_guest::query_1_____ - should panic ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Note that the names of the values try to convert the input expression in a Rust valid identifier name to help you find which tests fail.

More

Is that all? Not quite yet!

A fixture can be injected by another fixture, and they can be called using just some of its arguments.

#[fixture]
fn user(#[default("Alice")] name: &str, #[default(22)] age: u8) -> User {
    User::new(name, age)
}

#[rstest]
fn is_alice(user: User) {
    assert_eq!(user.name(), "Alice")
}

#[rstest]
fn is_22(user: User) {
    assert_eq!(user.age(), 22)
}

#[rstest]
fn is_bob(#[with("Bob")] user: User) {
    assert_eq!(user.name(), "Bob")
}

#[rstest]
fn is_42(#[with("", 42)] user: User) {
    assert_eq!(user.age(), 42)
}

As you noted you can provide default values without the need of a fixture to define it.

Finally, if you need tracing the input values you can just add the trace attribute to your test to enable the dump of all input variables.

#[rstest]
#[case(42, "FortyTwo", ("minus twelve", -12))]
#[case(24, "TwentyFour", ("minus twentyfour", -24))]
#[trace] //This attribute enable tracing
fn should_fail(#[case] number: u32, #[case] name: &str, #[case] tuple: (&str, i32)) {
    assert!(false); // <- stdout come out just for failed tests
}
running 2 tests
test should_fail::case_1 ... FAILED
test should_fail::case_2 ... FAILED

failures:

---- should_fail::case_1 stdout ----
------------ TEST ARGUMENTS ------------
number = 42
name = "FortyTwo"
tuple = ("minus twelve", -12)
-------------- TEST START --------------
thread 'should_fail::case_1' panicked at 'assertion failed: false', src/main.rs:64:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

---- should_fail::case_2 stdout ----
------------ TEST ARGUMENTS ------------
number = 24
name = "TwentyFour"
tuple = ("minus twentyfour", -24)
-------------- TEST START --------------
thread 'should_fail::case_2' panicked at 'assertion failed: false', src/main.rs:64:5


failures:
    should_fail::case_1
    should_fail::case_2

test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out

In case one or more variables don't implement the Debug trait, an error is raised, but it's also possible to exclude a variable using the #[notrace] argument attribute.

You can learn more on Docs and find more examples in tests/resources directory.

Rust version compatibility

The minimum supported Rust version is 1.67.1.

Changelog

See CHANGELOG.md

License

Licensed under either of

rstest's People

Contributors

actions-user avatar aesteve-rh avatar avhz avatar aviramha avatar borchero avatar cardosaum avatar ctron avatar dependabot[bot] avatar flokli avatar frisoft avatar kenkoooo avatar la10736 avatar michel-slm avatar narpfel avatar nukesor avatar oriontvv avatar philipcraig avatar poicipenso avatar quad avatar quenio avatar rubdos avatar svenstaro avatar touilleman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

rstest's Issues

rstest_parametrize should accept comma after last case

#[rstest_parametrize(u, s, t,
    case(42, "str", Unwrap(r#"("ss", -12)"#)),
    case(24, "trs", Unwrap(r#"("tt", -24)"#)),
)]
fn should_fail(u: u32, s: &str, t: (&str, i32)) {
    assert!(false);
}

Should work.

Pay attention: should work also if some other attributes exists at the end.

EX:

#[rstest_parametrize(u, s, t,
    case(42, "str", Unwrap(r#"("ss", -12)"#)),
    case(24, "trs", Unwrap(r#"("tt", -24)"#)),
    ::trace
)]
fn should_fail(u: u32, s: &str, t: (&str, i32)) {
    assert!(false);
}

Fixture as set of values

Investigate the possibility to define fixture that produce just a set of possible values. When we use these kind of fixture in rstest or rstest_parametrize we should produce a test case for each value.

Take care of

  • Cartesian product of all input fixtures that produce a set of values
  • Should wait depend fixture before compile (take a look to enum_dispatch crate )
  • Circular reference can create dead locks

Tests errors fail on 1.33

Error message change in beta

rustc 1.33.0-beta.5 (1045131c1 2019-01-31)

did you mean fixture? become help: a function with a similar name exists: fixture`

Use just `[rstest]` to generate test.

My macro knowledge is pretty poor so please excuse my ignorance, but I think it'd be conceptually and syntactically to use have [rstest] which may also be optionally parametrized. What do you think?

rstest_parametrize should relay on module name to group cases

Instead to repeat the test function name for each case is better to create a module with function name and create function with a generic case_xxx_<name> in the module.

So you will have a good tree view in Intellj and also a better list by cargo.

Dump arguments values before start test

Give a syntax to produce a dump of all fixture value for both rstest and rstest_parametrize.

Use specialization to implement it for non debug value (print just address). Specialization is opted in by feature that require nightly (till specialization become stable).

Standard behavior:

  • should indicate that you want dump test input values
  • can exclude some values by options
  • or enable nightly feature to dump value address

Introduce a new crate rstest_core to relay impl.

Clean generics in `fixture::default()`

In #5 we just cover the trivial cases where generics input are directly cited in output type.

Here we should cover all cases.

  • in impl (maybe just test it)
  • in dyn (maybe just test it)
  • in trait associated type (maybe just test it)
  • transitive annotate for default return type

The transitive case is the harder one. We should write the type's graph and check what input type is connected to output. Implement an annotation to define a default return type if need.

Test does not run when passed a negative number

I've been puzzled by tests that wouldn't run. I finally traced it down to giving a negative argument.

The following works:

    #[rstest_parametrize(
        input, expected,
        case(0.0, 0.0),
        case(1.0, 1.0),
        )]
    fn identity(input: f64, expected: f64) {
        assert_eq!(input, expected);
    }

and produces the following output:

running 2 tests
test point::tests::identity_case_0 ... ok
test point::tests::identity_case_1 ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Adding a case with a negative value, the test doesn't run anymore:

    #[rstest_parametrize(
        input, expected,
        case(0.0, 0.0),
        case(1.0, 1.0),
        case(-1.0, -1.0),
        )]
    fn identity(input: f64, expected: f64) {
        assert_eq!(input, expected);
    }

it produces the following output:

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

The issue happens with or without nightly.
cargo 1.33.0 (f099fe94b 2019-02-12)
rustc 1.33.0 (2aa4c46cf 2019-02-28)

cargo 1.35.0-nightly (6f3e9c367 2019-04-04)
rustc 1.35.0-nightly (acd8dd6a5 2019-04-05)

I'm using rstest 0.2.2. From Cargo.lock:

"checksum rstest 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "17060b44b74f0aed4e7ee6c970e57b5e51adbd3aecd814e1ab38a27e00901d67"

Documentation

Write some examples to document how to use the library.

Wrong error message when missing function argument

Code like this:

#[rstest_parametrize(
init, cmd, expected,
case(Unwrap("RegValue::B(0xaf)"), Unwrap("Xra(Reg::B)")),
)]
fn should_always_reset_aux_carry<I: Apply>(mut cpu: Cpu, init: I, cmd: Instruction) {
    init.apply(&mut cpu);
    cpu.set_aux_carry_state(true);

    cpu.exec(cmd).unwrap();

    assert!(!cpu.state.flags.get(AuxCarry));
}

Generate follow error:

error: macros that expand to items must be delimited with braces or followed by a semicolon
    --> src/cpu/test.rs:1555:12
     |
1555 | init, cmd, expected,
     |            ^^^^^^^^
help: change the delimiters to curly braces
     |
1555 | init, cmd,  {xpecte},
     |             ^      ^
help: add a semicolon
     |
1555 | init, cmd, expected;,
     |                    ^

error: Missed argument: 'expected' should be a test function argument.
    --> src/cpu/test.rs:1555:12
     |
1555 | init, cmd, expected,
     |            ^^^^^^^^

error: aborting due to 2 previous errors

The last part is correct but the first one is confusing and should be removed.

Meaningful error message in case formatting

Now if we write something like

#[rstest_parametrize(a, case(incorrect(some)))]
fn example(a: u32) {
    assert!(true)
}

We receive

error: custom attribute panicked
 --> src/main.rs:7:1
  |
7 | #[rstest_parametrize(a, case(incorrect(some)))]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = help: message: Unexpected case attribute: List(MetaList { ident: Ident { ident: "incorrect", span: #0 bytes(176..185) }, paren_token: Paren, nested: [Meta(Word(Ident { ident: "some", span: #0 bytes(186..190) }))] })

Should be a meaningful error message that point to incorrect(some) span.

Arbitrary rust code errors

Should span eventually error message at the code source.

Now for

#[rstest_parametrize(
condition,
case(r(r#"vec![1,5,7].contains(23)"#))
)]
fn arbitrary(condition: bool) {
    assert!(condition);
}

We receive

error[E0308]: mismatched types
  --> src/main.rs:7:1
   |
7  | / #[rstest_parametrize(
8  | | condition,
9  | | case(r(r#"vec![1,5,7].contains(23)"#))
10 | | )]
   | |__^ expected &{integer}, found integral variable
   |
   = note: expected type `&{integer}`
              found type `{integer}`
help: consider borrowing here
   |
7  | &#[rstest_parametrize(
8  | condition,
9  | case(r(r#"vec![1,5,7].contains(23)"#))
10 | )]
   |

Here is quite complicated to understand where the error is (should be &23) but in a more complicated example (more cases and wired code) should be real impossible.

The error should point at least to arbitrary code.

Question: why CaseArg::from(t).respan(a.span()) that we already use doesn't do the job?
Should we think to use quote_span! to redirect eventually errors at least to case line?

Combine rstest with proptest?

Is there any recommended/possible way of using rstest in combination with the amazing proptest? If there were, this would truly make this a killer combination for integration testing.

rstest_parametrize does not work in src/bin files

I have tried #[rstest] and #[rstest_parametrize] in a mod test{…} submodule in src and it works fine. However if I use them in a src/bin file, the #[rstest] works fine but the #[rstest_parametrize] fails to work. It seems that the rstest_parametrize stuff fails to work in a test submodule of a subsidiary binary.

Accept arbitrary rust code as case argument

As #18 pointed out is too cleaver use Unwrap("some rust code"). We can do better and parse arbitrary rust code in procedural macro attributes

Case like follow should be parsed:

case(42, -42, pippo("pluto"), Vec::new(), String::from(r#"prrr"#), 
            {
                let mut sum=0;
                for i in 1..3 {
                    sum += i;
                }
                sum
            }, vec![1,2,3])

Dynamic set of tests

In general the pattern would be:

lazy_static! {
    static ref TEST_CASES = ...; // Some computed set of cases.
}

#[rtest_from(TEST_CASES)]
fn test_case(...) {
    ...
}

This isn't just an alternative syntax, it allows computing a dynamic set of cases. In my use case, each test case is derived from a disk directory containing input files and expected output files. Right now, every time I create a new such test directory, I have to also write a silly:

#[test]
fn test_foo() {
    run_test_using_directory("foo");
}

Admittedly using rtest would reduce this overhead to just adding a single line case("foo") in a list, but it would still be best if I could just use glob to generate this list at run time, so just creating the directory would suffice.

However, this means that rtest_from can't work in the way described above; as a compile macro it has no way to access the dynamic values in TEST_CASES and therefore it can't "simply" generate a static test case function for each one.

Google search did not locate any way to generate a dynamic list of tests... is it even possible?

Originally posted by @orenbenkiki in #37 (comment)

Integrate uncover crate

Integrate uncover crate with rstest means to have a possibility to define the tags that a test/case cover or use implicity the test description as cover tag.

Custom errors are shown more than once

Maybe just the ones out of generated functions (like argument checking in rstest). That's because procedural macro output is compiled both in standard and in test and the early errors are not surrounded by #[cfg(test)].

Should be sufficient to annotate compiler errors (error_statement()) by #[cfg(test)].

I want multiple tests to share a single set of cases

Problem

I want multiple tests to share a single set of cases

Suggestion: Use a const vector/array as cases

let cases = [
  (0, "zero"),
  (1, "one"),
];

#[rstest_from(cases)]
fn mytest (case: (i32, &str)) {
  // ...
}

Matrix version of rstest_parametrize

this code:

#[rstest_matrix(
  foo => [0, 1, 2],
  bar => ['a', 'b'],
)]
fn mytest(foo: i32, bar: char) {
  // ...
}

should be equivalent to this code:

#[rstest_parametrize(
  foo, bar,
  case(0, 'a'),
  case(1, 'a'),
  case(2, 'a'),
  case(0, 'b'),
  case(1, 'b'),
  case(2, 'b'),
)]
fn mytest(foo: i32, bar: char) {
  // ...
}

Inject fixture args from test

Now there isn't any possibility to pass argument to fixture from test: fixtures use just fixtures to resolve its arguments.

I would like introduce a new syntax applicable to every test render or fixture to pass some or all arguments to the fixture.

By now it is sufficient to implement the possibility to call the fixture by the first n arguments for every n less equal to fixture arguments. In a future I can develop a way to call named args but it will be a new issue.

Syntax

From calling side every time there is an argument like name(expr_1,...,expr_n) it will try to resolve name arguments of test by call fixture with the first n arguments from (expr_1,...,expr_n).

Will generate an error if

  • name is already used (other fixture, matrix or parametrized)
  • name is not a function argument

Cavelets

We cannot use case as fixture name. Emit a warning on fixture that use case as fixture name with some arguments that will not possible to use that feature.

The partial functions return type can be hard to guess. Maybe I'll need a syntax to indicate them like I did for default

case with wrong arguments number should not compile

Example:

#[rstest_parametrize(a, case(1,2))]
fn example(a: u32) {
    assert!(true)
}

should not compile and give a meaningful error message.

Also

#[rstest_parametrize(a, case())]
fn example2(a: u32) {
    assert!(true)
}

Should indicate that case() don't match with given arguments.

Split rstest crate

Split monolithic crate in a crate to provide procedural macro and one for fixtures. The original crate will just reexport procedural macro in root and fixtures in fixtures module

Sub crate in workspace

  • rstest_core procedural macros
  • rstest_fixtures fixtures

Suppress unused variable warning

If you have a fixture that declare but not use an input fixture and use an #[allow(unused_variables)] attribute. Also the rendered fixture should suppress the same warning.

This pattern is common when you just need the fixture's data ownership and nothing else.

type SyncGuard = std::sync::MutexGuard<'static, ()>;
#[fixture]
fn synchronize() -> SyncGuard {
    use std::sync::Mutex;
    lazy_static!{
        static ref MUTEX : Mutex<()> = Mutex::new(());
    }
    MUTEX.lock().unwrap()
}

#[fixture]
#[allow(unused_variables)]
fn empty(synchronize: SyncGuard) -> TempFile {
   // Some code that need to be syncronized
}

Note take care to write an end to end test for both fixture, rstest and rstest_parametrize

Can you do an intermittent release?

I currently can't publish my crate because I really want to use ? inside #[rstest] tests but that only works with the git version. Could you publish a new version that only has those new compatibility changes?

Create a new crate for procedural macro

Investigate if rstest can be a non procedural-macro's crate that reexport the macros. So move macros in a new crate, reexport them and use rstest just for container crate (core logic if need) and reexport some base fixtures.

original fixture function should use `get()`

Replace the given function fixture(args) by call fixture::get(args).

Take care to remove mut in args definition (it will pass just the ownership) or , simpler, shut down the warning.

When we'll implement teardown we should take get() result and discard teardown

Better error message for missing fixture

Now we issue an error[E0433]: failed to resolve: use of undeclared type or module ...

Maybe we can use quote_span!{} in combination to check some fake field to report that the fixture function or annotation is missed.

Otherwise we can provide a good error by errors API at least in nightly

Rewrite `ParametrizeData::parse()`

Write unit tests for ParametrizeData::parse() and rewrite it by peek test case instead serch for ( or ::.

TestCase::peek() should check for case ident, an optional description and a parenthesis. Take a look to UnwrapRustCode::peek() as example.

Should report error for rstest_parametrize's invalid syntax

#[rstest_parametrize(datum, expected,
case("2018-12-29 18:15:33", NaiveDate::from_ymd(2018, 12, 29).and_hms(18, 15, 33)),
)]
fn t(datum: &str, expected:NaiveDate) {
    assert_eq!(1, 2, "Should not compile")
}

should report a some compile error

Fixture injection

Define a fixture procedural macro that define a fixture.

Input fixture can be simple functions or should be fixture functions. Fixture function will return Fixture wrapper that can be nested and control value life cycle.

We'll associate to fixture two static methods:

  • get(args) that return exactly what the original function returns
  • default() that return get()'s result by resolve all arguments as fixtures

Fixture will execute tear down at the end of the test and not when fixture value will be dropped.

We leave teardown implementation for next release and a new ticket. Maybe get and default will return a tuple instead a wrapper and Fixture will be a trait implemented on that tuples.

Note: We not use a wrapper for fixture return type to not go deep in the impl resolution hell. By now struct cannot wrap any function return type.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.