//! Tests for batch execution. use entropyk_cli::batch::{discover_config_files, BatchAggregator, BatchSummary, OutputFormat}; use entropyk_cli::run::{SimulationResult, SimulationStatus}; use std::path::PathBuf; use std::str::FromStr; use tempfile::tempdir; #[test] fn test_discover_config_files() { let dir = tempdir().unwrap(); std::fs::write(dir.path().join("config1.json"), "{}").unwrap(); std::fs::write(dir.path().join("config2.json"), "{}").unwrap(); std::fs::write(dir.path().join("config3.json"), "{}").unwrap(); std::fs::write(dir.path().join("readme.txt"), "").unwrap(); std::fs::write(dir.path().join("data.csv"), "a,b,c").unwrap(); let files = discover_config_files(dir.path()).unwrap(); assert_eq!(files.len(), 3); let names: Vec = files .iter() .map(|p: &PathBuf| p.file_name().unwrap().to_string_lossy().to_string()) .collect(); assert!(names.contains(&"config1.json".to_string())); assert!(names.contains(&"config2.json".to_string())); assert!(names.contains(&"config3.json".to_string())); } #[test] fn test_discover_config_files_sorted() { let dir = tempdir().unwrap(); std::fs::write(dir.path().join("zebra.json"), "{}").unwrap(); std::fs::write(dir.path().join("alpha.json"), "{}").unwrap(); std::fs::write(dir.path().join("middle.json"), "{}").unwrap(); let files = discover_config_files(dir.path()).unwrap(); assert_eq!(files.len(), 3); assert!(files[0].ends_with("alpha.json")); assert!(files[1].ends_with("middle.json")); assert!(files[2].ends_with("zebra.json")); } #[test] fn test_discover_empty_directory() { let dir = tempdir().unwrap(); let files = discover_config_files(dir.path()).unwrap(); assert!(files.is_empty()); } #[test] fn test_batch_summary_serialization() { let summary = BatchSummary { total: 100, succeeded: 95, failed: 3, non_converged: 2, total_elapsed_ms: 5000, avg_elapsed_ms: 50.0, results: vec![], }; let json = serde_json::to_string_pretty(&summary).unwrap(); assert!(json.contains("\"total\": 100")); assert!(json.contains("\"succeeded\": 95")); assert!(json.contains("\"avg_elapsed_ms\": 50.0")); } #[test] fn test_batch_summary_default() { let summary = BatchSummary::default(); assert_eq!(summary.total, 0); assert_eq!(summary.succeeded, 0); assert_eq!(summary.failed, 0); assert!(summary.results.is_empty()); } #[test] fn test_simulation_result_statuses() { let results = vec![ SimulationResult { input: "ok.json".to_string(), status: SimulationStatus::Converged, convergence: None, iterations: Some(10), state: None, performance: None, error: None, elapsed_ms: 50, }, SimulationResult { input: "fail.json".to_string(), status: SimulationStatus::Error, convergence: None, iterations: None, state: None, performance: None, error: Some("Error".to_string()), elapsed_ms: 0, }, SimulationResult { input: "timeout.json".to_string(), status: SimulationStatus::Timeout, convergence: None, iterations: Some(100), state: None, performance: None, error: None, elapsed_ms: 1000, }, ]; let converged_count = results .iter() .filter(|r| r.status == SimulationStatus::Converged) .count(); let error_count = results .iter() .filter(|r| r.status == SimulationStatus::Error) .count(); let timeout_count = results .iter() .filter(|r| r.status == SimulationStatus::Timeout) .count(); assert_eq!(converged_count, 1); assert_eq!(error_count, 1); assert_eq!(timeout_count, 1); } #[test] fn test_batch_aggregator_csv_output() { let results = vec![ SimulationResult { input: "scenario1.json".to_string(), status: SimulationStatus::Converged, convergence: Some(entropyk_cli::run::ConvergenceInfo { final_residual: 1e-8, tolerance: 1e-6, }), iterations: Some(25), state: None, performance: None, error: None, elapsed_ms: 150, }, SimulationResult { input: "scenario2.json".to_string(), status: SimulationStatus::Converged, convergence: Some(entropyk_cli::run::ConvergenceInfo { final_residual: 5e-7, tolerance: 1e-6, }), iterations: Some(30), state: None, performance: None, error: None, elapsed_ms: 200, }, SimulationResult { input: "scenario3.json".to_string(), status: SimulationStatus::Error, convergence: None, iterations: None, state: None, performance: None, error: Some("Solver failed".to_string()), elapsed_ms: 0, }, ]; let aggregator = BatchAggregator::new(results); let csv = aggregator.to_csv(); let lines: Vec<&str> = csv.lines().collect(); // Header + 3 data lines assert_eq!(lines.len(), 4); assert!(lines[0].contains("input,status,iterations,converged")); assert!(lines[1].contains("scenario1.json")); assert!(lines[1].contains("converged")); assert!(lines[1].contains("true")); assert!(lines[3].contains("error")); } #[test] fn test_batch_aggregator_json_summary() { let results = vec![ SimulationResult { input: "test1.json".to_string(), status: SimulationStatus::Converged, convergence: None, iterations: Some(10), state: None, performance: None, error: None, elapsed_ms: 50, }, SimulationResult { input: "test2.json".to_string(), status: SimulationStatus::Converged, convergence: None, iterations: Some(15), state: None, performance: None, error: None, elapsed_ms: 75, }, SimulationResult { input: "test3.json".to_string(), status: SimulationStatus::Timeout, convergence: None, iterations: Some(100), state: None, performance: None, error: None, elapsed_ms: 5000, }, ]; let aggregator = BatchAggregator::new(results); let summary = aggregator.summary(); assert_eq!(summary.total, 3); assert_eq!(summary.succeeded, 2); assert_eq!(summary.failed, 0); assert_eq!(summary.non_converged, 1); } #[test] fn test_output_format_parsing() { assert_eq!(OutputFormat::from_str("json").unwrap(), OutputFormat::Json); assert_eq!(OutputFormat::from_str("JSON").unwrap(), OutputFormat::Json); assert_eq!(OutputFormat::from_str("csv").unwrap(), OutputFormat::Csv); assert_eq!(OutputFormat::from_str("CSV").unwrap(), OutputFormat::Csv); assert!(OutputFormat::from_str("xml").is_err()); assert!(OutputFormat::from_str("invalid").is_err()); } #[test] fn test_batch_summary_json_serialization_roundtrip() { let summary = BatchSummary { total: 20, succeeded: 18, failed: 1, non_converged: 1, total_elapsed_ms: 5000, avg_elapsed_ms: 250.0, results: vec![], }; let json = summary.to_json().unwrap(); let deserialized: BatchSummary = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.total, 20); assert_eq!(deserialized.succeeded, 18); assert_eq!(deserialized.failed, 1); assert_eq!(deserialized.non_converged, 1); assert_eq!(deserialized.total_elapsed_ms, 5000); } #[test] fn test_batch_summary_csv_with_convergence() { let results = vec![SimulationResult { input: "config1.json".to_string(), status: SimulationStatus::Converged, convergence: Some(entropyk_cli::run::ConvergenceInfo { final_residual: 1e-9, tolerance: 1e-6, }), iterations: Some(42), state: None, performance: None, error: None, elapsed_ms: 300, }]; let summary = BatchSummary { total: 1, succeeded: 1, failed: 0, non_converged: 0, total_elapsed_ms: 300, avg_elapsed_ms: 300.0, results, }; let csv = summary.to_csv(); assert!(csv.contains("config1.json")); assert!(csv.contains("converged")); assert!(csv.contains("42")); assert!(csv.contains("1.00e-9")); assert!(csv.contains("300")); }