Files
retroSoC/scripts/check_simulation.py
T
maksyuki 6fbc6ec521 fix(flow): retain terminal evidence across interrupted runs
Record parent and child process IDs for diagnostics, persist interruption
before cleanup, and ignore repeated termination signals during cleanup.
Close unexpected failures with a terminal result and retain the child's
exit status without treating namespace-local PID absence as completion.

Require a successful terminal run_flow result when checking simulation
logs with an associated result file. Cover repeated cancellation and
reject running, failed or inconsistent terminal records despite PASS text.

This supports the TINY-R2-P7 campaign. Interrupted and timed-out runs remain
non-acceptance evidence; UART output alone cannot qualify a Tiny simulation.
2026-10-14 16:55:12 +08:00

73 lines
2.6 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import atomic_write # noqa: E402
DEFAULT_SUCCESS = "SIM_TEST_PASS"
DEFAULT_FAILURE = re.compile(
r"(?:\bFAILED?\b|\bFATAL\b|assertion failed|%Error|SIM_TEST_FAIL|SIM_TEST_TIMEOUT)",
re.IGNORECASE,
)
def main() -> int:
parser = argparse.ArgumentParser(description="Validate a retroSoC simulation log")
parser.add_argument("--log", type=Path, required=True)
parser.add_argument("--result", type=Path, required=True)
parser.add_argument("--require", action="append", default=[])
parser.add_argument("--reject", action="append", default=[])
args = parser.parse_args()
if not args.log.is_file():
raise SystemExit(f"simulation log not found: {args.log}")
content = args.log.read_text(encoding="utf-8", errors="replace")
required = args.require or [DEFAULT_SUCCESS]
missing = [marker for marker in required if marker not in content]
rejected = [pattern for pattern in args.reject if re.search(pattern, content)]
default_failure = DEFAULT_FAILURE.search(content)
if default_failure:
rejected.append(default_failure.group(0))
flow_result_path = args.result.with_name("result-sim.json")
flow_status = None
if flow_result_path.is_file():
try:
flow_result = json.loads(flow_result_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
flow_result = {}
flow_status = flow_result.get("status", "invalid")
# PIDs are namespace-local and can be reused. A durable terminal
# verdict, not a liveness guess from this process, establishes success.
if flow_status != "passed" or flow_result.get("exit_code") != 0:
missing.append("successful terminal run_flow result")
passed = not missing and not rejected
report = {
"schema_version": 1,
"status": "passed" if passed else "failed",
"log": str(args.log.resolve()),
"required_markers": required,
"missing_markers": missing,
"rejected_matches": sorted(set(rejected)),
}
if flow_status is not None:
report["flow_status"] = flow_status
atomic_write(args.result, json.dumps(report, indent=2, sort_keys=True) + "\n")
print(f"simulation check: {report['status']} ({args.result.resolve()})")
return int(not passed)
if __name__ == "__main__":
raise SystemExit(main())