protest/
config.rs

1//! Configuration types for controlling test behavior and generation parameters.
2
3use std::any::{Any, TypeId};
4use std::collections::HashMap;
5use std::time::Duration;
6
7/// Configuration validation errors
8#[derive(Debug, Clone, PartialEq)]
9pub enum ConfigError {
10    /// Invalid number of iterations (must be > 0)
11    InvalidIterations(usize),
12    /// Invalid number of shrink iterations (must be > 0)
13    InvalidShrinkIterations(usize),
14    /// Invalid timeout (must be > 0)
15    InvalidTimeout,
16    /// Invalid max depth (must be > 0)
17    InvalidMaxDepth(usize),
18}
19
20impl std::fmt::Display for ConfigError {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        match self {
23            ConfigError::InvalidIterations(n) => {
24                write!(f, "Invalid iterations count: {} (must be > 0)", n)
25            }
26            ConfigError::InvalidShrinkIterations(n) => {
27                write!(f, "Invalid shrink iterations count: {} (must be > 0)", n)
28            }
29            ConfigError::InvalidTimeout => {
30                write!(f, "Invalid timeout (must be > 0)")
31            }
32            ConfigError::InvalidMaxDepth(n) => {
33                write!(f, "Invalid max depth: {} (must be > 0)", n)
34            }
35        }
36    }
37}
38
39impl std::error::Error for ConfigError {}
40
41/// Configuration for generators
42#[derive(Debug)]
43pub struct GeneratorConfig {
44    /// Hint for the size of generated collections
45    pub size_hint: usize,
46    /// Maximum depth for nested structures
47    pub max_depth: usize,
48    /// Custom ranges and constraints for specific types
49    pub custom_ranges: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
50}
51
52impl Clone for GeneratorConfig {
53    fn clone(&self) -> Self {
54        Self {
55            size_hint: self.size_hint,
56            max_depth: self.max_depth,
57            // We can't clone the custom_ranges, so we create a new empty HashMap
58            // This is acceptable for now as custom ranges will be handled differently
59            custom_ranges: HashMap::new(),
60        }
61    }
62}
63
64impl Default for GeneratorConfig {
65    fn default() -> Self {
66        Self {
67            size_hint: 10,
68            max_depth: 5,
69            custom_ranges: HashMap::new(),
70        }
71    }
72}
73
74impl GeneratorConfig {
75    /// Create a new generator configuration with validation
76    pub fn new(
77        size_hint: usize,
78        max_depth: usize,
79        custom_ranges: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
80    ) -> Result<Self, ConfigError> {
81        if max_depth == 0 {
82            return Err(ConfigError::InvalidMaxDepth(max_depth));
83        }
84
85        Ok(Self {
86            size_hint,
87            max_depth,
88            custom_ranges,
89        })
90    }
91
92    /// Validate the generator configuration
93    pub fn validate(&self) -> Result<(), ConfigError> {
94        if self.max_depth == 0 {
95            return Err(ConfigError::InvalidMaxDepth(self.max_depth));
96        }
97        Ok(())
98    }
99
100    /// Merge this configuration with another, with this config taking precedence for non-default values
101    pub fn merge_with(self, _other: &GeneratorConfig) -> Self {
102        // For now, we can't clone custom_ranges due to trait object limitations
103        // In a real implementation, we'd need a different approach for custom ranges
104        Self {
105            size_hint: if self.size_hint != 10 {
106                self.size_hint
107            } else {
108                _other.size_hint
109            },
110            max_depth: if self.max_depth != 5 {
111                self.max_depth
112            } else {
113                _other.max_depth
114            },
115            custom_ranges: self.custom_ranges, // Always use self's custom ranges
116        }
117    }
118}
119
120/// Configuration for individual property tests
121#[derive(Debug, Clone)]
122pub struct TestConfig {
123    /// Number of test iterations to run
124    pub iterations: usize,
125    /// Maximum number of shrinking iterations
126    pub max_shrink_iterations: usize,
127    /// Timeout for shrinking process
128    pub shrink_timeout: Duration,
129    /// Optional seed for reproducible tests
130    pub seed: Option<u64>,
131    /// Generator configuration overrides
132    pub generator_config: GeneratorConfig,
133}
134
135impl Default for TestConfig {
136    fn default() -> Self {
137        Self {
138            iterations: 100,
139            max_shrink_iterations: 1000,
140            shrink_timeout: Duration::from_secs(10),
141            seed: None,
142            generator_config: GeneratorConfig::default(),
143        }
144    }
145}
146
147impl TestConfig {
148    /// Create a new test configuration with validation
149    pub fn new(
150        iterations: usize,
151        max_shrink_iterations: usize,
152        shrink_timeout: Duration,
153        seed: Option<u64>,
154        generator_config: GeneratorConfig,
155    ) -> Result<Self, ConfigError> {
156        if iterations == 0 {
157            return Err(ConfigError::InvalidIterations(iterations));
158        }
159        if max_shrink_iterations == 0 {
160            return Err(ConfigError::InvalidShrinkIterations(max_shrink_iterations));
161        }
162        if shrink_timeout.is_zero() {
163            return Err(ConfigError::InvalidTimeout);
164        }
165
166        Ok(Self {
167            iterations,
168            max_shrink_iterations,
169            shrink_timeout,
170            seed,
171            generator_config,
172        })
173    }
174
175    /// Validate the test configuration
176    pub fn validate(&self) -> Result<(), ConfigError> {
177        if self.iterations == 0 {
178            return Err(ConfigError::InvalidIterations(self.iterations));
179        }
180        if self.max_shrink_iterations == 0 {
181            return Err(ConfigError::InvalidShrinkIterations(
182                self.max_shrink_iterations,
183            ));
184        }
185        if self.shrink_timeout.is_zero() {
186            return Err(ConfigError::InvalidTimeout);
187        }
188        self.generator_config.validate()?;
189        Ok(())
190    }
191
192    /// Merge this configuration with a global configuration, with this config taking precedence
193    pub fn merge_with_global(self, global: &GlobalConfig) -> Self {
194        Self {
195            iterations: self.iterations,
196            max_shrink_iterations: self.max_shrink_iterations,
197            shrink_timeout: self.shrink_timeout,
198            seed: self.seed.or(global.default_seed),
199            generator_config: self.generator_config.merge_with(&global.generator_config),
200        }
201    }
202
203    /// Create a test configuration from global defaults with optional overrides
204    pub fn from_global_with_overrides(
205        global: &GlobalConfig,
206        iterations: Option<usize>,
207        seed: Option<u64>,
208        generator_overrides: Option<GeneratorConfig>,
209    ) -> Result<Self, ConfigError> {
210        let config = Self {
211            iterations: iterations.unwrap_or(global.default_iterations),
212            max_shrink_iterations: 1000,             // Default value
213            shrink_timeout: Duration::from_secs(10), // Default value
214            seed: seed.or(global.default_seed),
215            generator_config: generator_overrides
216                .unwrap_or_else(|| global.generator_config.clone()),
217        };
218        config.validate()?;
219        Ok(config)
220    }
221}
222
223/// Global configuration for default test behavior
224#[derive(Debug, Clone)]
225pub struct GlobalConfig {
226    /// Default number of iterations for tests
227    pub default_iterations: usize,
228    /// Default seed for reproducible tests
229    pub default_seed: Option<u64>,
230    /// Default generator configuration
231    pub generator_config: GeneratorConfig,
232}
233
234impl Default for GlobalConfig {
235    fn default() -> Self {
236        Self {
237            default_iterations: 100,
238            default_seed: None,
239            generator_config: GeneratorConfig::default(),
240        }
241    }
242}
243
244impl GlobalConfig {
245    /// Create a new global configuration with validation
246    pub fn new(
247        default_iterations: usize,
248        default_seed: Option<u64>,
249        generator_config: GeneratorConfig,
250    ) -> Result<Self, ConfigError> {
251        if default_iterations == 0 {
252            return Err(ConfigError::InvalidIterations(default_iterations));
253        }
254
255        Ok(Self {
256            default_iterations,
257            default_seed,
258            generator_config,
259        })
260    }
261
262    /// Validate the global configuration
263    pub fn validate(&self) -> Result<(), ConfigError> {
264        if self.default_iterations == 0 {
265            return Err(ConfigError::InvalidIterations(self.default_iterations));
266        }
267        self.generator_config.validate()?;
268        Ok(())
269    }
270}
271
272/// Statistics about value generation
273#[derive(Debug, Default)]
274pub struct GenerationStats {
275    /// Total values generated
276    pub total_generated: usize,
277    /// Distribution information (type-specific)
278    pub distribution_info: HashMap<String, Box<dyn Any + Send + Sync>>,
279    /// Coverage information for generated value ranges
280    pub coverage_info: CoverageInfo,
281    /// Performance metrics for generation
282    pub performance_metrics: GenerationPerformanceMetrics,
283}
284
285/// Coverage information for tracking generated value ranges and distributions
286#[derive(Debug, Default)]
287pub struct CoverageInfo {
288    /// Coverage for numeric types (min, max, count per range)
289    pub numeric_coverage: HashMap<String, NumericCoverage>,
290    /// Coverage for string types (length distribution, character sets)
291    pub string_coverage: HashMap<String, StringCoverage>,
292    /// Coverage for collection types (size distribution)
293    pub collection_coverage: HashMap<String, CollectionCoverage>,
294    /// Coverage for boolean values
295    pub boolean_coverage: HashMap<String, BooleanCoverage>,
296    /// Coverage for enum variants
297    pub enum_coverage: HashMap<String, EnumCoverage>,
298    /// Custom coverage for user-defined types
299    pub custom_coverage: HashMap<String, Box<dyn CustomCoverage + Send + Sync>>,
300}
301
302/// Coverage tracking for numeric types
303#[derive(Debug, Clone)]
304pub struct NumericCoverage {
305    /// Minimum value generated
306    pub min_value: f64,
307    /// Maximum value generated
308    pub max_value: f64,
309    /// Total count of values generated
310    pub total_count: usize,
311    /// Distribution across ranges (range_start -> count)
312    pub range_distribution: HashMap<String, usize>,
313    /// Statistical moments (mean, variance, etc.)
314    pub statistics: NumericStatistics,
315}
316
317/// Statistical information for numeric values
318#[derive(Debug, Clone)]
319pub struct NumericStatistics {
320    /// Mean of generated values
321    pub mean: f64,
322    /// Variance of generated values
323    pub variance: f64,
324    /// Standard deviation
325    pub std_dev: f64,
326    /// Skewness (measure of asymmetry)
327    pub skewness: f64,
328    /// Kurtosis (measure of tail heaviness)
329    pub kurtosis: f64,
330}
331
332/// Coverage tracking for string types
333#[derive(Debug, Clone)]
334pub struct StringCoverage {
335    /// Length distribution (length -> count)
336    pub length_distribution: HashMap<usize, usize>,
337    /// Character set coverage (character -> count)
338    pub character_distribution: HashMap<char, usize>,
339    /// Pattern coverage (regex patterns matched)
340    pub pattern_coverage: HashMap<String, usize>,
341    /// Total strings generated
342    pub total_count: usize,
343    /// Average string length
344    pub average_length: f64,
345}
346
347/// Coverage tracking for collection types
348#[derive(Debug, Clone)]
349pub struct CollectionCoverage {
350    /// Size distribution (size -> count)
351    pub size_distribution: HashMap<usize, usize>,
352    /// Total collections generated
353    pub total_count: usize,
354    /// Average collection size
355    pub average_size: f64,
356    /// Maximum size generated
357    pub max_size: usize,
358    /// Minimum size generated
359    pub min_size: usize,
360}
361
362/// Coverage tracking for boolean values
363#[derive(Debug, Clone)]
364pub struct BooleanCoverage {
365    /// Count of true values
366    pub true_count: usize,
367    /// Count of false values
368    pub false_count: usize,
369    /// Total boolean values generated
370    pub total_count: usize,
371    /// Ratio of true to total
372    pub true_ratio: f64,
373}
374
375/// Coverage tracking for enum variants
376#[derive(Debug, Clone)]
377pub struct EnumCoverage {
378    /// Variant distribution (variant_name -> count)
379    pub variant_distribution: HashMap<String, usize>,
380    /// Total enum values generated
381    pub total_count: usize,
382    /// Coverage percentage (variants_covered / total_variants)
383    pub coverage_percentage: f64,
384}
385
386/// Trait for custom coverage tracking
387pub trait CustomCoverage: std::fmt::Debug {
388    /// Record a generated value
389    fn record_value(&mut self, value: &dyn std::any::Any);
390    /// Get coverage summary
391    fn get_summary(&self) -> String;
392    /// Get coverage percentage (0.0 to 1.0)
393    fn get_coverage_percentage(&self) -> f64;
394}
395
396/// Performance metrics for generation process
397#[derive(Debug, Clone)]
398pub struct GenerationPerformanceMetrics {
399    /// Total time spent generating values
400    pub total_generation_time: std::time::Duration,
401    /// Average time per generation
402    pub average_generation_time: std::time::Duration,
403    /// Fastest generation time
404    pub fastest_generation: std::time::Duration,
405    /// Slowest generation time
406    pub slowest_generation: std::time::Duration,
407    /// Memory usage statistics
408    pub memory_stats: MemoryStats,
409}
410
411/// Memory usage statistics
412#[derive(Debug, Clone, Default)]
413pub struct MemoryStats {
414    /// Peak memory usage during generation
415    pub peak_memory_usage: usize,
416    /// Average memory usage
417    pub average_memory_usage: usize,
418    /// Total allocations
419    pub total_allocations: usize,
420}
421
422impl Clone for CoverageInfo {
423    fn clone(&self) -> Self {
424        Self {
425            numeric_coverage: self.numeric_coverage.clone(),
426            string_coverage: self.string_coverage.clone(),
427            collection_coverage: self.collection_coverage.clone(),
428            boolean_coverage: self.boolean_coverage.clone(),
429            enum_coverage: self.enum_coverage.clone(),
430            // We can't clone custom coverage, so we create a new empty HashMap
431            custom_coverage: HashMap::new(),
432        }
433    }
434}
435
436impl Default for GenerationPerformanceMetrics {
437    fn default() -> Self {
438        Self {
439            total_generation_time: std::time::Duration::from_secs(0),
440            average_generation_time: std::time::Duration::from_secs(0),
441            fastest_generation: std::time::Duration::from_secs(u64::MAX),
442            slowest_generation: std::time::Duration::from_secs(0),
443            memory_stats: MemoryStats::default(),
444        }
445    }
446}
447
448impl Default for NumericCoverage {
449    fn default() -> Self {
450        Self::new()
451    }
452}
453
454impl NumericCoverage {
455    /// Create a new numeric coverage tracker
456    pub fn new() -> Self {
457        Self {
458            min_value: f64::INFINITY,
459            max_value: f64::NEG_INFINITY,
460            total_count: 0,
461            range_distribution: HashMap::new(),
462            statistics: NumericStatistics::new(),
463        }
464    }
465
466    /// Record a numeric value
467    pub fn record_value(&mut self, value: f64) {
468        self.min_value = self.min_value.min(value);
469        self.max_value = self.max_value.max(value);
470        self.total_count += 1;
471
472        // Update range distribution (using simple bucketing)
473        let bucket = self.get_bucket_for_value(value);
474        *self.range_distribution.entry(bucket).or_insert(0) += 1;
475
476        // Update statistics
477        self.statistics.update(value, self.total_count);
478    }
479
480    /// Get bucket name for a value (simple implementation)
481    fn get_bucket_for_value(&self, value: f64) -> String {
482        let bucket_size = 10.0;
483        let bucket_index = (value / bucket_size).floor() as i64;
484        format!(
485            "[{}, {})",
486            bucket_index as f64 * bucket_size,
487            (bucket_index + 1) as f64 * bucket_size
488        )
489    }
490
491    /// Get coverage percentage for a specific range
492    pub fn get_range_coverage(&self, min: f64, max: f64) -> f64 {
493        if self.total_count == 0 {
494            return 0.0;
495        }
496
497        let values_in_range = self
498            .range_distribution
499            .iter()
500            .filter(|(range, _)| {
501                // Simple range parsing - in practice, this would be more robust
502                range.contains(&min.to_string()) || range.contains(&max.to_string())
503            })
504            .map(|(_, count)| *count)
505            .sum::<usize>();
506
507        values_in_range as f64 / self.total_count as f64
508    }
509}
510
511impl Default for NumericStatistics {
512    fn default() -> Self {
513        Self::new()
514    }
515}
516
517impl NumericStatistics {
518    /// Create new numeric statistics
519    pub fn new() -> Self {
520        Self {
521            mean: 0.0,
522            variance: 0.0,
523            std_dev: 0.0,
524            skewness: 0.0,
525            kurtosis: 0.0,
526        }
527    }
528
529    /// Update statistics with a new value (online algorithm)
530    pub fn update(&mut self, value: f64, count: usize) {
531        if count == 1 {
532            self.mean = value;
533            self.variance = 0.0;
534            self.std_dev = 0.0;
535            return;
536        }
537
538        // Online mean and variance calculation (Welford's algorithm)
539        let delta = value - self.mean;
540        self.mean += delta / count as f64;
541        let delta2 = value - self.mean;
542        self.variance += delta * delta2;
543
544        if count > 1 {
545            self.variance /= (count - 1) as f64;
546            self.std_dev = self.variance.sqrt();
547        }
548
549        // Simplified skewness and kurtosis calculation
550        // In practice, these would use more sophisticated online algorithms
551        self.skewness = 0.0; // Placeholder
552        self.kurtosis = 0.0; // Placeholder
553    }
554}
555
556impl Default for StringCoverage {
557    fn default() -> Self {
558        Self::new()
559    }
560}
561
562impl StringCoverage {
563    /// Create a new string coverage tracker
564    pub fn new() -> Self {
565        Self {
566            length_distribution: HashMap::new(),
567            character_distribution: HashMap::new(),
568            pattern_coverage: HashMap::new(),
569            total_count: 0,
570            average_length: 0.0,
571        }
572    }
573
574    /// Record a string value
575    pub fn record_value(&mut self, value: &str) {
576        self.total_count += 1;
577
578        // Update length distribution
579        *self.length_distribution.entry(value.len()).or_insert(0) += 1;
580
581        // Update character distribution
582        for ch in value.chars() {
583            *self.character_distribution.entry(ch).or_insert(0) += 1;
584        }
585
586        // Update average length
587        self.average_length = (self.average_length * (self.total_count - 1) as f64
588            + value.len() as f64)
589            / self.total_count as f64;
590
591        // Check for common patterns
592        self.check_patterns(value);
593    }
594
595    /// Check for common string patterns
596    fn check_patterns(&mut self, value: &str) {
597        if value.chars().all(|c| c.is_ascii_alphabetic()) {
598            *self
599                .pattern_coverage
600                .entry("alphabetic".to_string())
601                .or_insert(0) += 1;
602        }
603        if value.chars().all(|c| c.is_ascii_digit()) {
604            *self
605                .pattern_coverage
606                .entry("numeric".to_string())
607                .or_insert(0) += 1;
608        }
609        if value.chars().all(|c| c.is_ascii_alphanumeric()) {
610            *self
611                .pattern_coverage
612                .entry("alphanumeric".to_string())
613                .or_insert(0) += 1;
614        }
615        if value.contains(' ') {
616            *self
617                .pattern_coverage
618                .entry("contains_space".to_string())
619                .or_insert(0) += 1;
620        }
621    }
622
623    /// Get character set coverage percentage
624    pub fn get_character_set_coverage(&self, expected_chars: &[char]) -> f64 {
625        if expected_chars.is_empty() {
626            return 1.0;
627        }
628
629        let covered_chars = expected_chars
630            .iter()
631            .filter(|ch| self.character_distribution.contains_key(ch))
632            .count();
633
634        covered_chars as f64 / expected_chars.len() as f64
635    }
636}
637
638impl Default for CollectionCoverage {
639    fn default() -> Self {
640        Self::new()
641    }
642}
643
644impl CollectionCoverage {
645    /// Create a new collection coverage tracker
646    pub fn new() -> Self {
647        Self {
648            size_distribution: HashMap::new(),
649            total_count: 0,
650            average_size: 0.0,
651            max_size: 0,
652            min_size: usize::MAX,
653        }
654    }
655
656    /// Record a collection size
657    pub fn record_size(&mut self, size: usize) {
658        self.total_count += 1;
659
660        // Update size distribution
661        *self.size_distribution.entry(size).or_insert(0) += 1;
662
663        // Update min/max
664        self.min_size = self.min_size.min(size);
665        self.max_size = self.max_size.max(size);
666
667        // Update average size
668        self.average_size = (self.average_size * (self.total_count - 1) as f64 + size as f64)
669            / self.total_count as f64;
670    }
671
672    /// Get size range coverage
673    pub fn get_size_range_coverage(&self, min_size: usize, max_size: usize) -> f64 {
674        if self.total_count == 0 {
675            return 0.0;
676        }
677
678        let values_in_range = self
679            .size_distribution
680            .iter()
681            .filter(|(size, _)| **size >= min_size && **size <= max_size)
682            .map(|(_, count)| *count)
683            .sum::<usize>();
684
685        values_in_range as f64 / self.total_count as f64
686    }
687}
688
689impl Default for BooleanCoverage {
690    fn default() -> Self {
691        Self::new()
692    }
693}
694
695impl BooleanCoverage {
696    /// Create a new boolean coverage tracker
697    pub fn new() -> Self {
698        Self {
699            true_count: 0,
700            false_count: 0,
701            total_count: 0,
702            true_ratio: 0.0,
703        }
704    }
705
706    /// Record a boolean value
707    pub fn record_value(&mut self, value: bool) {
708        self.total_count += 1;
709
710        if value {
711            self.true_count += 1;
712        } else {
713            self.false_count += 1;
714        }
715
716        self.true_ratio = self.true_count as f64 / self.total_count as f64;
717    }
718
719    /// Check if both true and false values have been generated
720    pub fn has_full_coverage(&self) -> bool {
721        self.true_count > 0 && self.false_count > 0
722    }
723}
724
725impl EnumCoverage {
726    /// Create a new enum coverage tracker
727    pub fn new(_total_variants: usize) -> Self {
728        Self {
729            variant_distribution: HashMap::new(),
730            total_count: 0,
731            coverage_percentage: 0.0,
732        }
733    }
734
735    /// Record an enum variant
736    pub fn record_variant(&mut self, variant_name: &str, total_variants: usize) {
737        self.total_count += 1;
738        *self
739            .variant_distribution
740            .entry(variant_name.to_string())
741            .or_insert(0) += 1;
742
743        // Update coverage percentage
744        let covered_variants = self.variant_distribution.len();
745        self.coverage_percentage = covered_variants as f64 / total_variants as f64;
746    }
747
748    /// Get the least covered variant
749    pub fn get_least_covered_variant(&self) -> Option<(&String, &usize)> {
750        self.variant_distribution
751            .iter()
752            .min_by_key(|(_, count)| *count)
753    }
754
755    /// Get the most covered variant
756    pub fn get_most_covered_variant(&self) -> Option<(&String, &usize)> {
757        self.variant_distribution
758            .iter()
759            .max_by_key(|(_, count)| *count)
760    }
761}
762
763impl GenerationStats {
764    /// Create a comprehensive report of generation statistics
765    pub fn generate_report(&self) -> String {
766        let mut report = String::new();
767
768        report.push_str("═══════════════════════════════════════════════════════════════\n");
769        report.push_str("                    GENERATION STATISTICS                     \n");
770        report.push_str("═══════════════════════════════════════════════════════════════\n\n");
771
772        // Basic statistics
773        report.push_str("📊 BASIC STATISTICS:\n");
774        report.push_str(&format!(
775            "   Total values generated: {}\n",
776            self.total_generated
777        ));
778        report.push_str(&format!(
779            "   Generation time: {:?}\n",
780            self.performance_metrics.total_generation_time
781        ));
782        report.push_str(&format!(
783            "   Average time per value: {:?}\n",
784            self.performance_metrics.average_generation_time
785        ));
786        report.push('\n');
787
788        // Coverage information
789        report.push_str("📈 COVERAGE INFORMATION:\n");
790
791        // Numeric coverage
792        if !self.coverage_info.numeric_coverage.is_empty() {
793            report.push_str("   Numeric Types:\n");
794            for (type_name, coverage) in &self.coverage_info.numeric_coverage {
795                report.push_str(&format!(
796                    "     {}: {} values, range [{:.2}, {:.2}], mean: {:.2}\n",
797                    type_name,
798                    coverage.total_count,
799                    coverage.min_value,
800                    coverage.max_value,
801                    coverage.statistics.mean
802                ));
803            }
804        }
805
806        // String coverage
807        if !self.coverage_info.string_coverage.is_empty() {
808            report.push_str("   String Types:\n");
809            for (type_name, coverage) in &self.coverage_info.string_coverage {
810                report.push_str(&format!(
811                    "     {}: {} strings, avg length: {:.1}, {} unique chars\n",
812                    type_name,
813                    coverage.total_count,
814                    coverage.average_length,
815                    coverage.character_distribution.len()
816                ));
817            }
818        }
819
820        // Collection coverage
821        if !self.coverage_info.collection_coverage.is_empty() {
822            report.push_str("   Collection Types:\n");
823            for (type_name, coverage) in &self.coverage_info.collection_coverage {
824                report.push_str(&format!(
825                    "     {}: {} collections, avg size: {:.1}, range [{}, {}]\n",
826                    type_name,
827                    coverage.total_count,
828                    coverage.average_size,
829                    coverage.min_size,
830                    coverage.max_size
831                ));
832            }
833        }
834
835        // Boolean coverage
836        if !self.coverage_info.boolean_coverage.is_empty() {
837            report.push_str("   Boolean Types:\n");
838            for (type_name, coverage) in &self.coverage_info.boolean_coverage {
839                let coverage_status = if coverage.has_full_coverage() {
840                    "✓"
841                } else {
842                    "✗"
843                };
844                report.push_str(&format!(
845                    "     {}: {} values, true ratio: {:.2} {}\n",
846                    type_name, coverage.total_count, coverage.true_ratio, coverage_status
847                ));
848            }
849        }
850
851        // Enum coverage
852        if !self.coverage_info.enum_coverage.is_empty() {
853            report.push_str("   Enum Types:\n");
854            for (type_name, coverage) in &self.coverage_info.enum_coverage {
855                report.push_str(&format!(
856                    "     {}: {} values, {:.1}% variant coverage\n",
857                    type_name,
858                    coverage.total_count,
859                    coverage.coverage_percentage * 100.0
860                ));
861            }
862        }
863
864        report.push('\n');
865
866        // Performance metrics
867        report.push_str("⚡ PERFORMANCE METRICS:\n");
868        report.push_str(&format!(
869            "   Fastest generation: {:?}\n",
870            self.performance_metrics.fastest_generation
871        ));
872        report.push_str(&format!(
873            "   Slowest generation: {:?}\n",
874            self.performance_metrics.slowest_generation
875        ));
876        report.push_str(&format!(
877            "   Peak memory usage: {} bytes\n",
878            self.performance_metrics.memory_stats.peak_memory_usage
879        ));
880        report.push('\n');
881
882        report.push_str("═══════════════════════════════════════════════════════════════\n");
883
884        report
885    }
886
887    /// Get a concise summary of the statistics
888    pub fn get_summary(&self) -> String {
889        format!(
890            "Generated {} values in {:?} (avg: {:?}/value)",
891            self.total_generated,
892            self.performance_metrics.total_generation_time,
893            self.performance_metrics.average_generation_time
894        )
895    }
896
897    /// Check if coverage meets specified thresholds
898    pub fn check_coverage_thresholds(&self, thresholds: &CoverageThresholds) -> CoverageReport {
899        let mut report = CoverageReport::new();
900
901        // Check numeric coverage
902        for (type_name, coverage) in &self.coverage_info.numeric_coverage {
903            if let Some(threshold) = thresholds.numeric_thresholds.get(type_name) {
904                let range_coverage =
905                    coverage.get_range_coverage(threshold.min_value, threshold.max_value);
906                report.add_numeric_result(
907                    type_name.clone(),
908                    range_coverage >= threshold.min_coverage,
909                );
910            }
911        }
912
913        // Check boolean coverage
914        for (type_name, coverage) in &self.coverage_info.boolean_coverage {
915            if thresholds.require_full_boolean_coverage {
916                report.add_boolean_result(type_name.clone(), coverage.has_full_coverage());
917            }
918        }
919
920        // Check enum coverage
921        for (type_name, coverage) in &self.coverage_info.enum_coverage {
922            if let Some(threshold) = thresholds.enum_thresholds.get(type_name) {
923                report.add_enum_result(
924                    type_name.clone(),
925                    coverage.coverage_percentage >= threshold.min_coverage,
926                );
927            }
928        }
929
930        report
931    }
932}
933
934/// Thresholds for coverage checking
935#[derive(Debug, Clone)]
936pub struct CoverageThresholds {
937    /// Thresholds for numeric types
938    pub numeric_thresholds: HashMap<String, NumericThreshold>,
939    /// Whether to require full boolean coverage (both true and false)
940    pub require_full_boolean_coverage: bool,
941    /// Thresholds for enum types
942    pub enum_thresholds: HashMap<String, EnumThreshold>,
943}
944
945/// Threshold for numeric type coverage
946#[derive(Debug, Clone)]
947pub struct NumericThreshold {
948    /// Minimum value that should be covered
949    pub min_value: f64,
950    /// Maximum value that should be covered
951    pub max_value: f64,
952    /// Minimum coverage percentage required
953    pub min_coverage: f64,
954}
955
956/// Threshold for enum type coverage
957#[derive(Debug, Clone)]
958pub struct EnumThreshold {
959    /// Minimum coverage percentage required
960    pub min_coverage: f64,
961}
962
963/// Report of coverage threshold checking
964#[derive(Debug, Clone)]
965pub struct CoverageReport {
966    /// Results for numeric types
967    pub numeric_results: HashMap<String, bool>,
968    /// Results for boolean types
969    pub boolean_results: HashMap<String, bool>,
970    /// Results for enum types
971    pub enum_results: HashMap<String, bool>,
972    /// Overall pass/fail status
973    pub overall_pass: bool,
974}
975
976impl Default for CoverageReport {
977    fn default() -> Self {
978        Self::new()
979    }
980}
981
982impl CoverageReport {
983    /// Create a new coverage report
984    pub fn new() -> Self {
985        Self {
986            numeric_results: HashMap::new(),
987            boolean_results: HashMap::new(),
988            enum_results: HashMap::new(),
989            overall_pass: true,
990        }
991    }
992
993    /// Add a numeric coverage result
994    pub fn add_numeric_result(&mut self, type_name: String, passed: bool) {
995        self.numeric_results.insert(type_name, passed);
996        if !passed {
997            self.overall_pass = false;
998        }
999    }
1000
1001    /// Add a boolean coverage result
1002    pub fn add_boolean_result(&mut self, type_name: String, passed: bool) {
1003        self.boolean_results.insert(type_name, passed);
1004        if !passed {
1005            self.overall_pass = false;
1006        }
1007    }
1008
1009    /// Add an enum coverage result
1010    pub fn add_enum_result(&mut self, type_name: String, passed: bool) {
1011        self.enum_results.insert(type_name, passed);
1012        if !passed {
1013            self.overall_pass = false;
1014        }
1015    }
1016
1017    /// Generate a report string
1018    pub fn generate_report(&self) -> String {
1019        let mut report = String::new();
1020
1021        report.push_str("Coverage Threshold Report:\n");
1022        report.push_str(&format!(
1023            "Overall Status: {}\n",
1024            if self.overall_pass { "PASS" } else { "FAIL" }
1025        ));
1026
1027        if !self.numeric_results.is_empty() {
1028            report.push_str("\nNumeric Coverage:\n");
1029            for (type_name, passed) in &self.numeric_results {
1030                let status = if *passed { "✓" } else { "✗" };
1031                report.push_str(&format!("  {} {}\n", status, type_name));
1032            }
1033        }
1034
1035        if !self.boolean_results.is_empty() {
1036            report.push_str("\nBoolean Coverage:\n");
1037            for (type_name, passed) in &self.boolean_results {
1038                let status = if *passed { "✓" } else { "✗" };
1039                report.push_str(&format!("  {} {}\n", status, type_name));
1040            }
1041        }
1042
1043        if !self.enum_results.is_empty() {
1044            report.push_str("\nEnum Coverage:\n");
1045            for (type_name, passed) in &self.enum_results {
1046                let status = if *passed { "✓" } else { "✗" };
1047                report.push_str(&format!("  {} {}\n", status, type_name));
1048            }
1049        }
1050
1051        report
1052    }
1053}
1054
1055/// Global configuration manager for hierarchical configuration
1056pub struct ConfigManager {
1057    global_config: GlobalConfig,
1058}
1059
1060impl ConfigManager {
1061    /// Create a new configuration manager with default global configuration
1062    pub fn new() -> Self {
1063        Self {
1064            global_config: GlobalConfig::default(),
1065        }
1066    }
1067
1068    /// Create a new configuration manager with custom global configuration
1069    pub fn with_global_config(global_config: GlobalConfig) -> Result<Self, ConfigError> {
1070        global_config.validate()?;
1071        Ok(Self { global_config })
1072    }
1073
1074    /// Get the current global configuration
1075    pub fn global_config(&self) -> &GlobalConfig {
1076        &self.global_config
1077    }
1078
1079    /// Update the global configuration
1080    pub fn set_global_config(&mut self, global_config: GlobalConfig) -> Result<(), ConfigError> {
1081        global_config.validate()?;
1082        self.global_config = global_config;
1083        Ok(())
1084    }
1085
1086    /// Create a test configuration that inherits from global defaults
1087    pub fn create_test_config(&self) -> TestConfig {
1088        TestConfig::from_global_with_overrides(&self.global_config, None, None, None)
1089            .unwrap_or_else(|_| TestConfig::default())
1090    }
1091
1092    /// Create a test configuration with specific overrides
1093    pub fn create_test_config_with_overrides(
1094        &self,
1095        iterations: Option<usize>,
1096        seed: Option<u64>,
1097        generator_overrides: Option<GeneratorConfig>,
1098    ) -> Result<TestConfig, ConfigError> {
1099        TestConfig::from_global_with_overrides(
1100            &self.global_config,
1101            iterations,
1102            seed,
1103            generator_overrides,
1104        )
1105    }
1106}
1107
1108impl Default for ConfigManager {
1109    fn default() -> Self {
1110        Self::new()
1111    }
1112}
1113
1114// Thread-local global configuration manager (doc comment not allowed on thread_local!)
1115thread_local! {
1116    static CONFIG_MANAGER: std::cell::RefCell<ConfigManager> = std::cell::RefCell::new(ConfigManager::new());
1117}
1118
1119/// Get the current global configuration
1120pub fn get_global_config() -> GlobalConfig {
1121    CONFIG_MANAGER.with(|manager| manager.borrow().global_config().clone())
1122}
1123
1124/// Set the global configuration
1125pub fn set_global_config(config: GlobalConfig) -> Result<(), ConfigError> {
1126    config.validate()?;
1127    CONFIG_MANAGER.with(|manager| manager.borrow_mut().set_global_config(config))
1128}
1129
1130/// Create a test configuration that inherits from global defaults
1131pub fn create_test_config() -> TestConfig {
1132    CONFIG_MANAGER.with(|manager| manager.borrow().create_test_config())
1133}
1134
1135/// Create a test configuration with specific overrides
1136pub fn create_test_config_with_overrides(
1137    iterations: Option<usize>,
1138    seed: Option<u64>,
1139    generator_overrides: Option<GeneratorConfig>,
1140) -> Result<TestConfig, ConfigError> {
1141    CONFIG_MANAGER.with(|manager| {
1142        manager
1143            .borrow()
1144            .create_test_config_with_overrides(iterations, seed, generator_overrides)
1145    })
1146}
1147#[cfg(test)]
1148mod tests {
1149    use super::*;
1150
1151    #[test]
1152    fn test_generator_config_validation() {
1153        // Valid configuration
1154        let config = GeneratorConfig::new(10, 5, HashMap::new());
1155        assert!(config.is_ok());
1156
1157        // Invalid max_depth
1158        let config = GeneratorConfig::new(10, 0, HashMap::new());
1159        assert!(matches!(config, Err(ConfigError::InvalidMaxDepth(0))));
1160
1161        // Test validate method
1162        let mut config = GeneratorConfig::default();
1163        assert!(config.validate().is_ok());
1164
1165        config.max_depth = 0;
1166        assert!(matches!(
1167            config.validate(),
1168            Err(ConfigError::InvalidMaxDepth(0))
1169        ));
1170    }
1171
1172    #[test]
1173    fn test_test_config_validation() {
1174        // Valid configuration
1175        let config = TestConfig::new(
1176            100,
1177            1000,
1178            Duration::from_secs(10),
1179            None,
1180            GeneratorConfig::default(),
1181        );
1182        assert!(config.is_ok());
1183
1184        // Invalid iterations
1185        let config = TestConfig::new(
1186            0,
1187            1000,
1188            Duration::from_secs(10),
1189            None,
1190            GeneratorConfig::default(),
1191        );
1192        assert!(matches!(config, Err(ConfigError::InvalidIterations(0))));
1193
1194        // Invalid shrink iterations
1195        let config = TestConfig::new(
1196            100,
1197            0,
1198            Duration::from_secs(10),
1199            None,
1200            GeneratorConfig::default(),
1201        );
1202        assert!(matches!(
1203            config,
1204            Err(ConfigError::InvalidShrinkIterations(0))
1205        ));
1206
1207        // Invalid timeout
1208        let config = TestConfig::new(
1209            100,
1210            1000,
1211            Duration::from_secs(0),
1212            None,
1213            GeneratorConfig::default(),
1214        );
1215        assert!(matches!(config, Err(ConfigError::InvalidTimeout)));
1216    }
1217
1218    #[test]
1219    fn test_global_config_validation() {
1220        // Valid configuration
1221        let config = GlobalConfig::new(100, None, GeneratorConfig::default());
1222        assert!(config.is_ok());
1223
1224        // Invalid iterations
1225        let config = GlobalConfig::new(0, None, GeneratorConfig::default());
1226        assert!(matches!(config, Err(ConfigError::InvalidIterations(0))));
1227    }
1228
1229    #[test]
1230    fn test_config_merge_precedence() {
1231        let global = GlobalConfig {
1232            default_iterations: 50,
1233            default_seed: Some(123),
1234            generator_config: GeneratorConfig {
1235                size_hint: 20,
1236                max_depth: 3,
1237                custom_ranges: HashMap::new(),
1238            },
1239        };
1240
1241        let test_config = TestConfig {
1242            iterations: 200,
1243            max_shrink_iterations: 500,
1244            shrink_timeout: Duration::from_secs(5),
1245            seed: Some(456),
1246            generator_config: GeneratorConfig {
1247                size_hint: 15,
1248                max_depth: 7,
1249                custom_ranges: HashMap::new(),
1250            },
1251        };
1252
1253        let merged = test_config.merge_with_global(&global);
1254
1255        // Test config values should take precedence
1256        assert_eq!(merged.iterations, 200);
1257        assert_eq!(merged.seed, Some(456));
1258        assert_eq!(merged.generator_config.size_hint, 15);
1259        assert_eq!(merged.generator_config.max_depth, 7);
1260    }
1261
1262    #[test]
1263    fn test_config_merge_with_defaults() {
1264        let global = GlobalConfig {
1265            default_iterations: 50,
1266            default_seed: Some(123),
1267            generator_config: GeneratorConfig {
1268                size_hint: 20,
1269                max_depth: 3,
1270                custom_ranges: HashMap::new(),
1271            },
1272        };
1273
1274        let test_config = TestConfig {
1275            iterations: 200,
1276            max_shrink_iterations: 500,
1277            shrink_timeout: Duration::from_secs(5),
1278            seed: None,                                   // Should inherit from global
1279            generator_config: GeneratorConfig::default(), // Should merge with global
1280        };
1281
1282        let merged = test_config.merge_with_global(&global);
1283
1284        // Should inherit seed from global
1285        assert_eq!(merged.seed, Some(123));
1286        // Should inherit generator config from global since test config uses defaults
1287        assert_eq!(merged.generator_config.size_hint, 20);
1288        assert_eq!(merged.generator_config.max_depth, 3);
1289    }
1290
1291    #[test]
1292    fn test_from_global_with_overrides() {
1293        let global = GlobalConfig {
1294            default_iterations: 50,
1295            default_seed: Some(123),
1296            generator_config: GeneratorConfig {
1297                size_hint: 20,
1298                max_depth: 3,
1299                custom_ranges: HashMap::new(),
1300            },
1301        };
1302
1303        // Test with no overrides
1304        let config = TestConfig::from_global_with_overrides(&global, None, None, None).unwrap();
1305        assert_eq!(config.iterations, 50);
1306        assert_eq!(config.seed, Some(123));
1307
1308        // Test with iterations override
1309        let config =
1310            TestConfig::from_global_with_overrides(&global, Some(100), None, None).unwrap();
1311        assert_eq!(config.iterations, 100);
1312        assert_eq!(config.seed, Some(123));
1313
1314        // Test with seed override
1315        let config =
1316            TestConfig::from_global_with_overrides(&global, None, Some(456), None).unwrap();
1317        assert_eq!(config.iterations, 50);
1318        assert_eq!(config.seed, Some(456));
1319    }
1320
1321    #[test]
1322    fn test_config_manager() {
1323        let mut manager = ConfigManager::new();
1324
1325        // Test default global config
1326        let global = manager.global_config();
1327        assert_eq!(global.default_iterations, 100);
1328
1329        // Test creating test config from global
1330        let test_config = manager.create_test_config();
1331        assert_eq!(test_config.iterations, 100);
1332
1333        // Test updating global config
1334        let new_global = GlobalConfig {
1335            default_iterations: 200,
1336            default_seed: Some(789),
1337            generator_config: GeneratorConfig::default(),
1338        };
1339        manager.set_global_config(new_global).unwrap();
1340
1341        let updated_test_config = manager.create_test_config();
1342        assert_eq!(updated_test_config.iterations, 200);
1343        assert_eq!(updated_test_config.seed, Some(789));
1344    }
1345
1346    #[test]
1347    fn test_config_manager_with_overrides() {
1348        let manager = ConfigManager::new();
1349
1350        // Test creating test config with overrides
1351        let config = manager
1352            .create_test_config_with_overrides(Some(300), Some(999), None)
1353            .unwrap();
1354        assert_eq!(config.iterations, 300);
1355        assert_eq!(config.seed, Some(999));
1356    }
1357
1358    #[test]
1359    fn test_thread_local_config_functions() {
1360        // Test getting default global config
1361        let global = get_global_config();
1362        assert_eq!(global.default_iterations, 100);
1363
1364        // Test setting global config
1365        let new_global = GlobalConfig {
1366            default_iterations: 150,
1367            default_seed: Some(555),
1368            generator_config: GeneratorConfig::default(),
1369        };
1370        set_global_config(new_global).unwrap();
1371
1372        let updated_global = get_global_config();
1373        assert_eq!(updated_global.default_iterations, 150);
1374        assert_eq!(updated_global.default_seed, Some(555));
1375
1376        // Test creating test config
1377        let test_config = create_test_config();
1378        assert_eq!(test_config.iterations, 150);
1379        assert_eq!(test_config.seed, Some(555));
1380
1381        // Test creating test config with overrides
1382        let config = create_test_config_with_overrides(Some(250), None, None).unwrap();
1383        assert_eq!(config.iterations, 250);
1384        assert_eq!(config.seed, Some(555)); // Should inherit from global
1385    }
1386
1387    #[test]
1388    fn test_config_error_display() {
1389        let error = ConfigError::InvalidIterations(0);
1390        assert_eq!(
1391            format!("{}", error),
1392            "Invalid iterations count: 0 (must be > 0)"
1393        );
1394
1395        let error = ConfigError::InvalidShrinkIterations(0);
1396        assert_eq!(
1397            format!("{}", error),
1398            "Invalid shrink iterations count: 0 (must be > 0)"
1399        );
1400
1401        let error = ConfigError::InvalidTimeout;
1402        assert_eq!(format!("{}", error), "Invalid timeout (must be > 0)");
1403
1404        let error = ConfigError::InvalidMaxDepth(0);
1405        assert_eq!(format!("{}", error), "Invalid max depth: 0 (must be > 0)");
1406    }
1407
1408    #[test]
1409    fn test_generator_config_merge() {
1410        let base = GeneratorConfig {
1411            size_hint: 20,
1412            max_depth: 3,
1413            custom_ranges: HashMap::new(),
1414        };
1415
1416        // Test merging with default values (should use base values)
1417        let default_config = GeneratorConfig::default();
1418        let merged = default_config.merge_with(&base);
1419        assert_eq!(merged.size_hint, 20); // From base
1420        assert_eq!(merged.max_depth, 3); // From base
1421
1422        // Test merging with non-default values (should use override values)
1423        let override_config = GeneratorConfig {
1424            size_hint: 15,
1425            max_depth: 7,
1426            custom_ranges: HashMap::new(),
1427        };
1428        let merged = override_config.merge_with(&base);
1429        assert_eq!(merged.size_hint, 15); // From override
1430        assert_eq!(merged.max_depth, 7); // From override
1431    }
1432}