mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-13 12:22:33 -05:00
feat: add --verbose flag and ground truth rendering
This commit is contained in:
@@ -536,7 +536,39 @@ def verify_platform(
|
||||
# Output
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
def _format_ground_truth_aggregate(ground_truth: list[dict]) -> str:
|
||||
"""Format ground truth as a single aggregated line.
|
||||
|
||||
Example: beetle_psx [md5], pcsx_rearmed [existence]
|
||||
"""
|
||||
parts = []
|
||||
for gt in ground_truth:
|
||||
checks_label = "+".join(gt["checks"]) if gt["checks"] else "existence"
|
||||
parts.append(f"{gt['emulator']} [{checks_label}]")
|
||||
return ", ".join(parts)
|
||||
|
||||
|
||||
def _format_ground_truth_verbose(ground_truth: list[dict]) -> list[str]:
|
||||
"""Format ground truth as one line per core with expected values and source ref.
|
||||
|
||||
Example: handy validates size=512,crc32=0d973c9d [rom.h:48-49]
|
||||
"""
|
||||
lines = []
|
||||
for gt in ground_truth:
|
||||
checks_label = "+".join(gt["checks"]) if gt["checks"] else "existence"
|
||||
expected = gt.get("expected", {})
|
||||
if expected:
|
||||
vals = ",".join(f"{k}={v}" for k, v in sorted(expected.items()))
|
||||
part = f"{gt['emulator']} validates {vals}"
|
||||
else:
|
||||
part = f"{gt['emulator']} validates {checks_label}"
|
||||
if gt.get("source_ref"):
|
||||
part += f" [{gt['source_ref']}]"
|
||||
lines.append(part)
|
||||
return lines
|
||||
|
||||
|
||||
def print_platform_result(result: dict, group: list[str], verbose: bool = False) -> None:
|
||||
mode = result["verification_mode"]
|
||||
total = result["total_files"]
|
||||
c = result["severity_counts"]
|
||||
@@ -579,6 +611,13 @@ def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
hle = ", HLE available" if d.get("hle_fallback") else ""
|
||||
reason = d.get("reason", "")
|
||||
print(f" UNTESTED ({req}{hle}): {key} — {reason}")
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
if verbose:
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(f" {_format_ground_truth_aggregate(gt)}")
|
||||
for d in result["details"]:
|
||||
if d["status"] == Status.MISSING:
|
||||
key = f"{d['system']}/{d['name']}"
|
||||
@@ -588,6 +627,13 @@ def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
req = "required" if d.get("required", True) else "optional"
|
||||
hle = ", HLE available" if d.get("hle_fallback") else ""
|
||||
print(f" MISSING ({req}{hle}): {key}")
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
if verbose:
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(f" {_format_ground_truth_aggregate(gt)}")
|
||||
for d in result["details"]:
|
||||
disc = d.get("discrepancy")
|
||||
if disc:
|
||||
@@ -596,6 +642,27 @@ def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
continue
|
||||
seen_details.add(key)
|
||||
print(f" DISCREPANCY: {key} — {disc}")
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
if verbose:
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(f" {_format_ground_truth_aggregate(gt)}")
|
||||
|
||||
if verbose:
|
||||
for d in result["details"]:
|
||||
if d["status"] == Status.OK:
|
||||
key = f"{d['system']}/{d['name']}"
|
||||
if key in seen_details:
|
||||
continue
|
||||
seen_details.add(key)
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
req = "required" if d.get("required", True) else "optional"
|
||||
print(f" OK ({req}): {key}")
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
|
||||
# Cross-reference: undeclared files used by cores
|
||||
undeclared = result.get("undeclared_files", [])
|
||||
@@ -621,9 +688,39 @@ def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
if req_not_in_repo:
|
||||
for u in req_not_in_repo:
|
||||
print(f" MISSING (required): {u['emulator']} needs {u['name']}")
|
||||
checks = u.get("checks", [])
|
||||
if checks:
|
||||
if verbose:
|
||||
expected = u.get("expected", {})
|
||||
if expected:
|
||||
vals = ",".join(f"{k}={v}" for k, v in sorted(expected.items()))
|
||||
ref_part = f" [{u['source_ref']}]" if u.get("source_ref") else ""
|
||||
print(f" validates {vals}{ref_part}")
|
||||
else:
|
||||
checks_label = "+".join(checks)
|
||||
ref_part = f" [{u['source_ref']}]" if u.get("source_ref") else ""
|
||||
print(f" validates {checks_label}{ref_part}")
|
||||
else:
|
||||
checks_label = "+".join(checks)
|
||||
print(f" [{checks_label}]")
|
||||
if req_hle_not_in_repo:
|
||||
for u in req_hle_not_in_repo:
|
||||
print(f" MISSING (required, HLE fallback): {u['emulator']} needs {u['name']}")
|
||||
checks = u.get("checks", [])
|
||||
if checks:
|
||||
if verbose:
|
||||
expected = u.get("expected", {})
|
||||
if expected:
|
||||
vals = ",".join(f"{k}={v}" for k, v in sorted(expected.items()))
|
||||
ref_part = f" [{u['source_ref']}]" if u.get("source_ref") else ""
|
||||
print(f" validates {vals}{ref_part}")
|
||||
else:
|
||||
checks_label = "+".join(checks)
|
||||
ref_part = f" [{u['source_ref']}]" if u.get("source_ref") else ""
|
||||
print(f" validates {checks_label}{ref_part}")
|
||||
else:
|
||||
checks_label = "+".join(checks)
|
||||
print(f" [{checks_label}]")
|
||||
|
||||
if game_data:
|
||||
gd_missing = [u for u in game_data if not u["in_repo"]]
|
||||
@@ -638,6 +735,14 @@ def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
for ex in exclusions:
|
||||
print(f" {ex['emulator']} — {ex['detail']} [{ex['reason']}]")
|
||||
|
||||
# Ground truth coverage footer
|
||||
gt_cov = result.get("ground_truth_coverage")
|
||||
if gt_cov and gt_cov["total"] > 0:
|
||||
pct = gt_cov["with_validation"] * 100 // gt_cov["total"]
|
||||
print(f" Ground truth: {gt_cov['with_validation']}/{gt_cov['total']} files have emulator validation ({pct}%)")
|
||||
if gt_cov["platform_only"]:
|
||||
print(f" {gt_cov['platform_only']} platform-only (no emulator profile)")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Emulator/system mode verification
|
||||
@@ -884,7 +989,7 @@ def verify_system(
|
||||
return verify_emulator(matching, emulators_dir, db, standalone)
|
||||
|
||||
|
||||
def print_emulator_result(result: dict) -> None:
|
||||
def print_emulator_result(result: dict, verbose: bool = False) -> None:
|
||||
"""Print verification result for emulator/system mode."""
|
||||
label = " + ".join(result["emulators"])
|
||||
mode = result["verification_mode"]
|
||||
@@ -912,6 +1017,13 @@ def print_emulator_result(result: dict) -> None:
|
||||
hle = ", HLE available" if d.get("hle_fallback") else ""
|
||||
reason = d.get("reason", "")
|
||||
print(f" UNTESTED ({req}{hle}): {d['name']} — {reason}")
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
if verbose:
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(f" {_format_ground_truth_aggregate(gt)}")
|
||||
for d in result["details"]:
|
||||
if d["status"] == Status.MISSING:
|
||||
if d["name"] in seen:
|
||||
@@ -920,13 +1032,41 @@ def print_emulator_result(result: dict) -> None:
|
||||
req = "required" if d.get("required", True) else "optional"
|
||||
hle = ", HLE available" if d.get("hle_fallback") else ""
|
||||
print(f" MISSING ({req}{hle}): {d['name']}")
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
if verbose:
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(f" {_format_ground_truth_aggregate(gt)}")
|
||||
for d in result["details"]:
|
||||
if d.get("note"):
|
||||
print(f" {d['note']}")
|
||||
|
||||
if verbose:
|
||||
for d in result["details"]:
|
||||
if d["status"] == Status.OK:
|
||||
if d["name"] in seen:
|
||||
continue
|
||||
seen.add(d["name"])
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
req = "required" if d.get("required", True) else "optional"
|
||||
print(f" OK ({req}): {d['name']}")
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
|
||||
for ref in result.get("data_dir_notices", []):
|
||||
print(f" Note: data directory '{ref}' required but not included (use refresh_data_dirs.py)")
|
||||
|
||||
# Ground truth coverage footer
|
||||
gt_cov = result.get("ground_truth_coverage")
|
||||
if gt_cov and gt_cov["total"] > 0:
|
||||
pct = gt_cov["with_validation"] * 100 // gt_cov["total"]
|
||||
print(f" Ground truth: {gt_cov['with_validation']}/{gt_cov['total']} files have emulator validation ({pct}%)")
|
||||
if gt_cov["platform_only"]:
|
||||
print(f" {gt_cov['platform_only']} platform-only (no emulator profile)")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Platform-native BIOS verification")
|
||||
@@ -943,6 +1083,7 @@ def main():
|
||||
parser.add_argument("--db", default=DEFAULT_DB)
|
||||
parser.add_argument("--platforms-dir", default=DEFAULT_PLATFORMS_DIR)
|
||||
parser.add_argument("--emulators-dir", default=DEFAULT_EMULATORS_DIR)
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="Show emulator ground truth details")
|
||||
parser.add_argument("--json", action="store_true", help="JSON output")
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -990,7 +1131,7 @@ def main():
|
||||
result["details"] = [d for d in result["details"] if d["status"] != Status.OK]
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
print_emulator_result(result)
|
||||
print_emulator_result(result, verbose=args.verbose)
|
||||
return
|
||||
|
||||
# System mode
|
||||
@@ -1001,7 +1142,7 @@ def main():
|
||||
result["details"] = [d for d in result["details"] if d["status"] != Status.OK]
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
print_emulator_result(result)
|
||||
print_emulator_result(result, verbose=args.verbose)
|
||||
return
|
||||
|
||||
# Platform mode (existing)
|
||||
@@ -1055,7 +1196,7 @@ def main():
|
||||
|
||||
if not args.json:
|
||||
for result, group in group_results:
|
||||
print_platform_result(result, group)
|
||||
print_platform_result(result, group, verbose=args.verbose)
|
||||
print()
|
||||
|
||||
if args.json:
|
||||
|
||||
@@ -1461,5 +1461,43 @@ class TestE2E(unittest.TestCase):
|
||||
break
|
||||
|
||||
|
||||
def test_120_format_ground_truth_aggregate(self):
|
||||
"""Aggregate format: one line with all cores."""
|
||||
from verify import _format_ground_truth_aggregate
|
||||
gt = [
|
||||
{"emulator": "beetle_psx", "checks": ["md5"], "source_ref": "libretro.cpp:252", "expected": {"md5": "abc"}},
|
||||
{"emulator": "pcsx_rearmed", "checks": ["existence"], "source_ref": None, "expected": {}},
|
||||
]
|
||||
line = _format_ground_truth_aggregate(gt)
|
||||
self.assertIn("beetle_psx", line)
|
||||
self.assertIn("[md5]", line)
|
||||
self.assertIn("pcsx_rearmed", line)
|
||||
self.assertIn("[existence]", line)
|
||||
|
||||
def test_121_format_ground_truth_verbose(self):
|
||||
"""Verbose format: one line per core with expected values and source ref."""
|
||||
from verify import _format_ground_truth_verbose
|
||||
gt = [
|
||||
{"emulator": "handy", "checks": ["size", "crc32"],
|
||||
"source_ref": "rom.h:48-49", "expected": {"size": 512, "crc32": "0d973c9d"}},
|
||||
]
|
||||
lines = _format_ground_truth_verbose(gt)
|
||||
self.assertEqual(len(lines), 1)
|
||||
self.assertIn("handy", lines[0])
|
||||
self.assertIn("size=512", lines[0])
|
||||
self.assertIn("crc32=0d973c9d", lines[0])
|
||||
self.assertIn("[rom.h:48-49]", lines[0])
|
||||
|
||||
def test_122_format_ground_truth_verbose_no_source_ref(self):
|
||||
"""Verbose format omits bracket when source_ref is None."""
|
||||
from verify import _format_ground_truth_verbose
|
||||
gt = [
|
||||
{"emulator": "core_a", "checks": ["existence"], "source_ref": None, "expected": {}},
|
||||
]
|
||||
lines = _format_ground_truth_verbose(gt)
|
||||
self.assertEqual(len(lines), 1)
|
||||
self.assertNotIn("[", lines[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user