- 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
131 lines
4.4 KiB
Python
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}")
|