Expand description
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.
- 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:
[dependencies]
test-strategy = "0.2.1"
proptest = "1.0.0"§Example
You can use #[derive(Arbitrary)] to automatically implement proptest’s Arbitrary trait.
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.
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);
}§Helper attributes
Helper attributes can be written in the following positions.
| attribute | function | struct | enum | variant | field | function parameter |
|---|---|---|---|---|---|---|
#[strategy] | ✔ | ✔ | ||||
#[any] | ✔ | ✔ | ||||
#[weight] | ✔ | |||||
#[filter] | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
#[by_ref] | ✔ | ✔ | ||||
#[arbitrary(args = T)] | ✔ | ✔ | ||||
#[arbitrary(bound(...))] | ✔ | ✔ | ✔ | ✔ | ||
#[arbitrary(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.
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
x: u32,
y: u32,
}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.
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.
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)].
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.
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.
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.
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),
}§#[filter]
By adding #[filter] , you can limit the values generated.
In the following examples, x is an even number.
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[filter(#x % 2 == 0)]
x: u32,
}You can also use multiple variables in a predicate.
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
#[filter((#x + #y) % 2 == 0)]
struct TestInput {
x: u32,
y: u32,
}You can use the value of a structure or enum in the filter by using #self.
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
#[filter((#self.x + #self.y) % 2 == 0)]
struct TestInput {
x: u32,
y: u32,
}If the argument of the #[filter] does not contain a variable marked with #, it is treated as a predicate function.
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[filter(is_even)]
x: u32,
}
fn is_even(x: &u32) -> bool {
x % 2 == 0
}use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
#[filter(x_is_even)]
struct TestInput {
x: u32,
}
fn x_is_even(input: &TestInput) -> bool {
input.x % 2 == 0
}You can specify a filter name by passing two arguments to #[filter].
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[filter("filter name", #x % 2 == 0)]
x: u32,
}§#[by_ref]
By default, if you use a variable with #[strategy], #[any] 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.
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 value of this type in #[strategy], #[any], or #[filter] with the variable name args.
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.
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))].
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.
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.
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.
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].
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.
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.
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_dump]
You can add #[proptest_dump] to #[proptest] and output the code generated by #[proptest] as an compile error message.
#[proptest]
#[proptest_dump]
fn my_test(_input: i32) {
// ...
}