use std::fmt::Debug;
use std::marker::PhantomData;
use rand::rngs::StdRng;
use rand::{Rng, RngExt, SeedableRng};
const DEFAULT_NUM_TESTS: u64 = 100;
const DEFAULT_SHRINKS: u64 = 1000;
pub trait Strategy<T>: Send + Sync {
fn generate(&self, rng: &mut dyn Rng) -> T;
fn shrink(&self, _value: &T) -> Vec<T> {
Vec::new()
}
}
macro_rules! impl_range_strategy {
($ty:ty, $range:expr) => {
impl Strategy<$ty> for RangeStrategy<$ty> {
fn generate(&self, rng: &mut dyn Rng) -> $ty {
rng.random_range($range)
}
}
};
}
#[derive(Debug, Clone)]
pub struct RangeStrategy<T> {
_marker: PhantomData<T>,
}
impl_range_strategy!(i8, i8::MIN..=i8::MAX);
impl_range_strategy!(i16, i16::MIN..=i16::MAX);
impl_range_strategy!(i32, i32::MIN..=i32::MAX);
impl_range_strategy!(i64, i64::MIN..=i64::MAX);
impl_range_strategy!(u8, u8::MIN..=u8::MAX);
impl_range_strategy!(u16, u16::MIN..=u16::MAX);
impl_range_strategy!(u32, u32::MIN..=u32::MAX);
impl_range_strategy!(u64, u64::MIN..=u64::MAX);
impl_range_strategy!(usize, usize::MIN..=usize::MAX);
impl Strategy<bool> for RangeStrategy<bool> {
fn generate(&self, rng: &mut dyn Rng) -> bool {
rng.random_bool(0.5)
}
fn shrink(&self, value: &bool) -> Vec<bool> {
if *value { vec![false] } else { vec![] }
}
}
pub fn any<T: StrategyProvider>() -> T::Strategy {
T::strategy()
}
pub trait StrategyProvider: Sized {
type Strategy: Strategy<Self> + Default + Send + Sync;
fn strategy() -> Self::Strategy {
Self::Strategy::default()
}
}
macro_rules! impl_provider {
($ty:ty) => {
impl StrategyProvider for $ty {
type Strategy = RangeStrategy<$ty>;
fn strategy() -> Self::Strategy {
RangeStrategy { _marker: PhantomData }
}
}
impl Default for RangeStrategy<$ty> {
fn default() -> Self {
RangeStrategy { _marker: PhantomData }
}
}
};
}
impl_provider!(i8);
impl_provider!(i16);
impl_provider!(i32);
impl_provider!(i64);
impl_provider!(u8);
impl_provider!(u16);
impl_provider!(u32);
impl_provider!(u64);
impl_provider!(usize);
impl_provider!(bool);
#[derive(Debug, Clone)]
pub struct VecStrategy<S> {
element_strategy: S,
min_len: usize,
max_len: usize,
}
impl<T, S> Strategy<Vec<T>> for VecStrategy<S>
where
S: Strategy<T> + Send + Sync,
T: Send + Clone,
{
fn generate(&self, rng: &mut dyn Rng) -> Vec<T> {
let len = rng.random_range(self.min_len..=self.max_len);
(0..len).map(|_| self.element_strategy.generate(rng)).collect()
}
fn shrink(&self, value: &Vec<T>) -> Vec<Vec<T>> {
let mut candidates = Vec::new();
if !value.is_empty() {
let mut smaller = (*value).clone();
smaller.pop();
candidates.push(smaller);
}
candidates
}
}
pub fn vec<T, S>(strategy: S, min_len: usize, max_len: usize) -> VecStrategy<S>
where
S: Strategy<T>,
{
VecStrategy { element_strategy: strategy, min_len, max_len }
}
pub struct MapStrategy<S, F, T, U> {
inner: S,
f: F,
_phantom: PhantomData<(T, U)>,
}
impl<S: Clone, F: Clone, T, U> Clone for MapStrategy<S, F, T, U> {
fn clone(&self) -> Self {
MapStrategy {
inner: self.inner.clone(),
f: self.f.clone(),
_phantom: PhantomData,
}
}
}
impl<S: Debug, F, T, U> Debug for MapStrategy<S, F, T, U> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MapStrategy").field("inner", &self.inner).finish()
}
}
impl<T, U, S, F> Strategy<U> for MapStrategy<S, F, T, U>
where
S: Strategy<T>,
F: Fn(T) -> U + Send + Sync,
T: Send + Sync,
U: Send + Sync,
{
fn generate(&self, rng: &mut dyn Rng) -> U {
(self.f)(self.inner.generate(rng))
}
fn shrink(&self, value: &U) -> Vec<U> {
let _ = value;
Vec::new()
}
}
pub fn map<T, U, S, F>(strategy: S, f: F) -> MapStrategy<S, F, T, U>
where
S: Strategy<T>,
F: Fn(T) -> U,
{
MapStrategy { inner: strategy, f, _phantom: PhantomData }
}
pub struct FilterStrategy<S, P, T> {
inner: S,
predicate: P,
max_attempts: u32,
_phantom: PhantomData<T>,
}
impl<S: Clone, P: Clone, T> Clone for FilterStrategy<S, P, T> {
fn clone(&self) -> Self {
FilterStrategy {
inner: self.inner.clone(),
predicate: self.predicate.clone(),
max_attempts: self.max_attempts,
_phantom: PhantomData,
}
}
}
impl<S: Debug, P, T> Debug for FilterStrategy<S, P, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FilterStrategy").field("inner", &self.inner).finish()
}
}
impl<T, S, P> Strategy<T> for FilterStrategy<S, P, T>
where
S: Strategy<T>,
P: Fn(&T) -> bool + Send + Sync,
T: Send + Sync,
{
fn generate(&self, rng: &mut dyn Rng) -> T {
for _ in 0..self.max_attempts {
let value = self.inner.generate(rng);
if (self.predicate)(&value) {
return value;
}
}
self.inner.generate(rng)
}
fn shrink(&self, value: &T) -> Vec<T> {
self.inner
.shrink(value)
.into_iter()
.filter(|v| (self.predicate)(v))
.collect()
}
}
pub fn filter<T, S, P>(strategy: S, predicate: P) -> FilterStrategy<S, P, T>
where
S: Strategy<T>,
P: Fn(&T) -> bool,
{
FilterStrategy { inner: strategy, predicate, max_attempts: 100, _phantom: PhantomData }
}
#[derive(Debug, Clone)]
pub struct PropertyConfig {
pub num_tests: u64,
pub max_shrinks: u64,
pub seed: Option<u64>,
}
impl Default for PropertyConfig {
fn default() -> Self {
PropertyConfig { num_tests: DEFAULT_NUM_TESTS, max_shrinks: DEFAULT_SHRINKS, seed: None }
}
}
pub fn check<T, S>(
_name: &str,
strategy: S,
property: impl Fn(&T) -> bool,
) where
T: Debug,
S: Strategy<T>,
{
check_with(_name, strategy, property, PropertyConfig::default());
}
pub fn check_with<T, S>(
_name: &str,
strategy: S,
property: impl Fn(&T) -> bool,
config: PropertyConfig,
) where
T: Debug,
S: Strategy<T>,
{
let seed = config.seed.unwrap_or_else(rand::random);
let mut rng = StdRng::seed_from_u64(seed);
for _ in 0..config.num_tests {
let value = strategy.generate(&mut rng);
if !property(&value) {
let shrunk = shrink_counterexample(&value, &strategy, &property, config.max_shrinks);
panic!(
"property falsified after {} test(s)\n\
seed: {seed}\n\
counterexample: {value:?}\n\
shrunk to: {shrunk:?}",
config.num_tests,
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn any_bool_generates() {
let strategy = any::<bool>();
let mut rng = &mut StdRng::seed_from_u64(42);
let val = strategy.generate(&mut rng);
let _: bool = val;
}
#[test]
fn any_i32_generates() {
let strategy = any::<i32>();
let mut rng = &mut StdRng::seed_from_u64(42);
for _ in 0..100 {
let val = strategy.generate(&mut rng);
assert!(val >= i32::MIN && val <= i32::MAX);
}
}
#[test]
fn any_u64_generates() {
let strategy = any::<u64>();
let mut rng = &mut StdRng::seed_from_u64(99);
for _ in 0..10 {
let _val = strategy.generate(&mut rng);
}
}
#[test]
fn any_is_strategy_provider() {
let _s: RangeStrategy<i32> = any::<i32>();
let _s: RangeStrategy<bool> = any::<bool>();
}
#[test]
fn check_passes_for_valid_property() {
check("identity", any::<i32>(), |a: &i32| *a + 0 == *a);
}
#[test]
fn check_panics_on_false_property() {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
check("false", any::<i32>(), |_: &i32| false);
}));
assert!(result.is_err());
}
#[test]
fn check_with_custom_config() {
let config = PropertyConfig { num_tests: 5, max_shrinks: 10, seed: Some(12345) };
check_with("custom", any::<u32>(), |_: &u32| true, config);
}
#[test]
fn bool_shrink_true_to_false() {
let strategy = any::<bool>();
let shrunk = strategy.shrink(&true);
assert_eq!(shrunk, vec![false]);
}
#[test]
fn bool_shrink_false_empty() {
let strategy = any::<bool>();
let shrunk = strategy.shrink(&false);
assert!(shrunk.is_empty());
}
#[test]
fn map_transforms_output() {
let strategy = map(any::<i32>(), |x| x.to_string());
let mut rng = &mut StdRng::seed_from_u64(7);
let val = strategy.generate(&mut rng);
let _parsed: i32 = val.parse().expect("should be a valid i32 string");
}
#[test]
fn filter_rejects_bad_values() {
let strategy = filter(any::<i32>(), |x| x % 2 == 0);
let mut rng = &mut StdRng::seed_from_u64(42);
for _ in 0..50 {
let val = strategy.generate(&mut rng);
assert!(val % 2 == 0, "filter should only produce even numbers, got {val}");
}
}
#[test]
fn vec_strategy_generates() {
let strategy = vec(any::<i32>(), 0, 5);
let mut rng = &mut StdRng::seed_from_u64(1);
for _ in 0..20 {
let v = strategy.generate(&mut rng);
assert!(v.len() <= 5, "vec len {} > max 5", v.len());
}
}
#[test]
fn vec_strategy_shrink_pops() {
let strategy = vec(any::<i32>(), 0, 10);
let candidates = strategy.shrink(&vec![1, 2, 3]);
assert_eq!(candidates, vec![vec![1, 2]]);
}
#[test]
fn vec_strategy_shrink_empty() {
let strategy = vec(any::<i32>(), 0, 10);
let candidates: Vec<Vec<i32>> = strategy.shrink(&vec![]);
assert!(candidates.is_empty());
}
#[test]
fn strategy_provider_for_all_int_types() {
let _: RangeStrategy<i8> = any::<i8>();
let _: RangeStrategy<i16> = any::<i16>();
let _: RangeStrategy<i64> = any::<i64>();
let _: RangeStrategy<u8> = any::<u8>();
let _: RangeStrategy<u16> = any::<u16>();
let _: RangeStrategy<u32> = any::<u32>();
let _: RangeStrategy<u64> = any::<u64>();
let _: RangeStrategy<usize> = any::<usize>();
}
#[test]
fn default_property_config() {
let cfg = PropertyConfig::default();
assert_eq!(cfg.num_tests, DEFAULT_NUM_TESTS);
assert_eq!(cfg.max_shrinks, DEFAULT_SHRINKS);
assert!(cfg.seed.is_none());
}
mod map_strategy {
use super::*;
#[test]
fn cloned_works() {
let s = map(any::<i32>(), |x| x * 2);
let _ = s.clone();
}
}
}
fn shrink_counterexample<T, S>(
value: &T,
strategy: &S,
property: &impl Fn(&T) -> bool,
max_shrinks: u64,
) -> String
where
T: Debug,
S: Strategy<T>,
{
let mut best_repr = format!("{:?}", value);
let mut candidates = strategy.shrink(value);
let mut iterations = 0u64;
while !candidates.is_empty() && iterations < max_shrinks {
match candidates.into_iter().find(|c| !property(c)) {
Some(candidate) => {
best_repr = format!("{:?}", candidate);
candidates = strategy.shrink(&candidate);
}
None => break,
}
iterations += 1;
}
best_repr
}