pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
#![cfg_attr(coverage_nightly, coverage(off))]
//! Property-Based Testing Expansion Implementation
//! 
//! This module implements comprehensive property-based testing expansion
//! as specified in PMAT-4008, following the increase-property-testing-spec.md
//! document. The implementation focuses on hardening core components through
//! property-based testing with proptest and quickcheck.

pub mod ast_parser_properties;
pub mod state_machine_properties;
pub mod cache_consistency_properties;
pub mod dag_construction_properties;
pub mod satd_parser_properties;
pub mod test_generators;
pub mod shrinking_strategies;

use proptest::prelude::*;
use std::panic;

/// Configuration for property-based testing
#[derive(Debug, Clone)]
pub struct PropertyTestConfig {
    /// Number of test cases to generate
    pub cases: u32,
    /// Maximum shrinking iterations
    pub max_shrink_iters: u32,
    /// Enable process forking for isolation
    pub fork: bool,
    /// Test timeout in milliseconds
    pub timeout: u32,
    /// Enable parallel execution
    pub parallel: bool,
}

impl Default for PropertyTestConfig {
    fn default() -> Self {
        Self {
            cases: 1000,
            max_shrink_iters: 50,
            fork: true,
            timeout: 30_000,
            parallel: true,
        }
    }
}

/// Run a property test suite with the given configuration
pub fn run_property_suite<F>(config: PropertyTestConfig, test_fn: F) -> Result<(), TestError>
where
    F: Fn() -> Result<(), TestCaseError> + Send + Sync + 'static,
{
    let proptest_config = ProptestConfig {
        cases: config.cases,
        max_shrink_iters: config.max_shrink_iters,
        fork: config.fork,
        timeout: config.timeout,
        ..Default::default()
    };

    if config.parallel {
        // Use rayon for parallel execution
        use rayon::prelude::*;
        
        let pool = rayon::ThreadPoolBuilder::new()
            .num_threads(num_cpus::get())
            .panic_handler(|_| {}) // Suppress panic propagation
            .build()
            .map_err(|e| TestError::Setup(e.to_string()))?;

        pool.install(|| {
            (0..config.cases)
                .into_par_iter()
                .try_for_each(|_| test_fn())
                .map_err(|e| TestError::Property(format!("{:?}", e)))
        })
    } else {
        // Sequential execution
        for _ in 0..config.cases {
            test_fn().map_err(|e| TestError::Property(format!("{:?}", e)))?;
        }
        Ok(())
    }
}

/// Error types for property testing
#[derive(Debug)]
pub enum TestError {
    Setup(String),
    Property(String),
    Shrinking(String),
    Timeout,
}

impl std::fmt::Display for TestError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TestError::Setup(msg) => write!(f, "Test setup error: {}", msg),
            TestError::Property(msg) => write!(f, "Property test failed: {}", msg),
            TestError::Shrinking(msg) => write!(f, "Shrinking failed: {}", msg),
            TestError::Timeout => write!(f, "Test timed out"),
        }
    }
}

impl std::error::Error for TestError {}

/// Metrics for property test execution
#[derive(Debug, Default)]
pub struct PropertyTestMetrics {
    pub total_cases: u32,
    pub passed: u32,
    pub failed: u32,
    pub shrink_steps: u32,
    pub panics_caught: u32,
    pub timeouts: u32,
}

impl PropertyTestMetrics {
    pub fn success_rate(&self) -> f64 {
        if self.total_cases == 0 {
            0.0
        } else {
            self.passed as f64 / self.total_cases as f64
        }
    }

    pub fn defect_detection_rate(&self) -> f64 {
        if self.total_cases == 0 {
            0.0
        } else {
            self.failed as f64 / self.total_cases as f64
        }
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_property_config_defaults() {
        let config = PropertyTestConfig::default();
        assert_eq!(config.cases, 1000);
        assert_eq!(config.max_shrink_iters, 50);
        assert!(config.fork);
        assert_eq!(config.timeout, 30_000);
        assert!(config.parallel);
    }

    #[test]
    fn test_metrics_calculation() {
        let mut metrics = PropertyTestMetrics::default();
        metrics.total_cases = 100;
        metrics.passed = 95;
        metrics.failed = 5;

        assert_eq!(metrics.success_rate(), 0.95);
        assert_eq!(metrics.defect_detection_rate(), 0.05);
    }
}