summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/harness/run_contracts.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-06 19:28:39 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-06 19:28:39 -0400
commit6c3fd5df61ff2f5cb8833ead6c05eaff9fdc92bf (patch)
tree3b3680902cd2223cbe60aaec0a0799cfc7b26be6 /debug/risc-v-gsp-rm-vm/harness/run_contracts.py
parenta5473c91b8869b017385582ea969783b33553ded (diff)
downloadopen-iov-6c3fd5df61ff2f5cb8833ead6c05eaff9fdc92bf.tar.gz
open-iov-6c3fd5df61ff2f5cb8833ead6c05eaff9fdc92bf.zip
risc-v-gsp-rm-debugger: land docs 45-53 + harness/sim tool set with tests
rm_capture (chunked decode), launch_audit, state_seed/state_decode, valprobe, rehost, contracts+run_contracts, host_traffic, gmmu_walk, msgq_attach, libos_port, log_decode, server_snapshot; doc index updates in the tool overview, operations pages and the repo README.
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.py131
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())