use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum DisplayMode {
#[default]
Rich,
Minimal,
Json,
Silent,
}
impl std::str::FromStr for DisplayMode {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.to_lowercase().as_str() {
"rich" => Self::Rich,
"minimal" => Self::Minimal,
"json" => Self::Json,
"silent" => Self::Silent,
_ => Self::Rich, })
}
}
impl DisplayMode {
pub fn parse(s: &str) -> Self {
s.parse().unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConvergenceConfig {
pub input_dir: PathBuf,
pub target_rate: f64,
pub max_iterations: usize,
pub auto_fix: bool,
pub dry_run: bool,
pub verbose: bool,
pub fix_confidence_threshold: f64,
pub checkpoint_dir: Option<PathBuf>,
pub parallel_jobs: usize,
#[serde(default)]
pub display_mode: DisplayMode,
#[serde(default)]
pub oracle: bool,
#[serde(default)]
pub explain: bool,
#[serde(default = "default_use_cache")]
pub use_cache: bool,
#[serde(default)]
pub patch_transpiler: bool,
#[serde(default)]
pub apr_file: Option<PathBuf>,
}
fn default_use_cache() -> bool {
true
}
impl ConvergenceConfig {
pub fn validate(&self) -> Result<()> {
if self.target_rate < 0.0 || self.target_rate > 100.0 {
bail!("target_rate must be between 0 and 100");
}
if self.max_iterations == 0 {
bail!("max_iterations must be > 0");
}
if self.fix_confidence_threshold < 0.0 || self.fix_confidence_threshold > 1.0 {
bail!("fix_confidence_threshold must be between 0 and 1");
}
if self.parallel_jobs == 0 {
bail!("parallel_jobs must be > 0");
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExampleState {
pub path: PathBuf,
pub compiles: bool,
pub errors: Vec<String>,
pub last_compiled: Option<std::time::SystemTime>,
}
impl ExampleState {
pub fn new(path: PathBuf, compiles: bool) -> Self {
Self {
path,
compiles,
errors: vec![],
last_compiled: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppliedFix {
pub iteration: usize,
pub error_code: String,
pub description: String,
pub file_modified: PathBuf,
pub commit_hash: Option<String>,
pub verified: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConvergenceState {
pub config: ConvergenceConfig,
pub iteration: usize,
pub compilation_rate: f64,
pub examples: Vec<ExampleState>,
pub error_clusters: Vec<super::ErrorCluster>,
pub fixes_applied: Vec<AppliedFix>,
}
impl ConvergenceState {
pub fn new(config: ConvergenceConfig) -> Self {
Self {
config,
iteration: 0,
compilation_rate: 0.0,
examples: vec![],
error_clusters: vec![],
fixes_applied: vec![],
}
}
pub fn update_examples(&mut self, results: &[super::CompilationResult]) {
self.examples = results
.iter()
.map(|r| ExampleState {
path: r.source_file.clone(),
compiles: r.success,
errors: r.errors.iter().map(|e| e.message.clone()).collect(),
last_compiled: Some(std::time::SystemTime::now()),
})
.collect();
}
pub fn update_compilation_rate(&mut self) {
if self.examples.is_empty() {
self.compilation_rate = 0.0;
} else {
let passing = self.examples.iter().filter(|e| e.compiles).count();
self.compilation_rate = (passing as f64 / self.examples.len() as f64) * 100.0;
}
}
pub fn save_checkpoint(&self, dir: &Path) -> Result<()> {
let checkpoint_file = dir.join("convergence_checkpoint.json");
let json = serde_json::to_string_pretty(self)?;
std::fs::write(checkpoint_file, json)?;
Ok(())
}
pub fn load_checkpoint(dir: &Path) -> Result<Self> {
let checkpoint_file = dir.join("convergence_checkpoint.json");
let json = std::fs::read_to_string(checkpoint_file)?;
let state: Self = serde_json::from_str(&json)?;
Ok(state)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_display_mode_from_str() {
assert_eq!("rich".parse::<DisplayMode>().unwrap(), DisplayMode::Rich);
assert_eq!("RICH".parse::<DisplayMode>().unwrap(), DisplayMode::Rich);
assert_eq!(
"minimal".parse::<DisplayMode>().unwrap(),
DisplayMode::Minimal
);
assert_eq!("json".parse::<DisplayMode>().unwrap(), DisplayMode::Json);
assert_eq!(
"silent".parse::<DisplayMode>().unwrap(),
DisplayMode::Silent
);
assert_eq!("unknown".parse::<DisplayMode>().unwrap(), DisplayMode::Rich);
}
#[test]
fn test_display_mode_parse() {
assert_eq!(DisplayMode::parse("rich"), DisplayMode::Rich);
assert_eq!(DisplayMode::parse("minimal"), DisplayMode::Minimal);
assert_eq!(DisplayMode::parse("json"), DisplayMode::Json);
assert_eq!(DisplayMode::parse("silent"), DisplayMode::Silent);
assert_eq!(DisplayMode::parse("invalid"), DisplayMode::Rich);
}
#[test]
fn test_display_mode_default() {
assert_eq!(DisplayMode::default(), DisplayMode::Rich);
}
fn test_config() -> ConvergenceConfig {
ConvergenceConfig {
input_dir: PathBuf::from("/tmp/test"),
target_rate: 80.0,
max_iterations: 10,
auto_fix: false,
dry_run: false,
verbose: false,
fix_confidence_threshold: 0.8,
checkpoint_dir: None,
parallel_jobs: 4,
display_mode: DisplayMode::Rich,
oracle: false,
explain: false,
use_cache: true,
patch_transpiler: false,
apr_file: None,
}
}
#[test]
fn test_convergence_config_validate_valid() {
let config = test_config();
assert!(config.validate().is_ok());
}
#[test]
fn test_convergence_config_validate_invalid_target_rate_negative() {
let mut config = test_config();
config.target_rate = -1.0;
assert!(config.validate().is_err());
}
#[test]
fn test_convergence_config_validate_invalid_target_rate_over_100() {
let mut config = test_config();
config.target_rate = 101.0;
assert!(config.validate().is_err());
}
#[test]
fn test_convergence_config_validate_invalid_max_iterations() {
let mut config = test_config();
config.max_iterations = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_convergence_config_validate_invalid_confidence_threshold_negative() {
let mut config = test_config();
config.fix_confidence_threshold = -0.1;
assert!(config.validate().is_err());
}
#[test]
fn test_convergence_config_validate_invalid_confidence_threshold_over_1() {
let mut config = test_config();
config.fix_confidence_threshold = 1.1;
assert!(config.validate().is_err());
}
#[test]
fn test_convergence_config_validate_invalid_parallel_jobs() {
let mut config = test_config();
config.parallel_jobs = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_example_state_new() {
let state = ExampleState::new(PathBuf::from("test.py"), true);
assert_eq!(state.path, PathBuf::from("test.py"));
assert!(state.compiles);
assert!(state.errors.is_empty());
assert!(state.last_compiled.is_none());
}
#[test]
fn test_convergence_state_new() {
let config = test_config();
let state = ConvergenceState::new(config.clone());
assert_eq!(state.iteration, 0);
assert_eq!(state.compilation_rate, 0.0);
assert!(state.examples.is_empty());
assert!(state.error_clusters.is_empty());
assert!(state.fixes_applied.is_empty());
}
#[test]
fn test_convergence_state_update_compilation_rate_empty() {
let config = test_config();
let mut state = ConvergenceState::new(config);
state.update_compilation_rate();
assert_eq!(state.compilation_rate, 0.0);
}
#[test]
fn test_convergence_state_update_compilation_rate_all_pass() {
let config = test_config();
let mut state = ConvergenceState::new(config);
state.examples = vec![
ExampleState::new(PathBuf::from("a.py"), true),
ExampleState::new(PathBuf::from("b.py"), true),
];
state.update_compilation_rate();
assert_eq!(state.compilation_rate, 100.0);
}
#[test]
fn test_convergence_state_update_compilation_rate_mixed() {
let config = test_config();
let mut state = ConvergenceState::new(config);
state.examples = vec![
ExampleState::new(PathBuf::from("a.py"), true),
ExampleState::new(PathBuf::from("b.py"), false),
ExampleState::new(PathBuf::from("c.py"), true),
ExampleState::new(PathBuf::from("d.py"), false),
];
state.update_compilation_rate();
assert_eq!(state.compilation_rate, 50.0);
}
#[test]
fn test_convergence_state_checkpoint_roundtrip() {
let config = test_config();
let mut state = ConvergenceState::new(config);
state.iteration = 5;
state.compilation_rate = 75.0;
state.examples = vec![ExampleState::new(PathBuf::from("test.py"), true)];
let temp_dir = tempfile::tempdir().unwrap();
state.save_checkpoint(temp_dir.path()).unwrap();
let loaded = ConvergenceState::load_checkpoint(temp_dir.path()).unwrap();
assert_eq!(loaded.iteration, 5);
assert_eq!(loaded.compilation_rate, 75.0);
assert_eq!(loaded.examples.len(), 1);
}
#[test]
fn test_applied_fix_serialization() {
let fix = AppliedFix {
iteration: 1,
error_code: "E0425".to_string(),
description: "Fixed undefined variable".to_string(),
file_modified: PathBuf::from("src/lib.rs"),
commit_hash: Some("abc123".to_string()),
verified: true,
};
let json = serde_json::to_string(&fix).unwrap();
let parsed: AppliedFix = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.iteration, 1);
assert_eq!(parsed.error_code, "E0425");
assert!(parsed.verified);
}
#[test]
fn test_applied_fix_without_commit_hash() {
let fix = AppliedFix {
iteration: 5,
error_code: "E0308".to_string(),
description: "Fixed type mismatch".to_string(),
file_modified: PathBuf::from("type_mapper.rs"),
commit_hash: None,
verified: false,
};
let json = serde_json::to_string(&fix).unwrap();
let parsed: AppliedFix = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.iteration, 5);
assert!(parsed.commit_hash.is_none());
assert!(!parsed.verified);
}
#[test]
fn test_example_state_clone() {
let state = ExampleState::new(PathBuf::from("test.py"), true);
let cloned = state.clone();
assert_eq!(state.path, cloned.path);
assert_eq!(state.compiles, cloned.compiles);
}
#[test]
fn test_example_state_with_errors() {
let mut state = ExampleState::new(PathBuf::from("error.py"), false);
state.errors = vec![
"E0599: no method".to_string(),
"E0308: type mismatch".to_string(),
];
state.last_compiled = Some(std::time::SystemTime::now());
let json = serde_json::to_string(&state).unwrap();
let parsed: ExampleState = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.errors.len(), 2);
assert!(!parsed.compiles);
assert!(parsed.last_compiled.is_some());
}
#[test]
fn test_example_state_debug() {
let state = ExampleState::new(PathBuf::from("test.py"), true);
let debug = format!("{:?}", state);
assert!(debug.contains("test.py"));
assert!(debug.contains("compiles"));
}
#[test]
fn test_convergence_state_update_examples() {
use super::super::compiler::{CompilationError, CompilationResult};
let config = test_config();
let mut state = ConvergenceState::new(config);
let results = vec![
CompilationResult {
source_file: PathBuf::from("a.py"),
success: true,
errors: vec![],
rust_file: Some(PathBuf::from("a.rs")),
},
CompilationResult {
source_file: PathBuf::from("b.py"),
success: false,
errors: vec![CompilationError {
code: "E0599".to_string(),
message: "no method".to_string(),
file: PathBuf::from("b.rs"),
line: 10,
column: 5,
..Default::default()
}],
rust_file: None,
},
];
state.update_examples(&results);
assert_eq!(state.examples.len(), 2);
assert!(state.examples[0].compiles);
assert!(!state.examples[1].compiles);
assert_eq!(state.examples[1].errors.len(), 1);
}
#[test]
fn test_convergence_state_clone() {
let config = test_config();
let mut state = ConvergenceState::new(config);
state.iteration = 10;
state.compilation_rate = 75.5;
let cloned = state.clone();
assert_eq!(state.iteration, cloned.iteration);
assert!((state.compilation_rate - cloned.compilation_rate).abs() < 0.001);
}
#[test]
fn test_convergence_state_debug() {
let config = test_config();
let state = ConvergenceState::new(config);
let debug = format!("{:?}", state);
assert!(debug.contains("iteration"));
assert!(debug.contains("compilation_rate"));
}
#[test]
fn test_convergence_config_clone() {
let config = test_config();
let cloned = config.clone();
assert_eq!(config.target_rate, cloned.target_rate);
assert_eq!(config.max_iterations, cloned.max_iterations);
}
#[test]
fn test_convergence_config_debug() {
let config = test_config();
let debug = format!("{:?}", config);
assert!(debug.contains("target_rate"));
assert!(debug.contains("max_iterations"));
}
#[test]
fn test_convergence_config_serialization() {
let config = test_config();
let json = serde_json::to_string(&config).unwrap();
let parsed: ConvergenceConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config.target_rate, parsed.target_rate);
assert_eq!(config.max_iterations, parsed.max_iterations);
}
#[test]
fn test_convergence_config_with_all_options() {
let config = ConvergenceConfig {
input_dir: PathBuf::from("/test"),
target_rate: 95.0,
max_iterations: 100,
auto_fix: true,
dry_run: true,
verbose: true,
fix_confidence_threshold: 0.9,
checkpoint_dir: Some(PathBuf::from("/checkpoints")),
parallel_jobs: 8,
display_mode: DisplayMode::Minimal,
oracle: true,
explain: true,
use_cache: false,
patch_transpiler: true,
apr_file: Some(PathBuf::from("/custom.apr")),
};
assert!(config.validate().is_ok());
}
#[test]
fn test_display_mode_serialization() {
let modes = vec![
DisplayMode::Rich,
DisplayMode::Minimal,
DisplayMode::Json,
DisplayMode::Silent,
];
for mode in modes {
let json = serde_json::to_string(&mode).unwrap();
let parsed: DisplayMode = serde_json::from_str(&json).unwrap();
assert_eq!(mode, parsed);
}
}
#[test]
fn test_display_mode_copy() {
let mode = DisplayMode::Minimal;
let copy = mode; assert_eq!(mode, copy);
}
#[test]
fn test_display_mode_debug() {
assert!(format!("{:?}", DisplayMode::Rich).contains("Rich"));
assert!(format!("{:?}", DisplayMode::Minimal).contains("Minimal"));
assert!(format!("{:?}", DisplayMode::Json).contains("Json"));
assert!(format!("{:?}", DisplayMode::Silent).contains("Silent"));
}
#[test]
fn test_convergence_state_checkpoint_load_nonexistent() {
let temp_dir = tempfile::tempdir().unwrap();
let result = ConvergenceState::load_checkpoint(temp_dir.path());
assert!(result.is_err());
}
#[test]
fn test_convergence_state_with_error_clusters() {
use super::super::clusterer::{ErrorCluster, RootCause};
let config = test_config();
let mut state = ConvergenceState::new(config);
state.error_clusters.push(ErrorCluster {
root_cause: RootCause::TranspilerGap {
gap_type: "type_inference".to_string(),
location: "type_mapper.rs".to_string(),
},
error_code: "E0308".to_string(),
examples_blocked: vec![PathBuf::from("a.py"), PathBuf::from("b.py")],
sample_errors: vec![],
fix_confidence: 0.85,
suggested_fix: None,
});
assert_eq!(state.error_clusters.len(), 1);
assert_eq!(state.error_clusters[0].examples_blocked.len(), 2);
}
#[test]
fn test_convergence_state_with_fixes() {
let config = test_config();
let mut state = ConvergenceState::new(config);
state.fixes_applied.push(AppliedFix {
iteration: 1,
error_code: "E0599".to_string(),
description: "Added missing method".to_string(),
file_modified: PathBuf::from("expr_gen.rs"),
commit_hash: Some("abc123".to_string()),
verified: true,
});
state.fixes_applied.push(AppliedFix {
iteration: 3,
error_code: "E0308".to_string(),
description: "Fixed type inference".to_string(),
file_modified: PathBuf::from("type_mapper.rs"),
commit_hash: None,
verified: false,
});
assert_eq!(state.fixes_applied.len(), 2);
let temp_dir = tempfile::tempdir().unwrap();
state.save_checkpoint(temp_dir.path()).unwrap();
let loaded = ConvergenceState::load_checkpoint(temp_dir.path()).unwrap();
assert_eq!(loaded.fixes_applied.len(), 2);
}
#[test]
fn test_convergence_config_edge_case_rates() {
let mut config = test_config();
config.target_rate = 0.0;
assert!(config.validate().is_ok());
config.target_rate = 100.0;
assert!(config.validate().is_ok());
config.target_rate = 80.0;
config.fix_confidence_threshold = 0.0;
assert!(config.validate().is_ok());
config.fix_confidence_threshold = 1.0;
assert!(config.validate().is_ok());
}
#[test]
fn test_default_use_cache() {
assert!(default_use_cache());
}
#[test]
fn test_applied_fix_clone() {
let fix = AppliedFix {
iteration: 1,
error_code: "E0599".to_string(),
description: "test".to_string(),
file_modified: PathBuf::from("test.rs"),
commit_hash: None,
verified: true,
};
let cloned = fix.clone();
assert_eq!(fix.iteration, cloned.iteration);
assert_eq!(fix.verified, cloned.verified);
}
#[test]
fn test_applied_fix_debug() {
let fix = AppliedFix {
iteration: 1,
error_code: "E0599".to_string(),
description: "test fix".to_string(),
file_modified: PathBuf::from("test.rs"),
commit_hash: Some("abc".to_string()),
verified: true,
};
let debug = format!("{:?}", fix);
assert!(debug.contains("iteration"));
assert!(debug.contains("E0599"));
}
}