# test-strategy
[![Crates.io](https://img.shields.io/crates/v/test-strategy.svg)][test-strategy]
[![Docs.rs](https://docs.rs/test-strategy/badge.svg)](https://docs.rs/test-strategy/)
[![Actions Status](https://github.com/frozenlib/test-strategy/workflows/CI/badge.svg)](https://github.com/frozenlib/test-strategy/actions)
This crate provides two procedural macros, `#[derive(Arbitrary)]` and `#[proptest]`.
Each of these macros is an alternative to the following proptest's official macros.
| [`#[derive(Arbitrary)]`](#derivearbitrary) | | [`#[derive(Arbitrary)]`][offical-a] |
| [`#[proptest]`](#proptest) | [`proptest ! { }`][offical-m] | |
[test-strategy]: https://crates.io/crates/test-strategy
[proptest]: https://crates.io/crates/proptest
[proptest-derive]: https://crates.io/crates/proptest-derive
[offical-m]: https://altsysrq.github.io/rustdoc/proptest/latest/proptest/macro.proptest.html
[offical-a]: https://altsysrq.github.io/proptest-book/proptest-derive/modifiers.html
The macros provided by this crate have the following advantages over the proptest's official macros.
- Supports higher-order strategies. (`#[derive(Arbitrary)]` and `#[proptest]`)
- Code formatting is not disabled. (`#[proptest]`)
However, the syntax of this crate's macros are not compatible with the syntax of the official macros.
## Install
Add this to your Cargo.toml:
```toml
[dependencies]
test-strategy = "0.3.1"
proptest = "1.0.0"
```
## Example
You can use `#[derive(Arbitrary)]` to automatically implement proptest's `Arbitrary` trait.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInputStruct {
x: u32,
#[strategy(1..10u32)]
y: u32,
#[strategy(0..#y)]
z: u32,
}
#[derive(Arbitrary, Debug)]
enum TestInputEnum {
A,
B,
#[weight(3)]
C,
X(u32),
Y(#[strategy(0..10u32)] u32),
}
```
You can define a property test by adding `#[proptest]` to the function.
```rust
use test_strategy::proptest;
#[proptest]
fn my_test(_x: u32, #[strategy(1..10u32)] y: u32, #[strategy(0..#y)] z: u32) {
assert!(1 <= y && y < 10);
assert!(z <= y);
}
```
## Attributes
Attributes can be written in the following positions.
| [`#[strategy]`](#strategy) | | | | | ✔ | ✔ |
| [`#[any]`](#any) | | | | | ✔ | ✔ |
| [`#[weight]`](#weight) | | | | ✔ | | |
| [`#[map]`](#map) | | | | | ✔ | ✔ |
| [`#[filter]`](#filter) | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| [`#[by_ref]`](#by_ref) | | | | | ✔ | ✔ |
| [`#[arbitrary(args = T)]`](#arbitraryargs--t) | | ✔ | ✔ | | | |
| [`#[arbitrary(bound(...))]`](#arbitraryboundt1-t2-) | | ✔ | ✔ | ✔ | ✔ | |
| [`#[arbitrary(dump)]`](#arbitrarydump) | | ✔ | ✔ | | | |
| [`#[proptest]`](#proptest) | ✔ | | | | | |
| [`#[proptest(async = ...)]`](#proptestasync--) | ✔ | | | | | |
| [`#[proptest(dump)]`](#proptestdump) | ✔ | | | | | |
## `#[derive(Arbitrary)]`
You can implement `proptest::arbitrary::Arbitrary` automatically by adding `#[derive(Arbitrary)]` to struct or enum declaration.
By default, all fields are set using the strategy obtained by `proptest::arbitrary::any()`.
So the following two codes are equivalent.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
x: u32,
y: u32,
}
```
```rust
use proptest::{
arbitrary::{any, Arbitrary},
strategy::{BoxedStrategy, Strategy},
};
#[derive(Debug)]
struct TestInput {
x: u32,
y: u32,
}
impl Arbitrary for TestInput {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
let x = any::<u32>();
let y = any::<u32>();
(x, y).prop_map(|(x, y)| Self { x, y }).boxed()
}
}
```
## `#[strategy]`
You can specify a strategy to generate values for the field by adding `#[strategy(...)]` to the field.
In the following example, the value of field `x` will be less than 20.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[strategy(0..20u32)]
x: u32,
}
```
In `#[strategy]`, the values of other fields can be used by following `#` to the name of the field.
In the following example, the value of `y` is less than or equal to `x`.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
x: u32,
#[strategy(0..=#x)]
y: u32,
}
```
## `#[any]`
Instead of writing `#[strategy(any_with::<Type>(expr))]`, you can write `#[any(expr)]`.
```rust
use proptest::collection::size_range;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug, PartialEq)]
struct TestInput {
#[any(size_range(0..16).lift())]
x: Vec<u16>,
}
```
Instead of writing an expression to be passed to `any_with`, you can write only the value of the field to be changed from the default value.
Therefore, the following `TestInputA`, `TestInputB` and `TestInputC` are equivalent.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInputA {
#[any(InnerArgs { upper : 20, ..InnerArgs::default() })]
a: Inner,
}
#[derive(Arbitrary, Debug)]
struct TestInputB {
#[any(InnerArgs::default(), upper = 20)]
a: Inner,
}
#[derive(Arbitrary, Debug)]
struct TestInputC {
#[any(upper = 20)]
a: Inner,
}
#[derive(Default)]
struct InnerArgs {
lower: i32,
upper: i32,
}
#[derive(Arbitrary, Debug)]
#[arbitrary(args = InnerArgs)]
struct Inner {
#[strategy(args.lower..args.upper)]
x: i32,
}
```
## `#[weight]`
By default, all variants appear with equal probability.
You can add `#[weight]` to the variant to change the probability of the variant appearing.
In the following example, `TestInput::B` is twice as likely to appear as `TestInput::A`.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
enum TestInput {
A,
#[weight(2)]
B,
}
```
If you add `#[weight(0)]` to a variant, the variant does not appear, so you can use a type in that variant that cannot be used as `Arbitrary`.
```rust
use test_strategy::Arbitrary;
#[derive(Debug)]
struct NotArbitrary;
#[derive(Arbitrary, Debug)]
enum TestInput {
A,
#[allow(dead_code)]
#[weight(0)] // Removing this `#[weight(0)]` will cause a compile error.
B(NotArbitrary),
}
```
## `#[map]`
Instead of using `prop_map` in `#[strategy(...)]`, `#[map(...)]` can be used.
The following codes mean the same thing.
```rust
use proptest::arbitrary::any;
use proptest::strategy::Strategy;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput1 {
#[strategy(any::<u32>().prop_map(|x| x + 1))]
x: u32,
}
#[derive(Arbitrary, Debug)]
struct TestInput2 {
#[strategy(any::<u32>())]
#[map(|x| x + 1)]
x: u32,
}
#[derive(Arbitrary, Debug)]
struct TestInput3 {
#[map(|x: u32| x + 1)]
x: u32,
}
```
References to other fields in the function applied to `prop_map` or `#[map(...)]` will generate different strategies.
Referencing another field in `#[strategy(...)]` will expand it to `prop_flat_map`, even if it is in `prop_map`.
```rust
use proptest::arbitrary::any;
use proptest::strategy::{Just, Strategy};
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct T1 {
x: u32,
#[strategy(any::<u32>().prop_map(move |y| #x + y))]
y: u32,
}
// The code above generates the following strategy.
let t1 = any::<u32>()
.prop_flat_map(|x| (Just(x), any::<u32>().prop_map(move |y| x + y)))
.prop_map(|(x, y)| T1 { x, y });
```
On the other hand, if you refer to another field in `#[map]`, it will expand to `prop_map`.
```rust
use proptest::arbitrary::any;
use proptest::strategy::Strategy;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct T2 {
x: u32,
#[map(|y: u32| #x + y)]
y: u32,
}
// The code above generates the following strategy.
let t2 = (any::<u32>(), any::<u32>()).prop_map(|(x, y)| T2 { x, y });
```
If the input and output types of the function specified in `#[map]` are different, the value type of the strategy set in `#[strategy]` is the type of the function's input, not the type of the field.
```rust
use proptest::arbitrary::any;
use proptest::sample::Index;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct T1 {
#[strategy(any::<Index>())]
#[map(|i: Index| i.index(10))]
x: usize,
}
// `#[strategy(any::<Index>())]` can be omitted.
#[derive(Arbitrary, Debug)]
struct T2 {
#[map(|i: Index| i.index(10))]
x: usize,
}
```
## `#[filter]`
By adding `#[filter]` , you can limit the values generated.
In the following examples, x is an even number.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[filter(#x % 2 == 0)]
x: u32,
}
```
You can also use multiple variables in a predicate.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
#[filter((#x + #y) % 2 == 0)]
struct T1 {
x: u32,
y: u32,
}
#[derive(Arbitrary, Debug)]
struct T2 {
x: u32,
#[filter((#x + #y) % 2 == 0)]
y: u32,
}
```
You can use the value of a structure or enum in the filter by using `#self`.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
#[filter((#self.x + #self.y) % 2 == 0)]
struct TestInput {
x: u32,
y: u32,
}
```
If the expression specified for `#[filter]` does not contain a variable named by appending # to its own field name, the expression is treated as a predicate function, rather than an expression that returns a bool.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[filter(is_even)]
x: u32,
}
fn is_even(x: &u32) -> bool {
x % 2 == 0
}
#[derive(Arbitrary, Debug)]
struct T2 {
a: u32,
// Since `#a` exists but `#b` does not, it is treated as a predicate function.
#[filter(|&x| x > #a)]
b: u32,
}
```
Similarly, an expression that does not contain `#self` in the `#[filter(...)]` that it attaches to a type is treated as a predicate function.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
#[filter(is_even)]
struct T {
x: u32,
}
fn is_even(t: &T) -> bool {
t.x % 2 == 0
}
```
You can specify a filter name by passing two arguments to `#[filter]`.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[filter("x is even", #x % 2 == 0)]
x: u32,
}
```
## `#[by_ref]`
By default, if you use a variable with `#[strategy]`, `#[any]`, `#[map]` or `#[filter]` with `#` attached to it, the cloned value is set.
Adding `#[by_ref]` to the field makes it use the reference instead of the cloned value.
```rust
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[by_ref]
#[strategy(1..10u32)]
x: u32,
#[strategy(0..*#x)]
y: u32,
}
```
## `#[arbitrary]`
### `#[arbitrary(args = T)]`
Specifies the type of `Arbitrary::Parameters`.
You can use the `Rc` value of this type in `#[strategy]`, `#[any]`, or `#[filter]` with the variable name `args`.
```rust
use test_strategy::Arbitrary;
#[derive(Debug, Default)]
struct TestInputArgs {
x_max: u32,
}
#[derive(Arbitrary, Debug)]
#[arbitrary(args = TestInputArgs)]
struct TestInput {
#[strategy(0..=args.x_max)]
x: u32,
}
```
### `#[arbitrary(bound(T1, T2, ..))]`
By default, if the type of field for which `#[strategy]` is not specified contains a generic parameter, that type is set to trait bounds.
Therefore, the following `TestInputA` and `TestInputB` are equivalent.
```rust
use proptest::{
arbitrary::any, arbitrary::Arbitrary, strategy::BoxedStrategy, strategy::Strategy,
};
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInputA<T> {
x: T,
}
#[derive(Debug)]
struct TestInputB<T> {
x: T,
}
impl<T: Arbitrary + 'static> Arbitrary for TestInputB<T> {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<T>().prop_map(|x| Self { x }).boxed()
}
}
```
Types of fields with `#[strategy]` do not set trait bounds automatically, so you need to set trait bound manually with `#[arbitrary(bound(T))]`.
```rust
use proptest::arbitrary::any_with;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug, PartialEq)]
#[arbitrary(bound(T))]
struct TestInput<T> {
#[strategy(any_with::<T>(Default::default()))]
x: T,
}
```
You can also specify where predicate instead of type.
```rust
use proptest::arbitrary::{any_with, Arbitrary};
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug, PartialEq)]
#[arbitrary(bound(T : Arbitrary + 'static))]
struct TestInput<T> {
#[strategy(any_with::<T>(Default::default()))]
x: T,
}
```
`..` means automatically generated trait bounds.
The following example uses a manually specified trait bounds in addition to the automatically generated trait bounds.
```rust
use proptest::arbitrary::any_with;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug, PartialEq)]
#[arbitrary(bound(T1, ..))]
struct TestInput<T1, T2> {
#[strategy(any_with::<T1>(Default::default()))]
x: T1,
y: T2,
}
```
### `#[arbitrary(dump)]`
Causes a compile error and outputs the code generated by `#[derive(Arbitrary)]` as an error message.
## `#[proptest]`
`#[proptest]` is the attribute used instead of `#[test]` when defining a property test.
The following example defines a test that takes a variety of integers as input.
```rust
use test_strategy::proptest;
#[proptest]
fn my_test(_input: i32) {
// ...
}
```
You can add `#[strategy]`, `#[any]`, `#[filter]`, `#[by_ref]` to the parameter of the function with `# [proptest]`.
```rust
use test_strategy::proptest;
#[proptest]
fn my_test2(#[strategy(10..20)] _input: i32) {
// ...
}
```
You can change the configuration of a property test by setting the argument of `#[proptest]` attribute to a value of `proptest::prelude::ProptestConfig` type.
```rust
use proptest::prelude::ProptestConfig;
use test_strategy::proptest;
#[proptest(ProptestConfig { cases : 1000, ..ProptestConfig::default() })]
fn my_test_with_config(_input: i32) {
// ...
}
```
As with `#[any]`, you can also set only the value of the field to be changed from the default value.
The example below is equivalent to the one above.
```rust
use proptest::prelude::ProptestConfig;
use test_strategy::proptest;
#[proptest(ProptestConfig::default(), cases = 1000)]
fn my_test_with_config_2(_input: i32) {
// ...
}
#[proptest(cases = 1000)]
fn my_test_with_config_3(_input: i32) {
// ...
}
```
### `#[proptest(async = ...)]`
Async functions can be tested by setting `async = ...` to the argument of `#[proptest]`.
The following values are allowed after `async =`.
The value specifies the asynchronous runtime used for the test.
- "tokio"
```toml
[dev-dependencies]
test-strategy = "0.3"
proptest = "1.1.0"
tokio = { version = "1.28.1", features = ["rt-multi-thread"] }
```
```rust
use test_strategy::proptest;
use proptest::prop_assert;
#[proptest(async = "tokio")]
async fn my_test_async() {
async { }.await;
prop_assert!(true);
}
```
### `#[proptest(dump)]`
You can use `#[proptest(dump)]` and output the code generated by `#[proptest]` as an compile error message.
```compile_fail
#[proptest(dump)]
fn my_test(_input: i32) {
// ...
}
```
## License
This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-\* files for details.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.