use super::FeatureTest;
use super::backends::BackendExecutor;
use super::parity::{ExecutionResult, ParityResult, ParityStatus};
pub struct ParityRunner {
interpreter: Box<dyn BackendExecutor>,
vm: Box<dyn BackendExecutor>,
jit: Option<Box<dyn BackendExecutor>>,
}
impl ParityRunner {
pub fn new(
interpreter: Box<dyn BackendExecutor>,
vm: Box<dyn BackendExecutor>,
jit: Option<Box<dyn BackendExecutor>>,
) -> Self {
Self {
interpreter,
vm,
jit,
}
}
pub fn with_defaults() -> Self {
use super::backends::{InterpreterBackend, JITBackend, VMBackend};
Self {
interpreter: Box::new(InterpreterBackend),
vm: Box::new(VMBackend),
jit: Some(Box::new(JITBackend)),
}
}
pub fn run_test(&self, test: &FeatureTest) -> ParityResult {
let interpreter = if self.interpreter.is_available() {
self.interpreter.execute(test)
} else {
ExecutionResult::Skipped("Interpreter not available")
};
let vm = if self.vm.is_available() {
self.vm.execute(test)
} else {
ExecutionResult::Skipped("VM not available")
};
let jit = match &self.jit {
Some(jit_backend) if jit_backend.is_available() => jit_backend.execute(test),
Some(_) => ExecutionResult::Skipped("JIT not available"),
None => ExecutionResult::Skipped("JIT backend not configured"),
};
ParityResult {
test_name: test.name,
interpreter,
vm,
jit,
}
}
pub fn run_all(&self, tests: &[&FeatureTest]) -> ParityReport {
let mut results = Vec::with_capacity(tests.len());
for test in tests {
results.push(self.run_test(test));
}
ParityReport::from_results(results)
}
}
#[derive(Debug)]
pub struct ParityReport {
pub results: Vec<ParityResult>,
pub total: usize,
pub passed: usize,
pub failed: usize,
pub partial: usize,
}
impl ParityReport {
pub fn from_results(results: Vec<ParityResult>) -> Self {
let total = results.len();
let mut passed = 0;
let mut failed = 0;
let mut partial = 0;
for result in &results {
match result.parity_status() {
ParityStatus::AllMatch => passed += 1,
ParityStatus::PartialSkipped { .. } => partial += 1,
ParityStatus::AllFailed => partial += 1, _ => failed += 1,
}
}
Self {
results,
total,
passed,
failed,
partial,
}
}
pub fn all_passed(&self) -> bool {
self.failed == 0
}
pub fn failures(&self) -> Vec<&ParityResult> {
self.results.iter().filter(|r| !r.is_passing()).collect()
}
pub fn format_text(&self) -> String {
let mut output = String::new();
output.push_str("═══════════════════════════════════════════════════════════════\n");
output.push_str(" PARITY TEST REPORT\n");
output.push_str("═══════════════════════════════════════════════════════════════\n\n");
output.push_str(&format!("Total tests: {}\n", self.total));
output.push_str(&format!(" ✓ Passed (all match): {}\n", self.passed));
output.push_str(&format!(" ~ Partial (some skipped): {}\n", self.partial));
output.push_str(&format!(" ✗ Failed (mismatch): {}\n", self.failed));
output.push('\n');
if self.failed > 0 {
output.push_str("───────────────────────────────────────────────────────────────\n");
output.push_str(" FAILURES\n");
output.push_str("───────────────────────────────────────────────────────────────\n\n");
for result in self.failures() {
output.push_str(&format!("Test: {}\n", result.test_name));
output.push_str(&format!(" {}\n\n", result.format_diff()));
}
}
output.push_str("═══════════════════════════════════════════════════════════════\n");
if self.all_passed() {
output.push_str(" ALL TESTS PASSED\n");
} else {
output.push_str(&format!(" {} FAILURES\n", self.failed));
}
output.push_str("═══════════════════════════════════════════════════════════════\n");
output
}
pub fn format_json(&self) -> String {
let json = serde_json::json!({
"total": self.total,
"passed": self.passed,
"partial": self.partial,
"failed": self.failed,
"results": self.results.iter().map(|r| {
serde_json::json!({
"name": r.test_name,
"passing": r.is_passing(),
"status": format!("{:?}", r.parity_status()),
})
}).collect::<Vec<_>>(),
});
serde_json::to_string_pretty(&json).unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_report_from_results() {
let results = vec![
ParityResult {
test_name: "test1",
interpreter: ExecutionResult::Success("1".to_string()),
vm: ExecutionResult::Success("1".to_string()),
jit: ExecutionResult::Success("1".to_string()),
},
ParityResult {
test_name: "test2",
interpreter: ExecutionResult::Success("2".to_string()),
vm: ExecutionResult::Success("3".to_string()), jit: ExecutionResult::Success("2".to_string()),
},
];
let report = ParityReport::from_results(results);
assert_eq!(report.total, 2);
assert_eq!(report.passed, 1);
assert_eq!(report.failed, 1);
assert!(!report.all_passed());
}
#[test]
fn test_report_format_text() {
let results = vec![ParityResult {
test_name: "simple_add",
interpreter: ExecutionResult::Success("42".to_string()),
vm: ExecutionResult::Success("42".to_string()),
jit: ExecutionResult::Success("42".to_string()),
}];
let report = ParityReport::from_results(results);
let text = report.format_text();
assert!(text.contains("ALL TESTS PASSED"));
assert!(text.contains("Total tests: 1"));
}
}