1use std::any::{Any, TypeId};
4use std::collections::HashMap;
5use std::time::Duration;
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum ConfigError {
10 InvalidIterations(usize),
12 InvalidShrinkIterations(usize),
14 InvalidTimeout,
16 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#[derive(Debug)]
43pub struct GeneratorConfig {
44 pub size_hint: usize,
46 pub max_depth: usize,
48 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 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 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 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 pub fn merge_with(self, _other: &GeneratorConfig) -> Self {
102 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, }
117 }
118}
119
120#[derive(Debug, Clone)]
122pub struct TestConfig {
123 pub iterations: usize,
125 pub max_shrink_iterations: usize,
127 pub shrink_timeout: Duration,
129 pub seed: Option<u64>,
131 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 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 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 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 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, shrink_timeout: Duration::from_secs(10), 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#[derive(Debug, Clone)]
225pub struct GlobalConfig {
226 pub default_iterations: usize,
228 pub default_seed: Option<u64>,
230 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 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 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#[derive(Debug, Default)]
274pub struct GenerationStats {
275 pub total_generated: usize,
277 pub distribution_info: HashMap<String, Box<dyn Any + Send + Sync>>,
279 pub coverage_info: CoverageInfo,
281 pub performance_metrics: GenerationPerformanceMetrics,
283}
284
285#[derive(Debug, Default)]
287pub struct CoverageInfo {
288 pub numeric_coverage: HashMap<String, NumericCoverage>,
290 pub string_coverage: HashMap<String, StringCoverage>,
292 pub collection_coverage: HashMap<String, CollectionCoverage>,
294 pub boolean_coverage: HashMap<String, BooleanCoverage>,
296 pub enum_coverage: HashMap<String, EnumCoverage>,
298 pub custom_coverage: HashMap<String, Box<dyn CustomCoverage + Send + Sync>>,
300}
301
302#[derive(Debug, Clone)]
304pub struct NumericCoverage {
305 pub min_value: f64,
307 pub max_value: f64,
309 pub total_count: usize,
311 pub range_distribution: HashMap<String, usize>,
313 pub statistics: NumericStatistics,
315}
316
317#[derive(Debug, Clone)]
319pub struct NumericStatistics {
320 pub mean: f64,
322 pub variance: f64,
324 pub std_dev: f64,
326 pub skewness: f64,
328 pub kurtosis: f64,
330}
331
332#[derive(Debug, Clone)]
334pub struct StringCoverage {
335 pub length_distribution: HashMap<usize, usize>,
337 pub character_distribution: HashMap<char, usize>,
339 pub pattern_coverage: HashMap<String, usize>,
341 pub total_count: usize,
343 pub average_length: f64,
345}
346
347#[derive(Debug, Clone)]
349pub struct CollectionCoverage {
350 pub size_distribution: HashMap<usize, usize>,
352 pub total_count: usize,
354 pub average_size: f64,
356 pub max_size: usize,
358 pub min_size: usize,
360}
361
362#[derive(Debug, Clone)]
364pub struct BooleanCoverage {
365 pub true_count: usize,
367 pub false_count: usize,
369 pub total_count: usize,
371 pub true_ratio: f64,
373}
374
375#[derive(Debug, Clone)]
377pub struct EnumCoverage {
378 pub variant_distribution: HashMap<String, usize>,
380 pub total_count: usize,
382 pub coverage_percentage: f64,
384}
385
386pub trait CustomCoverage: std::fmt::Debug {
388 fn record_value(&mut self, value: &dyn std::any::Any);
390 fn get_summary(&self) -> String;
392 fn get_coverage_percentage(&self) -> f64;
394}
395
396#[derive(Debug, Clone)]
398pub struct GenerationPerformanceMetrics {
399 pub total_generation_time: std::time::Duration,
401 pub average_generation_time: std::time::Duration,
403 pub fastest_generation: std::time::Duration,
405 pub slowest_generation: std::time::Duration,
407 pub memory_stats: MemoryStats,
409}
410
411#[derive(Debug, Clone, Default)]
413pub struct MemoryStats {
414 pub peak_memory_usage: usize,
416 pub average_memory_usage: usize,
418 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 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 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 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 let bucket = self.get_bucket_for_value(value);
474 *self.range_distribution.entry(bucket).or_insert(0) += 1;
475
476 self.statistics.update(value, self.total_count);
478 }
479
480 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 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 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 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 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 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 self.skewness = 0.0; self.kurtosis = 0.0; }
554}
555
556impl Default for StringCoverage {
557 fn default() -> Self {
558 Self::new()
559 }
560}
561
562impl StringCoverage {
563 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 pub fn record_value(&mut self, value: &str) {
576 self.total_count += 1;
577
578 *self.length_distribution.entry(value.len()).or_insert(0) += 1;
580
581 for ch in value.chars() {
583 *self.character_distribution.entry(ch).or_insert(0) += 1;
584 }
585
586 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 self.check_patterns(value);
593 }
594
595 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 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 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 pub fn record_size(&mut self, size: usize) {
658 self.total_count += 1;
659
660 *self.size_distribution.entry(size).or_insert(0) += 1;
662
663 self.min_size = self.min_size.min(size);
665 self.max_size = self.max_size.max(size);
666
667 self.average_size = (self.average_size * (self.total_count - 1) as f64 + size as f64)
669 / self.total_count as f64;
670 }
671
672 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 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 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 pub fn has_full_coverage(&self) -> bool {
721 self.true_count > 0 && self.false_count > 0
722 }
723}
724
725impl EnumCoverage {
726 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 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 let covered_variants = self.variant_distribution.len();
745 self.coverage_percentage = covered_variants as f64 / total_variants as f64;
746 }
747
748 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 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 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 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 report.push_str("📈 COVERAGE INFORMATION:\n");
790
791 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 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 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 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 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 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 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 pub fn check_coverage_thresholds(&self, thresholds: &CoverageThresholds) -> CoverageReport {
899 let mut report = CoverageReport::new();
900
901 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 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 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#[derive(Debug, Clone)]
936pub struct CoverageThresholds {
937 pub numeric_thresholds: HashMap<String, NumericThreshold>,
939 pub require_full_boolean_coverage: bool,
941 pub enum_thresholds: HashMap<String, EnumThreshold>,
943}
944
945#[derive(Debug, Clone)]
947pub struct NumericThreshold {
948 pub min_value: f64,
950 pub max_value: f64,
952 pub min_coverage: f64,
954}
955
956#[derive(Debug, Clone)]
958pub struct EnumThreshold {
959 pub min_coverage: f64,
961}
962
963#[derive(Debug, Clone)]
965pub struct CoverageReport {
966 pub numeric_results: HashMap<String, bool>,
968 pub boolean_results: HashMap<String, bool>,
970 pub enum_results: HashMap<String, bool>,
972 pub overall_pass: bool,
974}
975
976impl Default for CoverageReport {
977 fn default() -> Self {
978 Self::new()
979 }
980}
981
982impl CoverageReport {
983 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 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 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 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 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
1055pub struct ConfigManager {
1057 global_config: GlobalConfig,
1058}
1059
1060impl ConfigManager {
1061 pub fn new() -> Self {
1063 Self {
1064 global_config: GlobalConfig::default(),
1065 }
1066 }
1067
1068 pub fn with_global_config(global_config: GlobalConfig) -> Result<Self, ConfigError> {
1070 global_config.validate()?;
1071 Ok(Self { global_config })
1072 }
1073
1074 pub fn global_config(&self) -> &GlobalConfig {
1076 &self.global_config
1077 }
1078
1079 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 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 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
1114thread_local! {
1116 static CONFIG_MANAGER: std::cell::RefCell<ConfigManager> = std::cell::RefCell::new(ConfigManager::new());
1117}
1118
1119pub fn get_global_config() -> GlobalConfig {
1121 CONFIG_MANAGER.with(|manager| manager.borrow().global_config().clone())
1122}
1123
1124pub 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
1130pub fn create_test_config() -> TestConfig {
1132 CONFIG_MANAGER.with(|manager| manager.borrow().create_test_config())
1133}
1134
1135pub 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 let config = GeneratorConfig::new(10, 5, HashMap::new());
1155 assert!(config.is_ok());
1156
1157 let config = GeneratorConfig::new(10, 0, HashMap::new());
1159 assert!(matches!(config, Err(ConfigError::InvalidMaxDepth(0))));
1160
1161 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 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 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 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 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 let config = GlobalConfig::new(100, None, GeneratorConfig::default());
1222 assert!(config.is_ok());
1223
1224 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 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, generator_config: GeneratorConfig::default(), };
1281
1282 let merged = test_config.merge_with_global(&global);
1283
1284 assert_eq!(merged.seed, Some(123));
1286 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 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 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 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 let global = manager.global_config();
1327 assert_eq!(global.default_iterations, 100);
1328
1329 let test_config = manager.create_test_config();
1331 assert_eq!(test_config.iterations, 100);
1332
1333 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 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 let global = get_global_config();
1362 assert_eq!(global.default_iterations, 100);
1363
1364 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 let test_config = create_test_config();
1378 assert_eq!(test_config.iterations, 150);
1379 assert_eq!(test_config.seed, Some(555));
1380
1381 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)); }
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 let default_config = GeneratorConfig::default();
1418 let merged = default_config.merge_with(&base);
1419 assert_eq!(merged.size_hint, 20); assert_eq!(merged.max_depth, 3); 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); assert_eq!(merged.max_depth, 7); }
1432}