use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Clone, PartialEq)]
pub enum ConfigError {
InvalidIterations(usize),
InvalidShrinkIterations(usize),
InvalidTimeout,
InvalidMaxDepth(usize),
}
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConfigError::InvalidIterations(n) => {
write!(f, "Invalid iterations count: {} (must be > 0)", n)
}
ConfigError::InvalidShrinkIterations(n) => {
write!(f, "Invalid shrink iterations count: {} (must be > 0)", n)
}
ConfigError::InvalidTimeout => {
write!(f, "Invalid timeout (must be > 0)")
}
ConfigError::InvalidMaxDepth(n) => {
write!(f, "Invalid max depth: {} (must be > 0)", n)
}
}
}
}
impl std::error::Error for ConfigError {}
#[derive(Debug)]
pub struct GeneratorConfig {
pub size_hint: usize,
pub max_depth: usize,
pub custom_ranges: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
}
impl Clone for GeneratorConfig {
fn clone(&self) -> Self {
Self {
size_hint: self.size_hint,
max_depth: self.max_depth,
custom_ranges: HashMap::new(),
}
}
}
impl Default for GeneratorConfig {
fn default() -> Self {
Self {
size_hint: 10,
max_depth: 5,
custom_ranges: HashMap::new(),
}
}
}
impl GeneratorConfig {
pub fn new(
size_hint: usize,
max_depth: usize,
custom_ranges: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
) -> Result<Self, ConfigError> {
if max_depth == 0 {
return Err(ConfigError::InvalidMaxDepth(max_depth));
}
Ok(Self {
size_hint,
max_depth,
custom_ranges,
})
}
pub fn validate(&self) -> Result<(), ConfigError> {
if self.max_depth == 0 {
return Err(ConfigError::InvalidMaxDepth(self.max_depth));
}
Ok(())
}
pub fn merge_with(self, _other: &GeneratorConfig) -> Self {
Self {
size_hint: if self.size_hint != 10 {
self.size_hint
} else {
_other.size_hint
},
max_depth: if self.max_depth != 5 {
self.max_depth
} else {
_other.max_depth
},
custom_ranges: self.custom_ranges, }
}
}
#[derive(Debug, Clone)]
pub struct TestConfig {
pub iterations: usize,
pub max_shrink_iterations: usize,
pub shrink_timeout: Duration,
pub seed: Option<u64>,
pub generator_config: GeneratorConfig,
}
impl Default for TestConfig {
fn default() -> Self {
Self {
iterations: 100,
max_shrink_iterations: 1000,
shrink_timeout: Duration::from_secs(10),
seed: None,
generator_config: GeneratorConfig::default(),
}
}
}
impl TestConfig {
pub fn new(
iterations: usize,
max_shrink_iterations: usize,
shrink_timeout: Duration,
seed: Option<u64>,
generator_config: GeneratorConfig,
) -> Result<Self, ConfigError> {
if iterations == 0 {
return Err(ConfigError::InvalidIterations(iterations));
}
if max_shrink_iterations == 0 {
return Err(ConfigError::InvalidShrinkIterations(max_shrink_iterations));
}
if shrink_timeout.is_zero() {
return Err(ConfigError::InvalidTimeout);
}
Ok(Self {
iterations,
max_shrink_iterations,
shrink_timeout,
seed,
generator_config,
})
}
pub fn validate(&self) -> Result<(), ConfigError> {
if self.iterations == 0 {
return Err(ConfigError::InvalidIterations(self.iterations));
}
if self.max_shrink_iterations == 0 {
return Err(ConfigError::InvalidShrinkIterations(
self.max_shrink_iterations,
));
}
if self.shrink_timeout.is_zero() {
return Err(ConfigError::InvalidTimeout);
}
self.generator_config.validate()?;
Ok(())
}
pub fn merge_with_global(self, global: &GlobalConfig) -> Self {
Self {
iterations: self.iterations,
max_shrink_iterations: self.max_shrink_iterations,
shrink_timeout: self.shrink_timeout,
seed: self.seed.or(global.default_seed),
generator_config: self.generator_config.merge_with(&global.generator_config),
}
}
pub fn from_global_with_overrides(
global: &GlobalConfig,
iterations: Option<usize>,
seed: Option<u64>,
generator_overrides: Option<GeneratorConfig>,
) -> Result<Self, ConfigError> {
let config = Self {
iterations: iterations.unwrap_or(global.default_iterations),
max_shrink_iterations: 1000, shrink_timeout: Duration::from_secs(10), seed: seed.or(global.default_seed),
generator_config: generator_overrides
.unwrap_or_else(|| global.generator_config.clone()),
};
config.validate()?;
Ok(config)
}
}
#[derive(Debug, Clone)]
pub struct GlobalConfig {
pub default_iterations: usize,
pub default_seed: Option<u64>,
pub generator_config: GeneratorConfig,
}
impl Default for GlobalConfig {
fn default() -> Self {
Self {
default_iterations: 100,
default_seed: None,
generator_config: GeneratorConfig::default(),
}
}
}
impl GlobalConfig {
pub fn new(
default_iterations: usize,
default_seed: Option<u64>,
generator_config: GeneratorConfig,
) -> Result<Self, ConfigError> {
if default_iterations == 0 {
return Err(ConfigError::InvalidIterations(default_iterations));
}
Ok(Self {
default_iterations,
default_seed,
generator_config,
})
}
pub fn validate(&self) -> Result<(), ConfigError> {
if self.default_iterations == 0 {
return Err(ConfigError::InvalidIterations(self.default_iterations));
}
self.generator_config.validate()?;
Ok(())
}
}
#[derive(Debug, Default)]
pub struct GenerationStats {
pub total_generated: usize,
pub distribution_info: HashMap<String, Box<dyn Any + Send + Sync>>,
pub coverage_info: CoverageInfo,
pub performance_metrics: GenerationPerformanceMetrics,
}
#[derive(Debug, Default)]
pub struct CoverageInfo {
pub numeric_coverage: HashMap<String, NumericCoverage>,
pub string_coverage: HashMap<String, StringCoverage>,
pub collection_coverage: HashMap<String, CollectionCoverage>,
pub boolean_coverage: HashMap<String, BooleanCoverage>,
pub enum_coverage: HashMap<String, EnumCoverage>,
pub custom_coverage: HashMap<String, Box<dyn CustomCoverage + Send + Sync>>,
}
#[derive(Debug, Clone)]
pub struct NumericCoverage {
pub min_value: f64,
pub max_value: f64,
pub total_count: usize,
pub range_distribution: HashMap<String, usize>,
pub statistics: NumericStatistics,
}
#[derive(Debug, Clone)]
pub struct NumericStatistics {
pub mean: f64,
pub variance: f64,
pub std_dev: f64,
pub skewness: f64,
pub kurtosis: f64,
}
#[derive(Debug, Clone)]
pub struct StringCoverage {
pub length_distribution: HashMap<usize, usize>,
pub character_distribution: HashMap<char, usize>,
pub pattern_coverage: HashMap<String, usize>,
pub total_count: usize,
pub average_length: f64,
}
#[derive(Debug, Clone)]
pub struct CollectionCoverage {
pub size_distribution: HashMap<usize, usize>,
pub total_count: usize,
pub average_size: f64,
pub max_size: usize,
pub min_size: usize,
}
#[derive(Debug, Clone)]
pub struct BooleanCoverage {
pub true_count: usize,
pub false_count: usize,
pub total_count: usize,
pub true_ratio: f64,
}
#[derive(Debug, Clone)]
pub struct EnumCoverage {
pub variant_distribution: HashMap<String, usize>,
pub total_count: usize,
pub coverage_percentage: f64,
}
pub trait CustomCoverage: std::fmt::Debug {
fn record_value(&mut self, value: &dyn std::any::Any);
fn get_summary(&self) -> String;
fn get_coverage_percentage(&self) -> f64;
}
#[derive(Debug, Clone)]
pub struct GenerationPerformanceMetrics {
pub total_generation_time: std::time::Duration,
pub average_generation_time: std::time::Duration,
pub fastest_generation: std::time::Duration,
pub slowest_generation: std::time::Duration,
pub memory_stats: MemoryStats,
}
#[derive(Debug, Clone, Default)]
pub struct MemoryStats {
pub peak_memory_usage: usize,
pub average_memory_usage: usize,
pub total_allocations: usize,
}
impl Clone for CoverageInfo {
fn clone(&self) -> Self {
Self {
numeric_coverage: self.numeric_coverage.clone(),
string_coverage: self.string_coverage.clone(),
collection_coverage: self.collection_coverage.clone(),
boolean_coverage: self.boolean_coverage.clone(),
enum_coverage: self.enum_coverage.clone(),
custom_coverage: HashMap::new(),
}
}
}
impl Default for GenerationPerformanceMetrics {
fn default() -> Self {
Self {
total_generation_time: std::time::Duration::from_secs(0),
average_generation_time: std::time::Duration::from_secs(0),
fastest_generation: std::time::Duration::from_secs(u64::MAX),
slowest_generation: std::time::Duration::from_secs(0),
memory_stats: MemoryStats::default(),
}
}
}
impl Default for NumericCoverage {
fn default() -> Self {
Self::new()
}
}
impl NumericCoverage {
pub fn new() -> Self {
Self {
min_value: f64::INFINITY,
max_value: f64::NEG_INFINITY,
total_count: 0,
range_distribution: HashMap::new(),
statistics: NumericStatistics::new(),
}
}
pub fn record_value(&mut self, value: f64) {
self.min_value = self.min_value.min(value);
self.max_value = self.max_value.max(value);
self.total_count += 1;
let bucket = self.get_bucket_for_value(value);
*self.range_distribution.entry(bucket).or_insert(0) += 1;
self.statistics.update(value, self.total_count);
}
fn get_bucket_for_value(&self, value: f64) -> String {
let bucket_size = 10.0;
let bucket_index = (value / bucket_size).floor() as i64;
format!(
"[{}, {})",
bucket_index as f64 * bucket_size,
(bucket_index + 1) as f64 * bucket_size
)
}
pub fn get_range_coverage(&self, min: f64, max: f64) -> f64 {
if self.total_count == 0 {
return 0.0;
}
let values_in_range = self
.range_distribution
.iter()
.filter(|(range, _)| {
range.contains(&min.to_string()) || range.contains(&max.to_string())
})
.map(|(_, count)| *count)
.sum::<usize>();
values_in_range as f64 / self.total_count as f64
}
}
impl Default for NumericStatistics {
fn default() -> Self {
Self::new()
}
}
impl NumericStatistics {
pub fn new() -> Self {
Self {
mean: 0.0,
variance: 0.0,
std_dev: 0.0,
skewness: 0.0,
kurtosis: 0.0,
}
}
pub fn update(&mut self, value: f64, count: usize) {
if count == 1 {
self.mean = value;
self.variance = 0.0;
self.std_dev = 0.0;
return;
}
let delta = value - self.mean;
self.mean += delta / count as f64;
let delta2 = value - self.mean;
self.variance += delta * delta2;
if count > 1 {
self.variance /= (count - 1) as f64;
self.std_dev = self.variance.sqrt();
}
self.skewness = 0.0; self.kurtosis = 0.0; }
}
impl Default for StringCoverage {
fn default() -> Self {
Self::new()
}
}
impl StringCoverage {
pub fn new() -> Self {
Self {
length_distribution: HashMap::new(),
character_distribution: HashMap::new(),
pattern_coverage: HashMap::new(),
total_count: 0,
average_length: 0.0,
}
}
pub fn record_value(&mut self, value: &str) {
self.total_count += 1;
*self.length_distribution.entry(value.len()).or_insert(0) += 1;
for ch in value.chars() {
*self.character_distribution.entry(ch).or_insert(0) += 1;
}
self.average_length = (self.average_length * (self.total_count - 1) as f64
+ value.len() as f64)
/ self.total_count as f64;
self.check_patterns(value);
}
fn check_patterns(&mut self, value: &str) {
if value.chars().all(|c| c.is_ascii_alphabetic()) {
*self
.pattern_coverage
.entry("alphabetic".to_string())
.or_insert(0) += 1;
}
if value.chars().all(|c| c.is_ascii_digit()) {
*self
.pattern_coverage
.entry("numeric".to_string())
.or_insert(0) += 1;
}
if value.chars().all(|c| c.is_ascii_alphanumeric()) {
*self
.pattern_coverage
.entry("alphanumeric".to_string())
.or_insert(0) += 1;
}
if value.contains(' ') {
*self
.pattern_coverage
.entry("contains_space".to_string())
.or_insert(0) += 1;
}
}
pub fn get_character_set_coverage(&self, expected_chars: &[char]) -> f64 {
if expected_chars.is_empty() {
return 1.0;
}
let covered_chars = expected_chars
.iter()
.filter(|ch| self.character_distribution.contains_key(ch))
.count();
covered_chars as f64 / expected_chars.len() as f64
}
}
impl Default for CollectionCoverage {
fn default() -> Self {
Self::new()
}
}
impl CollectionCoverage {
pub fn new() -> Self {
Self {
size_distribution: HashMap::new(),
total_count: 0,
average_size: 0.0,
max_size: 0,
min_size: usize::MAX,
}
}
pub fn record_size(&mut self, size: usize) {
self.total_count += 1;
*self.size_distribution.entry(size).or_insert(0) += 1;
self.min_size = self.min_size.min(size);
self.max_size = self.max_size.max(size);
self.average_size = (self.average_size * (self.total_count - 1) as f64 + size as f64)
/ self.total_count as f64;
}
pub fn get_size_range_coverage(&self, min_size: usize, max_size: usize) -> f64 {
if self.total_count == 0 {
return 0.0;
}
let values_in_range = self
.size_distribution
.iter()
.filter(|(size, _)| **size >= min_size && **size <= max_size)
.map(|(_, count)| *count)
.sum::<usize>();
values_in_range as f64 / self.total_count as f64
}
}
impl Default for BooleanCoverage {
fn default() -> Self {
Self::new()
}
}
impl BooleanCoverage {
pub fn new() -> Self {
Self {
true_count: 0,
false_count: 0,
total_count: 0,
true_ratio: 0.0,
}
}
pub fn record_value(&mut self, value: bool) {
self.total_count += 1;
if value {
self.true_count += 1;
} else {
self.false_count += 1;
}
self.true_ratio = self.true_count as f64 / self.total_count as f64;
}
pub fn has_full_coverage(&self) -> bool {
self.true_count > 0 && self.false_count > 0
}
}
impl EnumCoverage {
pub fn new(_total_variants: usize) -> Self {
Self {
variant_distribution: HashMap::new(),
total_count: 0,
coverage_percentage: 0.0,
}
}
pub fn record_variant(&mut self, variant_name: &str, total_variants: usize) {
self.total_count += 1;
*self
.variant_distribution
.entry(variant_name.to_string())
.or_insert(0) += 1;
let covered_variants = self.variant_distribution.len();
self.coverage_percentage = covered_variants as f64 / total_variants as f64;
}
pub fn get_least_covered_variant(&self) -> Option<(&String, &usize)> {
self.variant_distribution
.iter()
.min_by_key(|(_, count)| *count)
}
pub fn get_most_covered_variant(&self) -> Option<(&String, &usize)> {
self.variant_distribution
.iter()
.max_by_key(|(_, count)| *count)
}
}
impl GenerationStats {
pub fn generate_report(&self) -> String {
let mut report = String::new();
report.push_str("═══════════════════════════════════════════════════════════════\n");
report.push_str(" GENERATION STATISTICS \n");
report.push_str("═══════════════════════════════════════════════════════════════\n\n");
report.push_str("📊 BASIC STATISTICS:\n");
report.push_str(&format!(
" Total values generated: {}\n",
self.total_generated
));
report.push_str(&format!(
" Generation time: {:?}\n",
self.performance_metrics.total_generation_time
));
report.push_str(&format!(
" Average time per value: {:?}\n",
self.performance_metrics.average_generation_time
));
report.push('\n');
report.push_str("📈 COVERAGE INFORMATION:\n");
if !self.coverage_info.numeric_coverage.is_empty() {
report.push_str(" Numeric Types:\n");
for (type_name, coverage) in &self.coverage_info.numeric_coverage {
report.push_str(&format!(
" {}: {} values, range [{:.2}, {:.2}], mean: {:.2}\n",
type_name,
coverage.total_count,
coverage.min_value,
coverage.max_value,
coverage.statistics.mean
));
}
}
if !self.coverage_info.string_coverage.is_empty() {
report.push_str(" String Types:\n");
for (type_name, coverage) in &self.coverage_info.string_coverage {
report.push_str(&format!(
" {}: {} strings, avg length: {:.1}, {} unique chars\n",
type_name,
coverage.total_count,
coverage.average_length,
coverage.character_distribution.len()
));
}
}
if !self.coverage_info.collection_coverage.is_empty() {
report.push_str(" Collection Types:\n");
for (type_name, coverage) in &self.coverage_info.collection_coverage {
report.push_str(&format!(
" {}: {} collections, avg size: {:.1}, range [{}, {}]\n",
type_name,
coverage.total_count,
coverage.average_size,
coverage.min_size,
coverage.max_size
));
}
}
if !self.coverage_info.boolean_coverage.is_empty() {
report.push_str(" Boolean Types:\n");
for (type_name, coverage) in &self.coverage_info.boolean_coverage {
let coverage_status = if coverage.has_full_coverage() {
"✓"
} else {
"✗"
};
report.push_str(&format!(
" {}: {} values, true ratio: {:.2} {}\n",
type_name, coverage.total_count, coverage.true_ratio, coverage_status
));
}
}
if !self.coverage_info.enum_coverage.is_empty() {
report.push_str(" Enum Types:\n");
for (type_name, coverage) in &self.coverage_info.enum_coverage {
report.push_str(&format!(
" {}: {} values, {:.1}% variant coverage\n",
type_name,
coverage.total_count,
coverage.coverage_percentage * 100.0
));
}
}
report.push('\n');
report.push_str("⚡ PERFORMANCE METRICS:\n");
report.push_str(&format!(
" Fastest generation: {:?}\n",
self.performance_metrics.fastest_generation
));
report.push_str(&format!(
" Slowest generation: {:?}\n",
self.performance_metrics.slowest_generation
));
report.push_str(&format!(
" Peak memory usage: {} bytes\n",
self.performance_metrics.memory_stats.peak_memory_usage
));
report.push('\n');
report.push_str("═══════════════════════════════════════════════════════════════\n");
report
}
pub fn get_summary(&self) -> String {
format!(
"Generated {} values in {:?} (avg: {:?}/value)",
self.total_generated,
self.performance_metrics.total_generation_time,
self.performance_metrics.average_generation_time
)
}
pub fn check_coverage_thresholds(&self, thresholds: &CoverageThresholds) -> CoverageReport {
let mut report = CoverageReport::new();
for (type_name, coverage) in &self.coverage_info.numeric_coverage {
if let Some(threshold) = thresholds.numeric_thresholds.get(type_name) {
let range_coverage =
coverage.get_range_coverage(threshold.min_value, threshold.max_value);
report.add_numeric_result(
type_name.clone(),
range_coverage >= threshold.min_coverage,
);
}
}
for (type_name, coverage) in &self.coverage_info.boolean_coverage {
if thresholds.require_full_boolean_coverage {
report.add_boolean_result(type_name.clone(), coverage.has_full_coverage());
}
}
for (type_name, coverage) in &self.coverage_info.enum_coverage {
if let Some(threshold) = thresholds.enum_thresholds.get(type_name) {
report.add_enum_result(
type_name.clone(),
coverage.coverage_percentage >= threshold.min_coverage,
);
}
}
report
}
}
#[derive(Debug, Clone)]
pub struct CoverageThresholds {
pub numeric_thresholds: HashMap<String, NumericThreshold>,
pub require_full_boolean_coverage: bool,
pub enum_thresholds: HashMap<String, EnumThreshold>,
}
#[derive(Debug, Clone)]
pub struct NumericThreshold {
pub min_value: f64,
pub max_value: f64,
pub min_coverage: f64,
}
#[derive(Debug, Clone)]
pub struct EnumThreshold {
pub min_coverage: f64,
}
#[derive(Debug, Clone)]
pub struct CoverageReport {
pub numeric_results: HashMap<String, bool>,
pub boolean_results: HashMap<String, bool>,
pub enum_results: HashMap<String, bool>,
pub overall_pass: bool,
}
impl Default for CoverageReport {
fn default() -> Self {
Self::new()
}
}
impl CoverageReport {
pub fn new() -> Self {
Self {
numeric_results: HashMap::new(),
boolean_results: HashMap::new(),
enum_results: HashMap::new(),
overall_pass: true,
}
}
pub fn add_numeric_result(&mut self, type_name: String, passed: bool) {
self.numeric_results.insert(type_name, passed);
if !passed {
self.overall_pass = false;
}
}
pub fn add_boolean_result(&mut self, type_name: String, passed: bool) {
self.boolean_results.insert(type_name, passed);
if !passed {
self.overall_pass = false;
}
}
pub fn add_enum_result(&mut self, type_name: String, passed: bool) {
self.enum_results.insert(type_name, passed);
if !passed {
self.overall_pass = false;
}
}
pub fn generate_report(&self) -> String {
let mut report = String::new();
report.push_str("Coverage Threshold Report:\n");
report.push_str(&format!(
"Overall Status: {}\n",
if self.overall_pass { "PASS" } else { "FAIL" }
));
if !self.numeric_results.is_empty() {
report.push_str("\nNumeric Coverage:\n");
for (type_name, passed) in &self.numeric_results {
let status = if *passed { "✓" } else { "✗" };
report.push_str(&format!(" {} {}\n", status, type_name));
}
}
if !self.boolean_results.is_empty() {
report.push_str("\nBoolean Coverage:\n");
for (type_name, passed) in &self.boolean_results {
let status = if *passed { "✓" } else { "✗" };
report.push_str(&format!(" {} {}\n", status, type_name));
}
}
if !self.enum_results.is_empty() {
report.push_str("\nEnum Coverage:\n");
for (type_name, passed) in &self.enum_results {
let status = if *passed { "✓" } else { "✗" };
report.push_str(&format!(" {} {}\n", status, type_name));
}
}
report
}
}
pub struct ConfigManager {
global_config: GlobalConfig,
}
impl ConfigManager {
pub fn new() -> Self {
Self {
global_config: GlobalConfig::default(),
}
}
pub fn with_global_config(global_config: GlobalConfig) -> Result<Self, ConfigError> {
global_config.validate()?;
Ok(Self { global_config })
}
pub fn global_config(&self) -> &GlobalConfig {
&self.global_config
}
pub fn set_global_config(&mut self, global_config: GlobalConfig) -> Result<(), ConfigError> {
global_config.validate()?;
self.global_config = global_config;
Ok(())
}
pub fn create_test_config(&self) -> TestConfig {
TestConfig::from_global_with_overrides(&self.global_config, None, None, None)
.unwrap_or_else(|_| TestConfig::default())
}
pub fn create_test_config_with_overrides(
&self,
iterations: Option<usize>,
seed: Option<u64>,
generator_overrides: Option<GeneratorConfig>,
) -> Result<TestConfig, ConfigError> {
TestConfig::from_global_with_overrides(
&self.global_config,
iterations,
seed,
generator_overrides,
)
}
}
impl Default for ConfigManager {
fn default() -> Self {
Self::new()
}
}
thread_local! {
static CONFIG_MANAGER: std::cell::RefCell<ConfigManager> = std::cell::RefCell::new(ConfigManager::new());
}
pub fn get_global_config() -> GlobalConfig {
CONFIG_MANAGER.with(|manager| manager.borrow().global_config().clone())
}
pub fn set_global_config(config: GlobalConfig) -> Result<(), ConfigError> {
config.validate()?;
CONFIG_MANAGER.with(|manager| manager.borrow_mut().set_global_config(config))
}
pub fn create_test_config() -> TestConfig {
CONFIG_MANAGER.with(|manager| manager.borrow().create_test_config())
}
pub fn create_test_config_with_overrides(
iterations: Option<usize>,
seed: Option<u64>,
generator_overrides: Option<GeneratorConfig>,
) -> Result<TestConfig, ConfigError> {
CONFIG_MANAGER.with(|manager| {
manager
.borrow()
.create_test_config_with_overrides(iterations, seed, generator_overrides)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generator_config_validation() {
let config = GeneratorConfig::new(10, 5, HashMap::new());
assert!(config.is_ok());
let config = GeneratorConfig::new(10, 0, HashMap::new());
assert!(matches!(config, Err(ConfigError::InvalidMaxDepth(0))));
let mut config = GeneratorConfig::default();
assert!(config.validate().is_ok());
config.max_depth = 0;
assert!(matches!(
config.validate(),
Err(ConfigError::InvalidMaxDepth(0))
));
}
#[test]
fn test_test_config_validation() {
let config = TestConfig::new(
100,
1000,
Duration::from_secs(10),
None,
GeneratorConfig::default(),
);
assert!(config.is_ok());
let config = TestConfig::new(
0,
1000,
Duration::from_secs(10),
None,
GeneratorConfig::default(),
);
assert!(matches!(config, Err(ConfigError::InvalidIterations(0))));
let config = TestConfig::new(
100,
0,
Duration::from_secs(10),
None,
GeneratorConfig::default(),
);
assert!(matches!(
config,
Err(ConfigError::InvalidShrinkIterations(0))
));
let config = TestConfig::new(
100,
1000,
Duration::from_secs(0),
None,
GeneratorConfig::default(),
);
assert!(matches!(config, Err(ConfigError::InvalidTimeout)));
}
#[test]
fn test_global_config_validation() {
let config = GlobalConfig::new(100, None, GeneratorConfig::default());
assert!(config.is_ok());
let config = GlobalConfig::new(0, None, GeneratorConfig::default());
assert!(matches!(config, Err(ConfigError::InvalidIterations(0))));
}
#[test]
fn test_config_merge_precedence() {
let global = GlobalConfig {
default_iterations: 50,
default_seed: Some(123),
generator_config: GeneratorConfig {
size_hint: 20,
max_depth: 3,
custom_ranges: HashMap::new(),
},
};
let test_config = TestConfig {
iterations: 200,
max_shrink_iterations: 500,
shrink_timeout: Duration::from_secs(5),
seed: Some(456),
generator_config: GeneratorConfig {
size_hint: 15,
max_depth: 7,
custom_ranges: HashMap::new(),
},
};
let merged = test_config.merge_with_global(&global);
assert_eq!(merged.iterations, 200);
assert_eq!(merged.seed, Some(456));
assert_eq!(merged.generator_config.size_hint, 15);
assert_eq!(merged.generator_config.max_depth, 7);
}
#[test]
fn test_config_merge_with_defaults() {
let global = GlobalConfig {
default_iterations: 50,
default_seed: Some(123),
generator_config: GeneratorConfig {
size_hint: 20,
max_depth: 3,
custom_ranges: HashMap::new(),
},
};
let test_config = TestConfig {
iterations: 200,
max_shrink_iterations: 500,
shrink_timeout: Duration::from_secs(5),
seed: None, generator_config: GeneratorConfig::default(), };
let merged = test_config.merge_with_global(&global);
assert_eq!(merged.seed, Some(123));
assert_eq!(merged.generator_config.size_hint, 20);
assert_eq!(merged.generator_config.max_depth, 3);
}
#[test]
fn test_from_global_with_overrides() {
let global = GlobalConfig {
default_iterations: 50,
default_seed: Some(123),
generator_config: GeneratorConfig {
size_hint: 20,
max_depth: 3,
custom_ranges: HashMap::new(),
},
};
let config = TestConfig::from_global_with_overrides(&global, None, None, None).unwrap();
assert_eq!(config.iterations, 50);
assert_eq!(config.seed, Some(123));
let config =
TestConfig::from_global_with_overrides(&global, Some(100), None, None).unwrap();
assert_eq!(config.iterations, 100);
assert_eq!(config.seed, Some(123));
let config =
TestConfig::from_global_with_overrides(&global, None, Some(456), None).unwrap();
assert_eq!(config.iterations, 50);
assert_eq!(config.seed, Some(456));
}
#[test]
fn test_config_manager() {
let mut manager = ConfigManager::new();
let global = manager.global_config();
assert_eq!(global.default_iterations, 100);
let test_config = manager.create_test_config();
assert_eq!(test_config.iterations, 100);
let new_global = GlobalConfig {
default_iterations: 200,
default_seed: Some(789),
generator_config: GeneratorConfig::default(),
};
manager.set_global_config(new_global).unwrap();
let updated_test_config = manager.create_test_config();
assert_eq!(updated_test_config.iterations, 200);
assert_eq!(updated_test_config.seed, Some(789));
}
#[test]
fn test_config_manager_with_overrides() {
let manager = ConfigManager::new();
let config = manager
.create_test_config_with_overrides(Some(300), Some(999), None)
.unwrap();
assert_eq!(config.iterations, 300);
assert_eq!(config.seed, Some(999));
}
#[test]
fn test_thread_local_config_functions() {
let global = get_global_config();
assert_eq!(global.default_iterations, 100);
let new_global = GlobalConfig {
default_iterations: 150,
default_seed: Some(555),
generator_config: GeneratorConfig::default(),
};
set_global_config(new_global).unwrap();
let updated_global = get_global_config();
assert_eq!(updated_global.default_iterations, 150);
assert_eq!(updated_global.default_seed, Some(555));
let test_config = create_test_config();
assert_eq!(test_config.iterations, 150);
assert_eq!(test_config.seed, Some(555));
let config = create_test_config_with_overrides(Some(250), None, None).unwrap();
assert_eq!(config.iterations, 250);
assert_eq!(config.seed, Some(555)); }
#[test]
fn test_config_error_display() {
let error = ConfigError::InvalidIterations(0);
assert_eq!(
format!("{}", error),
"Invalid iterations count: 0 (must be > 0)"
);
let error = ConfigError::InvalidShrinkIterations(0);
assert_eq!(
format!("{}", error),
"Invalid shrink iterations count: 0 (must be > 0)"
);
let error = ConfigError::InvalidTimeout;
assert_eq!(format!("{}", error), "Invalid timeout (must be > 0)");
let error = ConfigError::InvalidMaxDepth(0);
assert_eq!(format!("{}", error), "Invalid max depth: 0 (must be > 0)");
}
#[test]
fn test_generator_config_merge() {
let base = GeneratorConfig {
size_hint: 20,
max_depth: 3,
custom_ranges: HashMap::new(),
};
let default_config = GeneratorConfig::default();
let merged = default_config.merge_with(&base);
assert_eq!(merged.size_hint, 20); assert_eq!(merged.max_depth, 3);
let override_config = GeneratorConfig {
size_hint: 15,
max_depth: 7,
custom_ranges: HashMap::new(),
};
let merged = override_config.merge_with(&base);
assert_eq!(merged.size_hint, 15); assert_eq!(merged.max_depth, 7); }
}