diff options
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/harness/run_contracts.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/harness/run_contracts.py | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/harness/run_contracts.py b/debug/risc-v-gsp-rm-vm/harness/run_contracts.py new file mode 100644 index 0000000..1ea8474 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/harness/run_contracts.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +"""Run the wall-contract registry (``harness/contracts.py``, docs/50). + +Every solved wall is pinned as a contract over an existing tool entry point; +this CLI runs them and prints pass/fail per contract plus per-wall-class +coverage. Fast contracts are the suite default; slow ones run with ``--all`` +(or by name). + +Offline only: never touches a GPU, kernel module, mdev, guest or network. +Exit codes: 0 = every selected contract passed (or skipped for a missing +prerequisite), 1 = at least one failure, 2 = usage error. +""" +from __future__ import annotations + +import argparse +import json +import os +import sys + +TOOL_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if TOOL_ROOT not in sys.path: + sys.path.insert(0, TOOL_ROOT) + +from harness.contracts import ( # noqa: E402 + Contract, + all_contracts, + fast_contracts, + run_contracts, + summarize, +) + + +def _select(args: argparse.Namespace) -> tuple[list[Contract], list[str]]: + if args.name: + wanted = set(args.name) + chosen = [c for c in all_contracts() if c.name in wanted] + unknown = sorted(wanted - {c.name for c in chosen}) + return chosen, unknown + return (all_contracts() if args.all else fast_contracts()), [] + + +def _table(rows: list[tuple[str, ...]], headers: tuple[str, ...]) -> list[str]: + widths = [ + max(len(headers[index]), *(len(row[index]) for row in rows)) + for index in range(len(headers)) + ] + lines = [" ".join(head.ljust(widths[i]) for i, head in enumerate(headers))] + lines.append(" ".join("-" * width for width in widths)) + for row in rows: + lines.append(" ".join(cell.ljust(widths[i]) for i, cell in enumerate(row))) + return lines + + +def _render(results, summary: dict) -> list[str]: + rows = [ + ( + result.name, + result.wall_class, + result.speed, + result.status.upper(), + f"{result.seconds:.2f}s", + result.detail, + ) + for result in results + ] + lines = _table(rows, ("contract", "class", "speed", "status", "time", "detail")) + lines.append("") + lines.append("wall-class coverage (selected):") + for domain in summary["domains"]: + lines.append( + f" {domain['wall_class']} {domain['domain']:<28} " + f"{domain['total']} contracts: {domain['pass']} pass, " + f"{domain['fail']} fail, {domain['skip']} skip" + ) + counts = summary["counts"] + lines.append("") + lines.append( + f"total: {counts['pass']} pass, {counts['fail']} fail, {counts['skip']} skip " + f"-> {'OK' if summary['ok'] else 'FAIL'}" + ) + return lines + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="run_contracts.py", + description=( + "Run the wall-contract registry: fast contracts by default, all " + "with --all (offline; never touches hardware)." + ), + ) + group = parser.add_mutually_exclusive_group() + group.add_argument( + "--fast", action="store_true", help="run fast contracts only (default)" + ) + group.add_argument("--all", action="store_true", help="run fast and slow contracts") + parser.add_argument( + "--name", + action="append", + default=[], + metavar="NAME", + help="run one named contract (repeatable); overrides --fast/--all selection", + ) + parser.add_argument("--json", metavar="PATH", help="write the run summary as JSON here") + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + selected, unknown = _select(args) + if unknown: + parser.error("unknown contract name(s): " + ", ".join(unknown)) + results = run_contracts(selected) + summary = summarize(results) + for line in _render(results, summary): + print(line) + if args.json: + out_path = os.path.abspath(args.json) + parent = os.path.dirname(out_path) + if parent: + os.makedirs(parent, exist_ok=True) + with open(out_path, "w", encoding="utf-8") as fh: + json.dump(summary, fh, indent=2, sort_keys=True) + fh.write("\n") + print(f"json: {out_path}") + return 0 if summary["ok"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) |
