1use std::time::{Duration, Instant};
4
5#[derive(Debug, Clone)]
7pub struct FailureDiagnostic {
8 pub category: &'static str,
9 pub test_name: &'static str,
10 pub input_size: usize,
11 pub input_sample: String,
12 pub expected_sample: String,
13 pub actual_sample: String,
14 pub first_diff_index: Option<usize>,
15 pub diff_count: usize,
16 pub error_message: String,
17}
18
19impl FailureDiagnostic {
20 pub fn new(
22 category: &'static str,
23 test_name: &'static str,
24 input_size: usize,
25 error_message: String,
26 ) -> Self {
27 Self {
28 category,
29 test_name,
30 input_size,
31 input_sample: String::new(),
32 expected_sample: String::new(),
33 actual_sample: String::new(),
34 first_diff_index: None,
35 diff_count: 0,
36 error_message,
37 }
38 }
39
40 pub fn with_input_sample(mut self, sample: String) -> Self {
42 self.input_sample = sample;
43 self
44 }
45
46 pub fn with_comparison(
48 mut self,
49 expected: String,
50 actual: String,
51 first_diff: Option<usize>,
52 diff_count: usize,
53 ) -> Self {
54 self.expected_sample = expected;
55 self.actual_sample = actual;
56 self.first_diff_index = first_diff;
57 self.diff_count = diff_count;
58 self
59 }
60
61 pub fn report(&self) -> String {
63 let mut report = String::new();
64 report.push_str(&format!(
65 "=== FAILURE: {}/{} ===\n",
66 self.category, self.test_name
67 ));
68 report.push_str(&format!("Input size: {}\n", self.input_size));
69 report.push_str(&format!("Error: {}\n", self.error_message));
70
71 if !self.input_sample.is_empty() {
72 report.push_str(&format!("Input sample: {}\n", self.input_sample));
73 }
74
75 if self.diff_count > 0 {
76 report.push_str(&format!("Differences: {} total\n", self.diff_count));
77 if let Some(idx) = self.first_diff_index {
78 report.push_str(&format!("First diff at index: {}\n", idx));
79 }
80 report.push_str(&format!("Expected: {}\n", self.expected_sample));
81 report.push_str(&format!("Actual: {}\n", self.actual_sample));
82 }
83
84 report.push_str("===\n");
85 report
86 }
87}
88
89#[derive(Debug, Clone)]
91pub enum TestStatus {
92 Passed,
93 Failed,
94 Skipped { reason: &'static str },
95 Error { message: String },
96}
97
98impl TestStatus {
99 pub fn is_passed(&self) -> bool {
100 matches!(self, TestStatus::Passed)
101 }
102
103 pub fn is_failed(&self) -> bool {
104 matches!(self, TestStatus::Failed | TestStatus::Error { .. })
105 }
106}
107
108#[derive(Debug, Clone)]
110pub struct TestResult {
111 pub name: &'static str,
112 pub status: TestStatus,
113 pub duration: Duration,
114 pub diagnostic: Option<FailureDiagnostic>,
115}
116
117impl TestResult {
118 pub fn passed(name: &'static str, duration: Duration) -> Self {
119 Self {
120 name,
121 status: TestStatus::Passed,
122 duration,
123 diagnostic: None,
124 }
125 }
126
127 pub fn failed(name: &'static str, duration: Duration, diagnostic: FailureDiagnostic) -> Self {
128 Self {
129 name,
130 status: TestStatus::Failed,
131 duration,
132 diagnostic: Some(diagnostic),
133 }
134 }
135
136 pub fn skipped(name: &'static str, reason: &'static str) -> Self {
137 Self {
138 name,
139 status: TestStatus::Skipped { reason },
140 duration: Duration::ZERO,
141 diagnostic: None,
142 }
143 }
144
145 pub fn error(name: &'static str, duration: Duration, message: String) -> Self {
146 Self {
147 name,
148 status: TestStatus::Error { message },
149 duration,
150 diagnostic: None,
151 }
152 }
153}
154
155#[derive(Debug, Clone)]
157pub struct CategoryResult {
158 pub name: &'static str,
159 pub tests: Vec<TestResult>,
160 pub duration: Duration,
161}
162
163impl CategoryResult {
164 pub fn new(name: &'static str) -> Self {
165 Self {
166 name,
167 tests: Vec::new(),
168 duration: Duration::ZERO,
169 }
170 }
171
172 pub fn add_result(&mut self, result: TestResult) {
173 self.tests.push(result);
174 }
175
176 pub fn set_duration(&mut self, duration: Duration) {
177 self.duration = duration;
178 }
179
180 pub fn passed_count(&self) -> usize {
181 self.tests.iter().filter(|t| t.status.is_passed()).count()
182 }
183
184 pub fn failed_count(&self) -> usize {
185 self.tests.iter().filter(|t| t.status.is_failed()).count()
186 }
187
188 pub fn skipped_count(&self) -> usize {
189 self.tests
190 .iter()
191 .filter(|t| matches!(t.status, TestStatus::Skipped { .. }))
192 .count()
193 }
194
195 pub fn total_count(&self) -> usize {
196 self.tests.len()
197 }
198
199 pub fn all_passed(&self) -> bool {
200 self.tests
201 .iter()
202 .all(|t| t.status.is_passed() || matches!(t.status, TestStatus::Skipped { .. }))
203 }
204}
205
206#[derive(Debug)]
208pub struct CertificationResults {
209 pub categories: Vec<CategoryResult>,
210 pub start_time: Instant,
211 pub total_duration: Duration,
212}
213
214impl CertificationResults {
215 pub fn new() -> Self {
216 Self {
217 categories: Vec::new(),
218 start_time: Instant::now(),
219 total_duration: Duration::ZERO,
220 }
221 }
222
223 pub fn run_category<F>(&mut self, _name: &'static str, f: F)
225 where
226 F: FnOnce() -> CategoryResult,
227 {
228 let start = Instant::now();
229 let mut result = f();
230 result.set_duration(start.elapsed());
231 self.categories.push(result);
232 }
233
234 pub fn add_category(&mut self, result: CategoryResult) {
236 self.categories.push(result);
237 }
238
239 pub fn finalize(&mut self) {
241 self.total_duration = self.start_time.elapsed();
242 }
243
244 pub fn total_tests(&self) -> usize {
245 self.categories.iter().map(|c| c.total_count()).sum()
246 }
247
248 pub fn total_passed(&self) -> usize {
249 self.categories.iter().map(|c| c.passed_count()).sum()
250 }
251
252 pub fn total_failed(&self) -> usize {
253 self.categories.iter().map(|c| c.failed_count()).sum()
254 }
255
256 pub fn total_skipped(&self) -> usize {
257 self.categories.iter().map(|c| c.skipped_count()).sum()
258 }
259
260 pub fn all_passed(&self) -> bool {
261 self.categories.iter().all(|c| c.all_passed())
262 }
263
264 pub fn print_summary(&self) {
266 println!("\n========== CERTIFICATION RESULTS ==========");
267 println!("Total Duration: {:.2}s", self.total_duration.as_secs_f64());
268 println!();
269
270 for cat in &self.categories {
271 let status = if cat.all_passed() { "PASS" } else { "FAIL" };
272 println!(
273 "[{}] {}: {}/{} passed, {} skipped ({:.2}s)",
274 status,
275 cat.name,
276 cat.passed_count(),
277 cat.total_count(),
278 cat.skipped_count(),
279 cat.duration.as_secs_f64()
280 );
281 }
282
283 println!();
284 println!("========== SUMMARY ==========");
285 println!("Total Tests: {}", self.total_tests());
286 println!("Passed: {}", self.total_passed());
287 println!("Failed: {}", self.total_failed());
288 println!("Skipped: {}", self.total_skipped());
289 println!(
290 "Pass Rate: {:.1}%",
291 (self.total_passed() as f64 / self.total_tests().max(1) as f64) * 100.0
292 );
293 println!("=====================================\n");
294 }
295
296 pub fn failure_report(&self) -> String {
298 let mut report = String::new();
299
300 for cat in &self.categories {
301 for test in &cat.tests {
302 if let Some(diag) = &test.diagnostic {
303 report.push_str(&diag.report());
304 report.push('\n');
305 } else if let TestStatus::Error { message } = &test.status {
306 report.push_str(&format!("=== ERROR: {}/{} ===\n", cat.name, test.name));
307 report.push_str(&format!("Error: {}\n", message));
308 report.push_str("===\n\n");
309 }
310 }
311 }
312
313 report
314 }
315}
316
317impl Default for CertificationResults {
318 fn default() -> Self {
319 Self::new()
320 }
321}
322
323#[macro_export]
325macro_rules! run_test {
326 ($results:expr, $name:expr, $body:expr) => {{
327 let start = std::time::Instant::now();
328 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| $body));
329 let duration = start.elapsed();
330
331 match result {
332 Ok(()) => $crate::harness::TestResult::passed($name, duration),
333 Err(e) => {
334 let message = if let Some(s) = e.downcast_ref::<&str>() {
335 s.to_string()
336 } else if let Some(s) = e.downcast_ref::<String>() {
337 s.clone()
338 } else {
339 "Unknown panic".to_string()
340 };
341 $crate::harness::TestResult::error($name, duration, message)
342 }
343 }
344 }};
345}