shape_vm/feature_tests/
parity_runner.rs1use super::FeatureTest;
6use super::backends::BackendExecutor;
7use super::parity::{ExecutionResult, ParityResult, ParityStatus};
8
9pub struct ParityRunner {
11 interpreter: Box<dyn BackendExecutor>,
12 vm: Box<dyn BackendExecutor>,
13 jit: Option<Box<dyn BackendExecutor>>,
14}
15
16impl ParityRunner {
17 pub fn new(
19 interpreter: Box<dyn BackendExecutor>,
20 vm: Box<dyn BackendExecutor>,
21 jit: Option<Box<dyn BackendExecutor>>,
22 ) -> Self {
23 Self {
24 interpreter,
25 vm,
26 jit,
27 }
28 }
29
30 pub fn with_defaults() -> Self {
32 use super::backends::{InterpreterBackend, JITBackend, VMBackend};
33 Self {
34 interpreter: Box::new(InterpreterBackend),
35 vm: Box::new(VMBackend),
36 jit: Some(Box::new(JITBackend)),
37 }
38 }
39
40 pub fn run_test(&self, test: &FeatureTest) -> ParityResult {
42 let interpreter = if self.interpreter.is_available() {
43 self.interpreter.execute(test)
44 } else {
45 ExecutionResult::Skipped("Interpreter not available")
46 };
47
48 let vm = if self.vm.is_available() {
49 self.vm.execute(test)
50 } else {
51 ExecutionResult::Skipped("VM not available")
52 };
53
54 let jit = match &self.jit {
55 Some(jit_backend) if jit_backend.is_available() => jit_backend.execute(test),
56 Some(_) => ExecutionResult::Skipped("JIT not available"),
57 None => ExecutionResult::Skipped("JIT backend not configured"),
58 };
59
60 ParityResult {
61 test_name: test.name,
62 interpreter,
63 vm,
64 jit,
65 }
66 }
67
68 pub fn run_all(&self, tests: &[&FeatureTest]) -> ParityReport {
70 let mut results = Vec::with_capacity(tests.len());
71
72 for test in tests {
73 results.push(self.run_test(test));
74 }
75
76 ParityReport::from_results(results)
77 }
78}
79
80#[derive(Debug)]
82pub struct ParityReport {
83 pub results: Vec<ParityResult>,
84 pub total: usize,
85 pub passed: usize,
86 pub failed: usize,
87 pub partial: usize,
88}
89
90impl ParityReport {
91 pub fn from_results(results: Vec<ParityResult>) -> Self {
93 let total = results.len();
94 let mut passed = 0;
95 let mut failed = 0;
96 let mut partial = 0;
97
98 for result in &results {
99 match result.parity_status() {
100 ParityStatus::AllMatch => passed += 1,
101 ParityStatus::PartialSkipped { .. } => partial += 1,
102 ParityStatus::AllFailed => partial += 1, _ => failed += 1,
104 }
105 }
106
107 Self {
108 results,
109 total,
110 passed,
111 failed,
112 partial,
113 }
114 }
115
116 pub fn all_passed(&self) -> bool {
118 self.failed == 0
119 }
120
121 pub fn failures(&self) -> Vec<&ParityResult> {
123 self.results.iter().filter(|r| !r.is_passing()).collect()
124 }
125
126 pub fn format_text(&self) -> String {
128 let mut output = String::new();
129
130 output.push_str("═══════════════════════════════════════════════════════════════\n");
131 output.push_str(" PARITY TEST REPORT\n");
132 output.push_str("═══════════════════════════════════════════════════════════════\n\n");
133
134 output.push_str(&format!("Total tests: {}\n", self.total));
135 output.push_str(&format!(" ✓ Passed (all match): {}\n", self.passed));
136 output.push_str(&format!(" ~ Partial (some skipped): {}\n", self.partial));
137 output.push_str(&format!(" ✗ Failed (mismatch): {}\n", self.failed));
138 output.push('\n');
139
140 if self.failed > 0 {
141 output.push_str("───────────────────────────────────────────────────────────────\n");
142 output.push_str(" FAILURES\n");
143 output.push_str("───────────────────────────────────────────────────────────────\n\n");
144
145 for result in self.failures() {
146 output.push_str(&format!("Test: {}\n", result.test_name));
147 output.push_str(&format!(" {}\n\n", result.format_diff()));
148 }
149 }
150
151 output.push_str("═══════════════════════════════════════════════════════════════\n");
152
153 if self.all_passed() {
154 output.push_str(" ALL TESTS PASSED\n");
155 } else {
156 output.push_str(&format!(" {} FAILURES\n", self.failed));
157 }
158
159 output.push_str("═══════════════════════════════════════════════════════════════\n");
160
161 output
162 }
163
164 pub fn format_json(&self) -> String {
166 let json = serde_json::json!({
167 "total": self.total,
168 "passed": self.passed,
169 "partial": self.partial,
170 "failed": self.failed,
171 "results": self.results.iter().map(|r| {
172 serde_json::json!({
173 "name": r.test_name,
174 "passing": r.is_passing(),
175 "status": format!("{:?}", r.parity_status()),
176 })
177 }).collect::<Vec<_>>(),
178 });
179 serde_json::to_string_pretty(&json).unwrap_or_default()
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_report_from_results() {
189 let results = vec![
190 ParityResult {
191 test_name: "test1",
192 interpreter: ExecutionResult::Success("1".to_string()),
193 vm: ExecutionResult::Success("1".to_string()),
194 jit: ExecutionResult::Success("1".to_string()),
195 },
196 ParityResult {
197 test_name: "test2",
198 interpreter: ExecutionResult::Success("2".to_string()),
199 vm: ExecutionResult::Success("3".to_string()), jit: ExecutionResult::Success("2".to_string()),
201 },
202 ];
203
204 let report = ParityReport::from_results(results);
205 assert_eq!(report.total, 2);
206 assert_eq!(report.passed, 1);
207 assert_eq!(report.failed, 1);
208 assert!(!report.all_passed());
209 }
210
211 #[test]
212 fn test_report_format_text() {
213 let results = vec![ParityResult {
214 test_name: "simple_add",
215 interpreter: ExecutionResult::Success("42".to_string()),
216 vm: ExecutionResult::Success("42".to_string()),
217 jit: ExecutionResult::Success("42".to_string()),
218 }];
219
220 let report = ParityReport::from_results(results);
221 let text = report.format_text();
222 assert!(text.contains("ALL TESTS PASSED"));
223 assert!(text.contains("Total tests: 1"));
224 }
225}