protest-criterion 1.1.0

Property-based benchmarking integration between Protest and Criterion
Documentation
//! Property-Based Benchmarking with Criterion
//!
//! This crate provides integration between [Protest](https://crates.io/crates/protest)
//! property-based testing and [Criterion](https://crates.io/crates/criterion) benchmarking.
//!
//! # Overview
//!
//! Property-based benchmarking allows you to:
//! - Benchmark functions with **diverse, generated inputs**
//! - Measure performance across the **full input space**
//! - Detect **performance regressions** with statistical analysis
//! - Understand **worst-case and average-case** performance
//!
//! # Quick Start
//!
//! ```rust,no_run
//! use criterion::{criterion_group, criterion_main, Criterion};
//! use protest_criterion::PropertyBencher;
//! use protest::primitives::IntGenerator;
//!
//! fn bench_abs(c: &mut Criterion) {
//!     c.bench_function_over_inputs(
//!         "i32::abs",
//!         |b, &input: &i32| b.iter(|| input.abs()),
//!         IntGenerator::new(-1000, 1000),
//!         100, // sample count
//!     );
//! }
//!
//! criterion_group!(benches, bench_abs);
//! criterion_main!(benches);
//! ```
//!
//! # Features
//!
//! - **Generator Integration**: Use any Protest generator for benchmark inputs
//! - **Statistical Analysis**: Leverage Criterion's statistical analysis
//! - **Regression Detection**: Automatic detection of performance changes
//! - **Input Distribution**: Benchmark across diverse input distributions
//! - **Reproducible**: Seed-based generation for consistent benchmarks
//!
//! # Examples
//!
//! ## Benchmarking Sorting Algorithms
//!
//! ```rust,no_run
//! use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId};
//! use protest_criterion::PropertyBencher;
//! use protest::primitives::VecGenerator;
//! use protest::{Generator, config::GeneratorConfig};
//!
//! fn bench_sort_algorithms(c: &mut Criterion) {
//!     let mut group = c.benchmark_group("sorting");
//!
//!     for size in [10, 100, 1000].iter() {
//!         let generator = VecGenerator::new(
//!             protest::primitives::IntGenerator::new(0, 1000),
//!             *size,
//!             *size
//!         );
//!
//!         group.bench_with_input(
//!             BenchmarkId::new("sort", size),
//!             &generator,
//!             |b, generator| {
//!                 let config = GeneratorConfig::default();
//!                 b.iter_batched(
//!                     || generator.generate(&mut rand::thread_rng(), &config),
//!                     |mut v| v.sort(),
//!                     criterion::BatchSize::SmallInput
//!                 )
//!             }
//!         );
//!     }
//!
//!     group.finish();
//! }
//!
//! criterion_group!(benches, bench_sort_algorithms);
//! criterion_main!(benches);
//! ```
//!
//! ## Benchmarking with Properties
//!
//! ```rust,no_run
//! use criterion::{criterion_group, criterion_main, Criterion};
//! use protest_criterion::PropertyBencher;
//! use protest::primitives::VecGenerator;
//! use protest::Generator;
//!
//! fn bench_property(c: &mut Criterion) {
//!     c.bench_property(
//!         "vec reversal is involutive",
//!         VecGenerator::new(
//!             protest::primitives::IntGenerator::new(0, 100),
//!             0,
//!             1000
//!         ),
//!         |v: &Vec<i32>| {
//!             let mut reversed = v.clone();
//!             reversed.reverse();
//!             reversed.reverse();
//!             assert_eq!(v, &reversed);
//!         },
//!         100 // sample count
//!     );
//! }
//!
//! criterion_group!(benches, bench_property);
//! criterion_main!(benches);
//! ```

use criterion::{BenchmarkGroup, BenchmarkId, Criterion, measurement::WallTime};
use protest::{Generator, config::GeneratorConfig};
use rand::SeedableRng;
use rand::rngs::StdRng;

/// Extension trait for Criterion to enable property-based benchmarking
pub trait PropertyBencher {
    /// Benchmark a function over inputs generated by a property-based generator
    ///
    /// # Arguments
    ///
    /// * `name` - Name of the benchmark
    /// * `bench_fn` - Function to benchmark (takes immutable reference to input)
    /// * `generator` - Generator for creating diverse test inputs
    /// * `sample_count` - Number of samples to generate and benchmark
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use criterion::{criterion_group, criterion_main, Criterion};
    /// use protest_criterion::PropertyBencher;
    /// use protest::primitives::IntGenerator;
    ///
    /// fn bench_abs(c: &mut Criterion) {
    ///     c.bench_function_over_inputs(
    ///         "i32::abs",
    ///         |b, &input: &i32| b.iter(|| input.abs()),
    ///         IntGenerator::new(-1000, 1000),
    ///         100,
    ///     );
    /// }
    ///
    /// criterion_group!(benches, bench_abs);
    /// criterion_main!(benches);
    /// ```
    fn bench_function_over_inputs<I, G, F>(
        &mut self,
        name: &str,
        bench_fn: F,
        generator: G,
        sample_count: usize,
    ) where
        I: Clone + 'static,
        G: Generator<I>,
        F: FnMut(&mut criterion::Bencher, &I);

    /// Benchmark a property test (function that takes ownership and may assert)
    ///
    /// # Arguments
    ///
    /// * `name` - Name of the benchmark
    /// * `generator` - Generator for creating diverse test inputs
    /// * `property` - Property function to benchmark
    /// * `sample_count` - Number of samples to generate and benchmark
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use criterion::{criterion_group, criterion_main, Criterion};
    /// use protest_criterion::PropertyBencher;
    /// use protest::primitives::VecGenerator;
    /// use protest::Generator;
    ///
    /// fn bench_reverse_involutive(c: &mut Criterion) {
    ///     c.bench_property(
    ///         "vec reverse is involutive",
    ///         VecGenerator::new(
    ///             protest::primitives::IntGenerator::new(0, 100),
    ///             0,
    ///             100
    ///         ),
    ///         |v: &Vec<i32>| {
    ///             let mut reversed = v.clone();
    ///             reversed.reverse();
    ///             reversed.reverse();
    ///         },
    ///         50,
    ///     );
    /// }
    ///
    /// criterion_group!(benches, bench_reverse_involutive);
    /// criterion_main!(benches);
    /// ```
    fn bench_property<I, G, P>(
        &mut self,
        name: &str,
        generator: G,
        property: P,
        sample_count: usize,
    ) where
        I: Clone + 'static,
        G: Generator<I>,
        P: Fn(&I) + 'static;
}

impl PropertyBencher for Criterion {
    fn bench_function_over_inputs<I, G, F>(
        &mut self,
        name: &str,
        mut bench_fn: F,
        generator: G,
        sample_count: usize,
    ) where
        I: Clone + 'static,
        G: Generator<I>,
        F: FnMut(&mut criterion::Bencher, &I),
    {
        let mut rng = StdRng::from_entropy();
        let config = GeneratorConfig::default();
        let inputs: Vec<I> = (0..sample_count)
            .map(|_| generator.generate(&mut rng, &config))
            .collect();

        let mut group = self.benchmark_group(name);

        for (idx, input) in inputs.iter().enumerate() {
            group.bench_with_input(BenchmarkId::from_parameter(idx), input, &mut bench_fn);
        }

        group.finish();
    }

    fn bench_property<I, G, P>(
        &mut self,
        name: &str,
        generator: G,
        property: P,
        sample_count: usize,
    ) where
        I: Clone + 'static,
        G: Generator<I>,
        P: Fn(&I) + 'static,
    {
        let mut rng = StdRng::from_entropy();
        let config = GeneratorConfig::default();
        let inputs: Vec<I> = (0..sample_count)
            .map(|_| generator.generate(&mut rng, &config))
            .collect();

        let mut group = self.benchmark_group(name);

        for (idx, input) in inputs.iter().enumerate() {
            group.bench_with_input(BenchmarkId::from_parameter(idx), &input, |b, input| {
                b.iter(|| property(input));
            });
        }

        group.finish();
    }
}

/// Helper for creating benchmark groups with property-based inputs
///
/// This provides a more ergonomic API for benchmarking multiple configurations
/// with generated inputs.
pub trait PropertyBenchmarkGroup<'a> {
    /// Benchmark over generated inputs with a specific parameter
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use criterion::{criterion_group, criterion_main, Criterion};
    /// use protest_criterion::PropertyBenchmarkGroup;
    /// use protest::primitives::VecGenerator;
    /// use protest::Generator;
    ///
    /// fn bench_by_size(c: &mut Criterion) {
    ///     let mut group = c.benchmark_group("sort_by_size");
    ///
    ///     for size in [10, 100, 1000] {
    ///         let generator = VecGenerator::new(
    ///             protest::primitives::IntGenerator::new(0, 1000),
    ///             size,
    ///             size
    ///         );
    ///
    ///         group.bench_generated(&size.to_string(), generator, |v: &Vec<i32>| {
    ///             let mut sorted = v.clone();
    ///             sorted.sort();
    ///         });
    ///     }
    ///
    ///     group.finish();
    /// }
    ///
    /// criterion_group!(benches, bench_by_size);
    /// criterion_main!(benches);
    /// ```
    fn bench_generated<I, G, F>(&mut self, id: &str, generator: G, f: F)
    where
        I: Clone + 'static,
        G: Generator<I>,
        F: FnMut(&I) + 'static;
}

impl<'a> PropertyBenchmarkGroup<'a> for BenchmarkGroup<'a, WallTime> {
    fn bench_generated<I, G, F>(&mut self, id: &str, generator: G, mut f: F)
    where
        I: Clone + 'static,
        G: Generator<I>,
        F: FnMut(&I) + 'static,
    {
        self.bench_function(id, move |b| {
            let mut rng = StdRng::from_entropy();
            let config = GeneratorConfig::default();
            b.iter_batched(
                || generator.generate(&mut rng, &config),
                |input| f(&input),
                criterion::BatchSize::SmallInput,
            );
        });
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use protest::primitives::{IntGenerator, VecGenerator};

    #[test]
    fn test_generates_inputs() {
        let generator = IntGenerator::new(0, 100);
        let mut rng = StdRng::from_seed([42; 32]);
        let config = GeneratorConfig::default();

        let inputs: Vec<i32> = (0..10)
            .map(|_| generator.generate(&mut rng, &config))
            .collect();

        assert_eq!(inputs.len(), 10);
        for input in inputs {
            assert!((0..=100).contains(&input));
        }
    }

    #[test]
    fn test_vec_generator() {
        let generator = VecGenerator::new(IntGenerator::new(0, 10), 5, 5);
        let mut rng = StdRng::from_seed([42; 32]);
        let config = GeneratorConfig::default();

        let input = generator.generate(&mut rng, &config);
        assert_eq!(input.len(), 5);
        for &val in &input {
            assert!((0..=10).contains(&val));
        }
    }
}