Files
mlkem-sync/test_framework/lib/reporter.py
FallenSigh 8fdf944555 feat: init mlkem project with Verilator test framework
- sync_rtl/common/: skid_buffer, pipeline_reg, defines (valid/ready)
- sync_rtl/mod_add/: modular adder example with Verilator C++ TB
- test_framework/: Python-driven Verilator compile/sim/compare pipeline
- test_framework/modules/mod_add/: 50-vector test plan, full鏈路 PASS
- .trellis/spec/: RTL and test_framework conventions documented
2026-06-24 19:43:29 +08:00

131 lines
4.4 KiB
Python

"""reporter.py - Formats and displays test results."""
import os
import time
from datetime import datetime
class Reporter:
"""Formats test results for terminal and HTML output."""
# ANSI color codes
GREEN = '\033[92m'
RED = '\033[91m'
YELLOW = '\033[93m'
CYAN = '\033[96m'
RESET = '\033[0m'
BOLD = '\033[1m'
def __init__(self, report_dir: str = 'test_framework/reports'):
self.report_dir = report_dir
os.makedirs(report_dir, exist_ok=True)
def print_summary(self, results: dict, total_elapsed: float = 0.0) -> bool:
"""Print formatted test summary to terminal.
Args:
results: Dict of module_name -> module_results.
total_elapsed: Total elapsed time in seconds.
Returns:
bool: True if all tests passed.
"""
total_pass = 0
total_fail = 0
all_pass = True
print()
print(f"{self.BOLD}{'='*60}{self.RESET}")
print(f"{self.BOLD} TEST RESULTS{self.RESET}")
print(f"{self.BOLD}{'='*60}{self.RESET}")
for module_name, module_result in results.items():
status = module_result.get('status', 'UNKNOWN')
num_pass = module_result.get('pass', 0)
num_fail = module_result.get('fail', 0)
elapsed = module_result.get('elapsed', 0.0)
total_pass += num_pass
total_fail += num_fail
if status == 'PASS':
color = self.GREEN
icon = ''
elif status == 'FAIL':
color = self.RED
icon = ''
all_pass = False
else:
color = self.YELLOW
icon = '?'
all_pass = False
print(f" {color}{icon} {module_name}{self.RESET}: "
f"{num_pass} pass, {num_fail} fail, "
f"{elapsed:.2f}s")
print(f"{self.BOLD}{'='*60}{self.RESET}")
total = total_pass + total_fail
if all_pass:
print(f" {self.GREEN}{self.BOLD}ALL TESTS PASSED{self.RESET} "
f"({total_pass}/{total} vectors, {total_elapsed:.2f}s)")
else:
print(f" {self.RED}{self.BOLD}SOME TESTS FAILED{self.RESET} "
f"({total_pass}/{total} pass, {total_fail}/{total} fail, "
f"{total_elapsed:.2f}s)")
print()
# Write HTML report
self._write_html_report(results, total_elapsed)
return all_pass
def _write_html_report(self, results: dict, total_elapsed: float) -> None:
"""Write an HTML test report."""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
report_path = os.path.join(self.report_dir, f'report_{timestamp}.html')
total_pass = sum(r.get('pass', 0) for r in results.values())
total_fail = sum(r.get('fail', 0) for r in results.values())
all_pass = total_fail == 0
rows = ''
for module_name, module_result in results.items():
status = module_result.get('status', 'UNKNOWN')
color = '#4CAF50' if status == 'PASS' else '#F44336'
rows += f'''
<tr>
<td>{module_name}</td>
<td style="color:{color}">{status}</td>
<td>{module_result.get('pass', 0)}</td>
<td>{module_result.get('fail', 0)}</td>
<td>{module_result.get('elapsed', 0):.2f}s</td>
</tr>'''
html = f'''<!DOCTYPE html>
<html>
<head>
<title>ML-KEM Test Report</title>
<style>
body {{ font-family: monospace; background: #1e1e1e; color: #d4d4d4;
padding: 20px; }}
h1 {{ color: {'#4CAF50' if all_pass else '#F44336'}; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ border: 1px solid #444; padding: 8px; text-align: left; }}
th {{ background: #333; }}
</style>
</head>
<body>
<h1>{'ALL TESTS PASSED' if all_pass else 'SOME TESTS FAILED'}</h1>
<p>Generated: {timestamp}<br>
Total: {total_pass + total_fail} vectors ({total_pass} pass, {total_fail} fail)<br>
Elapsed: {total_elapsed:.2f}s</p>
<table>
<tr><th>Module</th><th>Status</th><th>Pass</th><th>Fail</th><th>Time</th></tr>
{rows}
</table>
</body>
</html>'''
with open(report_path, 'w') as f:
f.write(html)
print(f" Report saved to: {report_path}")