Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import json | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from typing import Dict, List, Optional, Set, Tuple | |
| class CoverageBin: | |
| name: str | |
| hit_count: int | |
| goal: int = 1 | |
| def covered(self) -> bool: | |
| return self.hit_count >= self.goal | |
| class SimResult: | |
| passed: bool | |
| total_bins: int = 0 | |
| covered_bins: int = 0 | |
| bins: List[CoverageBin] = field(default_factory=list) | |
| errors: List[str] = field(default_factory=list) | |
| log_output: str = "" | |
| seed: int = 0 | |
| def coverage_pct(self) -> float: | |
| if self.total_bins == 0: | |
| return 0.0 | |
| return (self.covered_bins / self.total_bins) * 100.0 | |
| def uncovered_bins(self) -> List[CoverageBin]: | |
| return [b for b in self.bins if not b.covered] | |
| class CoverageDB: | |
| """Merges coverage results across multiple seeds for regression.""" | |
| def __init__(self): | |
| self.seed_results: List[SimResult] = [] | |
| self.merged_bins: Dict[str, CoverageBin] = {} | |
| def add_seed_result(self, result: SimResult) -> None: | |
| self.seed_results.append(result) | |
| for b in result.bins: | |
| key = b.name | |
| if key in self.merged_bins: | |
| existing = self.merged_bins[key] | |
| existing.hit_count = max(existing.hit_count, b.hit_count) | |
| existing.goal = max(existing.goal, b.goal) | |
| else: | |
| self.merged_bins[key] = CoverageBin(name=b.name, hit_count=b.hit_count, goal=b.goal) | |
| def merge(self) -> SimResult: | |
| bins = list(self.merged_bins.values()) | |
| covered = sum(1 for b in bins if b.covered) | |
| total = len(bins) | |
| return SimResult( | |
| passed=covered == total, | |
| total_bins=total, | |
| covered_bins=covered, | |
| bins=bins, | |
| errors=[], | |
| log_output=self._format_summary(bins, covered, total), | |
| ) | |
| def _format_summary(self, bins: List[CoverageBin], covered: int, total: int) -> str: | |
| pct = (covered / total * 100) if total else 0 | |
| lines = [ | |
| f"--- CoverageDB merged ({len(self.seed_results)} seeds) ---", | |
| ] | |
| for b in bins: | |
| status = "HIT" if b.covered else "MISS" | |
| lines.append(f"COVERAGE: {b.name} {b.hit_count}/{b.goal} [{status}]") | |
| lines.append(f"--- Merged: {covered}/{total} ({pct:.1f}%) ---") | |
| return "\n".join(lines) | |
| def save(self, path: str) -> None: | |
| data = { | |
| "num_seeds": len(self.seed_results), | |
| "merged": {k: {"hit_count": v.hit_count, "goal": v.goal} | |
| for k, v in self.merged_bins.items()}, | |
| } | |
| Path(path).write_text(json.dumps(data, indent=2), encoding="utf-8") | |
| def load(cls, path: str) -> CoverageDB: | |
| data = json.loads(Path(path).read_text(encoding="utf-8")) | |
| db = cls() | |
| for name, bdata in data.get("merged", {}).items(): | |
| db.merged_bins[name] = CoverageBin(name=name, **bdata) | |
| return db | |
| def uncovered(self) -> List[CoverageBin]: | |
| return [b for b in self.merged_bins.values() if not b.covered] | |
| class Simulator: | |
| def __init__(self, work_dir: str = "sim_output"): | |
| self.work_dir = work_dir | |
| def run(self, files: List[str], top: str = "testbench", | |
| plusargs: Optional[List[str]] = None) -> SimResult: | |
| raise NotImplementedError | |
| def run_multi_seed(self, files: List[str], num_seeds: int = 3, | |
| top: str = "testbench") -> Tuple[SimResult, CoverageDB]: | |
| db = CoverageDB() | |
| merged = None | |
| for s in range(num_seeds): | |
| result = self.run(files, top=top, plusargs=[f"+seed={s+1}"]) | |
| result.seed = s + 1 | |
| db.add_seed_result(result) | |
| merged = db.merge() | |
| return merged, db | |
| def parse_coverage(self, log: str) -> SimResult: | |
| raise NotImplementedError | |
| def name(self) -> str: | |
| return self.__class__.__name__ | |