Macro zebra_test::prelude::prop_compose

macro_rules! prop_compose {
    ($(#[$meta:meta])*
     $vis:vis
     $([$($modi:tt)*])? fn $name:ident $params:tt
     ($($var:pat in $strategy:expr),+ $(,)?)
       -> $return_type:ty $body:block) => { ... };
    ($(#[$meta:meta])*
     $vis:vis
     $([$($modi:tt)*])? fn $name:ident $params:tt
     ($($var:pat in $strategy:expr),+ $(,)?)
     ($($var2:pat in $strategy2:expr),+ $(,)?)
       -> $return_type:ty $body:block) => { ... };
    ($(#[$meta:meta])*
     $vis:vis
     $([$($modi:tt)*])? fn $name:ident $params:tt
     ($($arg:tt)+)
       -> $return_type:ty $body:block) => { ... };
    ($(#[$meta:meta])*
     $vis:vis
     $([$($modi:tt)*])? fn $name:ident $params:tt
     ($($arg:tt)+ $(,)?)
     ($($arg2:tt)+ $(,)?)
       -> $return_type:ty $body:block) => { ... };
}
Expand description

Convenience to define functions which produce new strategies.

The macro has two general forms. In the first, you define a function with two argument lists. The first argument list uses the usual syntax and becomes exactly the argument list of the defined function. The second argument list uses the in strategy syntax as with proptest!, and is used to generate the other inputs for the function. The second argument list has access to all arguments in the first. The return type indicates the type of value being generated; the final return type of the function is impl Strategy<Value = $type>.

use proptest::prelude::*;

#[derive(Clone, Debug)]
struct MyStruct {
  integer: u32,
  string: String,
}

prop_compose! {
  fn my_struct_strategy(max_integer: u32)
                       (integer in 0..max_integer, string in ".*")
                       -> MyStruct {
    MyStruct { integer, string }
  }
}

This form is simply sugar around making a tuple and then calling prop_map on it. You can also use arg: type as in proptest! { .. }:


prop_compose! {
  fn my_struct_strategy(max_integer: u32)
                       (integer in 0..max_integer, string: String)
                       -> MyStruct {
    MyStruct { integer, string }
  }
}

The second form is mostly the same, except that it takes three argument lists. The third argument list can see all values in both prior, which permits producing strategies based on other strategies.

use proptest::prelude::*;

prop_compose! {
  fn nearby_numbers()(centre in -1000..1000)
                   (a in centre-10..centre+10,
                    b in centre-10..centre+10)
                   -> (i32, i32) {
    (a, b)
  }
}

However, the body of the function does not have access to the second argument list. If the body needs access to those values, they must be passed through explicitly.

use proptest::prelude::*;

prop_compose! {
  fn vec_and_index
    (max_length: usize)
    (vec in prop::collection::vec(1..10, 1..max_length))
    (index in 0..vec.len(), vec in Just(vec))
    -> (Vec<i32>, usize)
  {
    (vec, index)
  }
}

The second form is sugar around making a strategy tuple, calling prop_flat_map(), then prop_map().

To give the function any modifier which isn’t a visibility modifier, put it in brackets before the fn token but after any visibility modifier.

use proptest::prelude::*;

prop_compose! {
  pub(crate) [unsafe] fn pointer()(v in prop::num::usize::ANY)
                                -> *const () {
    v as *const ()
  }
}

§Comparison with Hypothesis’ @composite

prop_compose! makes it easy to do a lot of things you can do with Hypothesis’ @composite, but not everything.

  • You can’t filter via this macro. For filtering, you need to make the strategy the “normal” way and use prop_filter().

  • More than two layers of strategies or arbitrary logic between the two layers. If you need either of these, you can achieve them by calling prop_flat_map() by hand.