chore: sync project state and current artifacts

This commit is contained in:
Sepehr
2026-02-22 23:27:31 +01:00
parent 1b6415776e
commit dd77089b22
232 changed files with 37056 additions and 4296 deletions

View File

@@ -0,0 +1,127 @@
//! Tests for batch execution.
use entropyk_cli::batch::{discover_config_files, BatchSummary};
use entropyk_cli::run::{SimulationResult, SimulationStatus};
use std::path::PathBuf;
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,
error: None,
elapsed_ms: 50,
},
SimulationResult {
input: "fail.json".to_string(),
status: SimulationStatus::Error,
convergence: None,
iterations: None,
state: 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,
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);
}

View File

@@ -0,0 +1,170 @@
//! Tests for configuration parsing.
use entropyk_cli::config::{ComponentConfig, ScenarioConfig, SolverConfig};
use entropyk_cli::error::CliError;
use std::path::PathBuf;
use tempfile::tempdir;
#[test]
fn test_parse_minimal_config() {
let json = r#"{ "fluid": "R134a" }"#;
let config = ScenarioConfig::from_json(json).unwrap();
assert_eq!(config.fluid, "R134a");
assert!(config.circuits.is_empty());
assert_eq!(config.solver.strategy, "fallback");
}
#[test]
fn test_parse_full_config() {
let json = r#"
{
"fluid": "R410A",
"circuits": [{
"id": 0,
"components": [
{ "type": "Condenser", "name": "cond1", "ua": 5000.0 },
{ "type": "Evaporator", "name": "evap1", "ua": 4000.0 }
],
"edges": [
{ "from": "cond1:outlet", "to": "evap1:inlet" }
]
}],
"solver": {
"strategy": "newton",
"max_iterations": 50,
"tolerance": 1e-8
}
}"#;
let config = ScenarioConfig::from_json(json).unwrap();
assert_eq!(config.fluid, "R410A");
assert_eq!(config.circuits.len(), 1);
assert_eq!(config.circuits[0].components.len(), 2);
assert_eq!(config.solver.strategy, "newton");
assert_eq!(config.solver.max_iterations, 50);
}
#[test]
fn test_validate_missing_fluid() {
let json = r#"{ "fluid": "" }"#;
let result = ScenarioConfig::from_json(json);
assert!(result.is_err());
if let Err(CliError::Config(msg)) = result {
assert!(msg.contains("fluid"));
}
}
#[test]
fn test_validate_empty_circuit() {
let json = r#"
{
"fluid": "R134a",
"circuits": [{
"id": 0,
"components": []
}]
}"#;
let result = ScenarioConfig::from_json(json);
assert!(result.is_err());
}
#[test]
fn test_validate_invalid_edge_format() {
let json = r#"
{
"fluid": "R134a",
"circuits": [{
"id": 0,
"components": [{ "type": "Condenser", "name": "cond1", "ua": 5000.0 }],
"edges": [{ "from": "invalid", "to": "also_invalid" }]
}]
}"#;
let result = ScenarioConfig::from_json(json);
assert!(result.is_err());
if let Err(CliError::Config(msg)) = result {
assert!(msg.contains("edge format"));
}
}
#[test]
fn test_load_config_from_file() {
let dir = tempdir().unwrap();
let config_path = dir.path().join("test.json");
let json = r#"{ "fluid": "R744" }"#;
std::fs::write(&config_path, json).unwrap();
let config = ScenarioConfig::from_file(&config_path).unwrap();
assert_eq!(config.fluid, "R744");
}
#[test]
fn test_load_config_file_not_found() {
let result = ScenarioConfig::from_file(PathBuf::from("/nonexistent/path.json").as_path());
assert!(result.is_err());
if let Err(CliError::ConfigNotFound(path)) = result {
assert!(path.to_str().unwrap().contains("nonexistent"));
}
}
#[test]
fn test_solver_config_defaults() {
let config = SolverConfig::default();
assert_eq!(config.strategy, "fallback");
assert_eq!(config.max_iterations, 100);
assert_eq!(config.tolerance, 1e-6);
assert_eq!(config.timeout_ms, 0);
assert!(!config.verbose);
}
#[test]
fn test_component_config_params() {
let json = r#"
{
"type": "Evaporator",
"name": "evap1",
"ua": 4000.0,
"t_sat_k": 278.15,
"superheat_k": 5.0
}"#;
let component: ComponentConfig = serde_json::from_str(json).unwrap();
assert_eq!(component.component_type, "Evaporator");
assert_eq!(component.name, "evap1");
assert_eq!(
component.params.get("ua").unwrap().as_f64().unwrap(),
4000.0
);
assert_eq!(
component.params.get("t_sat_k").unwrap().as_f64().unwrap(),
278.15
);
assert_eq!(
component
.params
.get("superheat_k")
.unwrap()
.as_f64()
.unwrap(),
5.0
);
}
#[test]
fn test_validate_edge_unknown_component() {
let json = r#"
{
"fluid": "R134a",
"circuits": [{
"id": 0,
"components": [{ "type": "Condenser", "name": "cond1", "ua": 5000.0 }],
"edges": [{ "from": "cond1:outlet", "to": "nonexistent:inlet" }]
}]
}"#;
let result = ScenarioConfig::from_json(json);
assert!(result.is_err());
if let Err(CliError::Config(msg)) = result {
assert!(msg.contains("unknown component"));
assert!(msg.contains("nonexistent"));
}
}

View File

@@ -0,0 +1,77 @@
//! Tests for single simulation execution.
use entropyk_cli::error::ExitCode;
use entropyk_cli::run::{SimulationResult, SimulationStatus};
use tempfile::tempdir;
#[test]
fn test_simulation_result_serialization() {
let result = SimulationResult {
input: "test.json".to_string(),
status: SimulationStatus::Converged,
convergence: Some(entropyk_cli::run::ConvergenceInfo {
final_residual: 1e-8,
tolerance: 1e-6,
}),
iterations: Some(25),
state: Some(vec![entropyk_cli::run::StateEntry {
edge: 0,
pressure_bar: 10.0,
enthalpy_kj_kg: 400.0,
}]),
error: None,
elapsed_ms: 50,
};
let json = serde_json::to_string_pretty(&result).unwrap();
assert!(json.contains("\"status\": \"converged\""));
assert!(json.contains("\"iterations\": 25"));
assert!(json.contains("\"pressure_bar\": 10.0"));
}
#[test]
fn test_simulation_status_values() {
assert_eq!(SimulationStatus::Converged, SimulationStatus::Converged);
assert_ne!(SimulationStatus::Converged, SimulationStatus::Error);
let status = SimulationStatus::NonConverged;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, "\"non_converged\"");
}
#[test]
fn test_exit_codes() {
assert_eq!(ExitCode::Success as i32, 0);
assert_eq!(ExitCode::SimulationError as i32, 1);
assert_eq!(ExitCode::ConfigError as i32, 2);
assert_eq!(ExitCode::IoError as i32, 3);
}
#[test]
fn test_error_result_serialization() {
let result = SimulationResult {
input: "invalid.json".to_string(),
status: SimulationStatus::Error,
convergence: None,
iterations: None,
state: None,
error: Some("Configuration error".to_string()),
elapsed_ms: 0,
};
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("Configuration error"));
}
#[test]
fn test_create_minimal_config_file() {
let dir = tempdir().unwrap();
let config_path = dir.path().join("minimal.json");
let json = r#"{ "fluid": "R134a" }"#;
std::fs::write(&config_path, json).unwrap();
assert!(config_path.exists());
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(content.contains("R134a"));
}