#![allow(clippy::result_large_err)]
use std::marker::PhantomData;
use std::time::Duration;
use crate::config::TestConfig;
use crate::ergonomic::auto_gen::{AutoGen, InferredGenerator};
use crate::ergonomic::closure_property::{ClosureProperty, PropertyClosure};
use crate::error::PropertyResult;
use crate::execution::check_with_config;
use crate::generator::{BoxedGenerator, Generator};
pub struct ErgonomicPropertyTest<T> {
config: TestConfig,
generator: Option<BoxedGenerator<T>>,
_phantom: PhantomData<T>,
}
impl<T> ErgonomicPropertyTest<T> {
pub fn new() -> Self {
Self {
config: TestConfig::default(),
generator: None,
_phantom: PhantomData,
}
}
pub fn iterations(mut self, n: usize) -> Self {
self.config.iterations = n;
self
}
pub fn seed(mut self, seed: u64) -> Self {
self.config.seed = Some(seed);
self
}
pub fn max_shrink_iterations(mut self, n: usize) -> Self {
self.config.max_shrink_iterations = n;
self
}
pub fn shrink_timeout(mut self, timeout: Duration) -> Self {
self.config.shrink_timeout = timeout;
self
}
pub fn size_hint(mut self, hint: usize) -> Self {
self.config.generator_config.size_hint = hint;
self
}
pub fn max_depth(mut self, depth: usize) -> Self {
self.config.generator_config.max_depth = depth;
self
}
pub fn with_generator<G: Generator<T> + Send + Sync + 'static>(mut self, generator: G) -> Self {
self.generator = Some(BoxedGenerator::new(generator));
self
}
pub fn run<F>(self, closure: F) -> PropertyResult<T>
where
T: Clone + std::fmt::Debug + PartialEq + AutoGen + Send + Sync + 'static,
F: PropertyClosure<T>,
{
let property = ClosureProperty::new(closure);
let generator = self
.generator
.unwrap_or_else(|| BoxedGenerator::new(InferredGenerator::<T>::new()));
check_with_config(generator, property, self.config)
}
pub fn run_with<G, F>(self, generator: G, closure: F) -> PropertyResult<T>
where
T: Clone + std::fmt::Debug + PartialEq + 'static,
G: Generator<T> + 'static,
F: PropertyClosure<T>,
{
let property = ClosureProperty::new(closure);
check_with_config(generator, property, self.config)
}
}
impl<T> Default for ErgonomicPropertyTest<T> {
fn default() -> Self {
Self::new()
}
}
pub fn property<T, F>(_closure: F) -> ErgonomicPropertyTestWithClosure<T, F>
where
F: PropertyClosure<T>,
{
ErgonomicPropertyTestWithClosure {
closure: _closure,
config: TestConfig::default(),
generator: None,
_phantom: PhantomData,
}
}
pub struct ErgonomicPropertyTestWithClosure<T, F> {
closure: F,
config: TestConfig,
generator: Option<BoxedGenerator<T>>,
_phantom: PhantomData<T>,
}
impl<T, F> ErgonomicPropertyTestWithClosure<T, F>
where
F: PropertyClosure<T>,
{
pub fn iterations(mut self, n: usize) -> Self {
self.config.iterations = n;
self
}
pub fn seed(mut self, seed: u64) -> Self {
self.config.seed = Some(seed);
self
}
pub fn max_shrink_iterations(mut self, n: usize) -> Self {
self.config.max_shrink_iterations = n;
self
}
pub fn shrink_timeout(mut self, timeout: Duration) -> Self {
self.config.shrink_timeout = timeout;
self
}
pub fn size_hint(mut self, hint: usize) -> Self {
self.config.generator_config.size_hint = hint;
self
}
pub fn max_depth(mut self, depth: usize) -> Self {
self.config.generator_config.max_depth = depth;
self
}
pub fn with_generator<G: Generator<T> + Send + Sync + 'static>(mut self, generator: G) -> Self {
self.generator = Some(BoxedGenerator::new(generator));
self
}
pub fn run(self) -> PropertyResult<T>
where
T: Clone + std::fmt::Debug + PartialEq + AutoGen + Send + Sync + 'static,
{
let property = ClosureProperty::new(self.closure);
let generator = self
.generator
.unwrap_or_else(|| BoxedGenerator::new(InferredGenerator::<T>::new()));
check_with_config(generator, property, self.config)
}
pub fn run_with<G>(self, generator: G) -> PropertyResult<T>
where
T: Clone + std::fmt::Debug + PartialEq + 'static,
G: Generator<T> + 'static,
{
let property = ClosureProperty::new(self.closure);
check_with_config(generator, property, self.config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::IntGenerator;
#[test]
fn test_builder_basic() {
let result = ErgonomicPropertyTest::<i32>::new()
.iterations(10)
.run_with(IntGenerator::new(1, 100), |x: i32| x > 0);
assert!(result.is_ok());
}
#[test]
fn test_builder_with_seed() {
let result = ErgonomicPropertyTest::<i32>::new()
.iterations(10)
.seed(42)
.run_with(IntGenerator::new(1, 100), |x: i32| x > 0);
assert!(result.is_ok());
}
#[test]
fn test_builder_with_custom_generator() {
let result = ErgonomicPropertyTest::<i32>::new()
.iterations(10)
.with_generator(IntGenerator::new(5, 10))
.run_with(IntGenerator::new(5, 10), |x: i32| (5..=10).contains(&x));
assert!(result.is_ok());
}
#[test]
fn test_property_function_basic() {
let result = property(|x: i32| x >= 0)
.iterations(10)
.run_with(IntGenerator::new(0, 100));
assert!(result.is_ok());
}
#[test]
fn test_property_function_with_seed() {
let result = property(|x: i32| x.abs() >= 0)
.iterations(10)
.seed(123)
.run_with(IntGenerator::new(-100, 100));
assert!(result.is_ok());
}
#[test]
fn test_builder_config_chaining() {
let result = ErgonomicPropertyTest::<i32>::new()
.iterations(20)
.seed(42)
.max_shrink_iterations(100)
.size_hint(5)
.max_depth(3)
.run_with(IntGenerator::new(1, 50), |x: i32| x > 0);
assert!(result.is_ok());
}
#[test]
fn test_property_failing_case() {
let result = property(|x: i32| x < 50)
.iterations(100) .seed(42) .run_with(IntGenerator::new(1, 100));
assert!(result.is_err());
}
}