yare 3.0.0

Lean parameterized test case library for Rust 🚀
Documentation
# Yare ⛵

Procedural macro based parameterized testing library. Run a test case with many different inputs.
Test cases can be defined using the 'parameterized' attribute instead of the 'test' attribute.

**Features**

- **Parameterization:** Specify different inputs to test multiple scenarios with a single test definition.
- **Flexible:** Parameterized test case arguments are expressions.
- **Out of the box experience:** Works with any Rust version out of the box. No custom test harness necessary.
- **Reusable:** Promotes code reuse by defining test cases once and using them with different parameters across multiple
  tests.
- **Readable:** Maintains readability by using a familiar Rustic attribute syntax.
- **Identifiable:** Each test case has a user defined name which can be referred back to, and can be used to run
  individual test cases.
- **Battle tested:** Used for years in tests of
  the [cargo-msrv]https://crates.io/crates/cargo-msrv, [bisector]https://crates.io/crates/bisector
  and [rust-releases]https://crates.io/crates/rust-releases crates (amongst others).

## Table of contents

* [Introduction]#yare-
* [Examples]#examples-back-to-top
* [Arguments are expressions]#arguments-are-expressions-back-to-top
* [Custom test macro (e.g. tokio::test)]#custom-test-macro-eg-tokiotest-back-to-top
* [Return types]#return-types-back-to-top
* [Function qualifiers]#function-qualifiers-back-to-top
* [Global #[parameterized(,,,)] import]#globally-importing-parameterized-back-to-top
* [License]#license-back-to-top

## Examples <sup>(<a href="#yare-">back to top</a>)</sup>

**A first example**

```rust
fn add5<T: Into<u32>>(component: T) -> u32 {
    component.into() + 5
}

#[cfg(test)]
mod tests {
    use super::*;
    use yare::parameterized;

    #[parameterized(
      zero_plus_five = { 0, 5 },
      one_plus_five = { 1, 6 },
      two_plus_five = { 2, 7 },
    )]
    fn test_add5(input: u16, expected: u32) {
        assert_eq!(add5(input), expected);
    }
}
```

**An example with values**

```rust
enum Fruit {
    Apple,
    Bramble(BrambleFruit),
    Pear,
}

trait NameOf {
    fn name_of(&self) -> &str;
}

impl NameOf for Fruit {
    fn name_of(&self) -> &str {
        match self {
            Fruit::Apple => "apple",
            Fruit::Bramble(fruit) => fruit.name_of(),
            Fruit::Pear => "pear",
        }
    }
}

enum BrambleFruit {
    Blackberry,
}

impl NameOf for BrambleFruit {
    fn name_of(&self) -> &str {
        match self {
            BrambleFruit::Blackberry => "blackberry",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use yare::parameterized;

    #[parameterized(
      apple = { Fruit::Apple, "apple" },
      pear = { Fruit::Pear, "pear" },
      blackberry = { Fruit::Bramble(BrambleFruit::Blackberry), "blackberry" },
    )]
    fn a_fruity_test(fruit: Fruit, name: &str) {
        assert_eq!(fruit.name_of(), name)
    }
}
```

<br>

## Arguments are expressions <sup>(<a href="#yare-">back to top</a>)</sup>

While the arguments above were simple values, any expression can be used as argument in a test case.

**Example**

In the example below, we roll the dice 3 times to generate a seed for later roll_dice function calls.
The first argument `seed1` is a _function call_ to roll_dice. This randomness function is seeded with value `0`.
The second argument `seed2` is a _block expression_. In the expression the roll_dice function is called twice.
The test itself takes the maximum of `seed1` and `seed2`, rolls the die 1000 times, and checks that all values
are valid for a d6 die.

```rust
use std::sync::atomic::{AtomicU32, Ordering};
use yare::parameterized;

#[parameterized(
  seeding_randomness_with_two_dice_rolls = 
  {
    roll_dice(&AtomicU32::new(0)),                              // <- This is an expression (a function call)
    {                                                           // <- This is also an expression (a block expression)
      let from_half = roll_dice( &AtomicU32::new(u32::MAX / 2));
      let from_max = roll_dice( &AtomicU32::new(u32::MAX));
      
      u8::min(from_half, from_max)
    }
  }
)]
fn dicey(seed1: u8, seed2: u8) {
    // Creating a base seed in a complicated way for the sake of it.
    let max = u8::max(seed1, seed2);
    let seed = AtomicU32::new(u32::from(max));

    let out_of_bounds_values = (0..1000)         // roll the dice 1000 times
        .map(|_| roll_dice(&seed))
        .find(|value| !(1..=6).contains(value)); // check that the outputs of the dice are just 1, 2, 3, 4, 5 or 6. 

    assert!(out_of_bounds_values.is_none());
}
```

## Custom test macro (e.g. tokio::test) <sup>(<a href="#yare-">back to top</a>)</sup>

By default, the code generation step of the `parameterized` attribute will generate test cases marked with a `#[test]`
attribute. For example, the `add5` test from the [examples](#examples-back-to-top) would generate something like:

```rust
#[cfg(test)]
mod tests {
    use super::*;
    use yare::parameterized;

    // Approximate generated code from add5 example:
    #[cfg(test)]
    mod add5 {
        use super::*;

        #[test]
        fn zero_plus_five() {
            let input: u16 = 0;
            let expected: u32 = 5;
            assert_eq!(add5(input), expected);
        }

        #[test]
        fn one_plus_five() {
            let input: u16 = 1;
            let expected: u32 = 6;
            assert_eq!(add5(input), expected);
        }

        #[test]
        fn two_plus_five() {
            let input: u16 = 2;
            let expected: u32 = 7;
            assert_eq!(add5(input), expected);
        }
    }
}
```

However, sometimes a different test macro is desired. An example is when writing tests for projects which depend on tokio.
For this, you may want to use `#[tokio::test]` (it also requires the test function to also have the `async` qualifier).

In Yare, it is possible to specify a custom test macro. To do so, you may add the `#[test_macro(...)]` attribute _after_
a `#[parameterized]` attribute.

**Custom test macro example: tokio::test**

```rust,ignore
use yare::parameterized;

#[parameterized(
    zero_wait = { 0, 0 },
    show_paused = { 500, 0 },
)]
#[test_macro(tokio::test(start_paused = true))]
async fn test(wait: u64, time_elapsed: u128) {
    let start = std::time::Instant::now();
    tokio::time::sleep(tokio::time::Duration::from_millis(wait)).await;

    assert_eq!(start.elapsed().as_millis(), time_elapsed);
}
```

Gotchas:

* The `#[test_macro(...)]` must always be specified after a `#[parameterized(...)]` attribute.
* Only one `#[test_macro(...)]` attribute per parameterized test function is allowed.
* While you can rename the parameterized attribute using import aliassing (
  e.g. `use yare::parameterized as pm`), the `test_macro` attribute cannot be renamed,
  since it's not actually defined as a separate macro.
  Instead, the `parameterized` macro parses this attribute as well.

## Return types <sup>(<a href="#yare-">back to top</a>)</sup>

Yare supports specifying a return type for a parameterized test function.

Note that the underlying test attribute must also have support for return types.
By default, Yare generates individual test cases decorated with the
familiar [test](https://doc.rust-lang.org/reference/attributes/testing.html#the-test-attribute)
attribute, which is included with any Rust distribution by default.

**Example**

```rust
use yare::parameterized;

#[parameterized(
  ok = { Ok(0) },
  // err = {  Err("noes!".to_string()) }, <-- enabling this would result in a failed test, since the error code will not be an `ErrorCode::Success`. See the `Termination` trait for more.
)]
fn test(value: Result<u32, String>) -> Result<(), String> {
    let v = value?;

    assert_eq!(v.unwrap(), 0);
}

```

## Function qualifiers <sup>(<a href="#yare-">back to top</a>)</sup>

Yare supports the following function qualifiers: `const`, `async`, `unsafe` and `extern`.
This is particularly useful if you use `#[parameterized(...)]` with a custom test macro such as `tokio::test`, instead
of the built-in test macro.

**Example**

```rust
use yare::parameterized;

#[parameterized(
  purple = { & [128, 0, 128] },
  orange = { & [255, 127, 0] },
)]
const extern "C" fn has_reds(streamed_color: &[u8]) {
    assert!(streamed_color.first().is_some());
}
```

## Globally importing parameterized <sup>(<a href="#yare-">back to top</a>)</sup>

If you prefer not to import this library (with `use yare::parameterized;`) in every test module, you can put
the following snippet at the top of your crate root:

```rust
#[cfg(test)]
#[macro_use]
extern crate yare;
```

## License <sup>(<a href="#yare-">back to top</a>)</sup>

Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.

<br>

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.