use serde::Deserialize; use std::collections::HashMap; use std::fs; use std::sync::{Arc, OnceLock, RwLock}; #[derive(Debug, Deserialize)] pub struct TestSuite { #[allow(dead_code)] pub description: String, pub database: serde_json::Value, pub tests: Vec, } use crate::tests::types::TestCase; use serde_json::Value; pub fn deserialize_some<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { let v = Value::deserialize(deserializer)?; Ok(Some(v)) } // Type alias for easier reading type CompiledSuite = Arc)>>; // Global cache mapping filename -> Vector of (Parsed JSON suite, Compiled Database) static CACHE: OnceLock>> = OnceLock::new(); fn get_cached_file(path: &str) -> CompiledSuite { let cache_lock = CACHE.get_or_init(|| RwLock::new(HashMap::new())); let file_data = { let read_guard = cache_lock.read().unwrap(); read_guard.get(path).cloned() }; match file_data { Some(data) => data, None => { let mut write_guard = cache_lock.write().unwrap(); // double check in case another thread compiled while we waited for lock if let Some(data) = write_guard.get(path) { data.clone() } else { let content = fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read file: {}", path)); let suites: Vec = serde_json::from_str(&content) .unwrap_or_else(|e| panic!("Failed to parse JSON in {}: {}", path, e)); let mut compiled_suites = Vec::new(); for suite in suites { let db_result = crate::database::Database::new(&suite.database); if let Err(drop) = db_result { let error_messages: Vec = drop .errors .into_iter() .map(|e| format!("Error {} at path {}: {}", e.code, e.details.path, e.message)) .collect(); panic!( "System Setup Compilation failed for {}:\n{}", path, error_messages.join("\n") ); } compiled_suites.push((suite, Arc::new(db_result.unwrap()))); } let new_data = Arc::new(compiled_suites); write_guard.insert(path.to_string(), new_data.clone()); new_data } } } } pub fn run_test_case(path: &str, suite_idx: usize, case_idx: usize) -> Result<(), String> { let file_data = get_cached_file(path); if suite_idx >= file_data.len() { panic!("Suite Index {} out of bounds for file {}", suite_idx, path); } let (group, db) = &file_data[suite_idx]; if case_idx >= group.tests.len() { panic!( "Case Index {} out of bounds for suite {} in file {}", case_idx, suite_idx, path ); } let test = &group.tests[case_idx]; let mut failures = Vec::::new(); // 4. Run Tests match test.action.as_str() { "validate" => { let result = test.run_validate(db.clone()); if let Err(e) = result { println!("TEST VALIDATE ERROR FOR '{}': {}", test.description, e); failures.push(format!( "[{}] Validate Test '{}' failed. Error: {}", group.description, test.description, e )); } } "merge" => { let result = test.run_merge(db.clone()); if let Err(e) = result { println!("TEST MERGE ERROR FOR '{}': {}", test.description, e); failures.push(format!( "[{}] Merge Test '{}' failed. Error: {}", group.description, test.description, e )); } } "query" => { let result = test.run_query(db.clone()); if let Err(e) = result { println!("TEST QUERY ERROR FOR '{}': {}", test.description, e); failures.push(format!( "[{}] Query Test '{}' failed. Error: {}", group.description, test.description, e )); } } _ => { failures.push(format!( "[{}] Unknown action '{}' for test '{}'", group.description, test.action, test.description )); } } if !failures.is_empty() { return Err(failures.join("\n")); } Ok(()) }