Files
Entropyk/crates/cli/tests/batch_execution.rs
Sepehr ab5dc7e568 chore: remove BMAD framework files and IDE configuration artifacts
Clean up unused BMAD workflow, agent, and command files across all IDE
configurations (.agent, .clinerules, .cursor, .gemini, .github, .kilocode,
.opencode) and internal module files (_bmad/bmb, _bmad/bmm).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-25 15:01:09 +02:00

296 lines
8.7 KiB
Rust

//! 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<String> = 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"));
}