This crate provides two procedural macros, #[derive(Arbitrary)]
and #[proptest]
.
Each of these macros is an alternative to the following proptest's official macros.
| test-strategy | proptest | proptest-derive |
| ------------------------------------------ | ----------------------------- | ----------------------------------- |
| #[derive(Arbitrary)]
| | #[derive(Arbitrary)]
|
| #[proptest]
| proptest ! { }
| |
The macros provided by this crate have the following advantages over the proptest's official macros.
#[derive(Arbitrary)]
and #[proptest]
)#[proptest]
)However, the syntax of this crate's macros are not compatible with the syntax of the official macros.
Add this to your Cargo.toml:
toml
[dependencies]
test-strategy = "0.3.1"
proptest = "1.0.0"
You can use #[derive(Arbitrary)]
to automatically implement proptest's Arbitrary
trait.
```rust use test_strategy::Arbitrary;
struct TestInputStruct { x: u32,
#[strategy(1..10u32)]
y: u32,
#[strategy(0..#y)]
z: u32,
}
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;
fn mytest(x: u32, #[strategy(1..10u32)] y: u32, #[strategy(0..#y)] z: u32) { assert!(1 <= y && y < 10); assert!(z <= y); } ```
Attributes can be written in the following positions.
| attribute | function | struct | enum | variant | field | function parameter |
| --------------------------------------------------- | -------- | ------ | ---- | ------- | ----- | ------------------ |
| #[strategy]
| | | | | ✔ | ✔ |
| #[any]
| | | | | ✔ | ✔ |
| #[weight]
| | | | ✔ | | |
| #[map]
| | | | | ✔ | ✔ |
| #[filter]
| ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| #[by_ref]
| | | | | ✔ | ✔ |
| #[arbitrary(args = T)]
| | ✔ | ✔ | | | |
| #[arbitrary(bound(...))]
| | ✔ | ✔ | ✔ | ✔ | |
| #[arbitrary(dump)]
| | ✔ | ✔ | | | |
| #[proptest]
| ✔ | | | | | |
| #[proptest(async = ...)]
| ✔ | | | | | |
| #[proptest(dump)]
| ✔ | | | | | |
#[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;
struct TestInput { x: u32, y: u32, } ```
```rust use proptest::{ arbitrary::{any, Arbitrary}, strategy::{BoxedStrategy, Strategy}, };
struct TestInput {
x: u32,
y: u32,
}
impl Arbitrary for TestInput {
type Parameters = ();
type Strategy = BoxedStrategy
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;
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;
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::sizerange; use teststrategy::Arbitrary;
struct TestInput {
#[any(size_range(0..16).lift())]
x: Vec
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;
struct TestInputA { #[any(InnerArgs { upper : 20, ..InnerArgs::default() })] a: Inner, }
struct TestInputB { #[any(InnerArgs::default(), upper = 20)] a: Inner, }
struct TestInputC { #[any(upper = 20)] a: Inner, }
struct InnerArgs { lower: i32, upper: i32, }
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;
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;
struct NotArbitrary;
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;
struct TestInput1 {
#[strategy(any::
struct TestInput2 {
#[strategy(any::
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;
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::
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;
struct T2 { x: u32,
#[map(|y: u32| #x + y)]
y: u32,
}
// The code above generates the following strategy.
let t2 = (any::
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;
struct T1 {
#[strategy(any::
// #[strategy(any::<Index>())]
can be omitted.
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;
struct TestInput { #[filter(#x % 2 == 0)] x: u32, } ```
You can also use multiple variables in a predicate.
```rust use test_strategy::Arbitrary;
struct T1 { x: u32, y: u32, }
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;
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;
struct TestInput { #[filter(iseven)] x: u32, } fn iseven(x: &u32) -> bool { x % 2 == 0 }
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;
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;
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;
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;
struct TestInputArgs { x_max: u32, }
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;
struct TestInputA
struct TestInputB
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::anywith; use teststrategy::Arbitrary;
struct TestInput
You can also specify where predicate instead of type.
```rust use proptest::arbitrary::{anywith, Arbitrary}; use teststrategy::Arbitrary;
struct TestInput
..
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::anywith; use teststrategy::Arbitrary;
struct TestInput
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;
fn mytest(input: i32) { // ... } ```
You can add #[strategy]
, #[any]
, #[filter]
, #[by_ref]
to the parameter of the function with # [proptest]
.
```rust use test_strategy::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;
fn mytestwithconfig(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;
fn mytestwithconfig2(_input: i32) { // ... }
fn mytestwithconfig3(_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.
toml
[dev-dependencies]
test-strategy = "0.3"
proptest = "1.1.0"
tokio = { version = "1.28.1", features = ["rt-multi-thread"] }
```rust use teststrategy::proptest; use proptest::propassert;
async fn mytestasync() {
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
fn mytest(input: i32) { // ... } ```
This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-* files for details.
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.