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
This commit is contained in:
130
test_framework/lib/reporter.py
Normal file
130
test_framework/lib/reporter.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""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}")
|
||||
Reference in New Issue
Block a user