From 7fe65936e00ab32641ccbff69c46f44d076dc7a5 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:26:57 +0200 Subject: [PATCH] fix: apply verify --strict in every mode --- scripts/verify.py | 34 ++++++++++++++++++++++++++++--- tests/test_audit_regressions.py | 36 +++++++++++++++++++++++++++++++++ wiki/tools.md | 1 + 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/scripts/verify.py b/scripts/verify.py index 62fac910..b6f79e23 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -2016,6 +2016,8 @@ def _refuse_listing_narrowings( ("--standalone", args.standalone), ("--include-archived", args.include_archived), ("--json", args.json), + ("--strict", args.strict), + ("--verbose", args.verbose), ): if on: parser.error(f"{flag} is incompatible with {listing}") @@ -2094,7 +2096,8 @@ def main(): parser.add_argument( "--strict", action="store_true", - help="Exit 1 when a platform that checks hashes misses a required file", + help="Exit 1 on a required file missing: where the frontend checks " + "hashes for a platform, without HLE for --emulator and --system", ) args = parser.parse_args() if args.platform: @@ -2141,6 +2144,7 @@ def main(): print(json.dumps(result, indent=2)) else: print_emulator_result(result, verbose=args.verbose) + _exit_if_strict_emulator(args.strict, result) return # System mode @@ -2157,6 +2161,7 @@ def main(): print(json.dumps(result, indent=2)) else: print_emulator_result(result, verbose=args.verbose) + _exit_if_strict_emulator(args.strict, result) return # Platform mode (existing) @@ -2230,13 +2235,36 @@ def main(): r["details"] = [d for d in r["details"] if d["status"] != Status.OK] print(json.dumps(all_results, indent=2)) - critical = sum(r["severity_counts"].get(Severity.CRITICAL, 0) for r, _ in group_results) - if args.strict and critical: + _exit_if_strict(args.strict, [r for r, _ in group_results]) + + +def _exit_if_strict(strict: bool, results: list[dict]) -> None: + """Exit 1 under --strict when a platform result counts a critical file: + a required file missing or rejected where the frontend reads hashes.""" + critical = sum(r["severity_counts"].get(Severity.CRITICAL, 0) for r in results) + if strict and critical: print(f"ERROR: {critical} required file(s) missing where the frontend " "checks hashes", file=sys.stderr) sys.exit(1) +def _exit_if_strict_emulator(strict: bool, result: dict) -> None: + """Exit 1 under --strict when an emulator lacks a file it cannot run without. + + The emulator, not a frontend, is the judge here: a required file with no + HLE fallback that the collection does not hold stops the core. + """ + missing = sorted({ + d["name"] for d in result["details"] + if d["status"] == Status.MISSING and d.get("required", True) + and not d.get("hle_fallback") + }) + if strict and missing: + print(f"ERROR: {len(missing)} required file(s) missing: " + f"{', '.join(missing)}", file=sys.stderr) + sys.exit(1) + + def _overall_summary(group_results: list[tuple[dict, list[str]]]) -> str: """One closing line for a multi-platform run, worst severity first.""" platforms = sum(len(group) for _, group in group_results) diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index 3a12e17d..646b23ea 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -3075,3 +3075,39 @@ class TriageSurvivesAForgeRefusal(unittest.TestCase): lines = out.getvalue().splitlines() self.assertEqual(len(lines), 2) self.assertTrue(any("eden" in l and "unreachable" in l for l in lines)) + + +class VerifyStrictAppliesInEveryMode(unittest.TestCase): + """--strict was read only by the platform branch: `--emulator x --strict` + printed MISSING (required) and exited 0, and the listings accepted it.""" + + @classmethod + def setUpClass(cls): + if not (ROOT / "database.json").exists(): + raise unittest.SkipTest("database.json is not built") + cls.dir = Path(tempfile.mkdtemp(dir=TMP_ROOT)) + (cls.dir / "fakeemu.yml").write_text( + "emulator: FakeEmu\ntype: libretro\nsystems: [zz-console]\nfiles:\n" + " - name: does_not_exist_strict.bin\n required: true\n" + " md5: 0123456789abcdef0123456789abcdef\n validation: [md5]\n" + ) + + @classmethod + def tearDownClass(cls): + __import__("shutil").rmtree(cls.dir, True) + + def run_verify(self, *flags: str) -> int: + return subprocess.run( + [sys.executable, "scripts/verify.py", *flags], cwd=ROOT, + capture_output=True, text=True, + ).returncode + + def test_emulator_and_system_modes_fail_on_a_missing_required_file(self): + for mode in (["--emulator", "fakeemu"], ["--system", "zz-console"]): + with self.subTest(mode=mode[0]): + flags = [*mode, "--emulators-dir", str(self.dir)] + self.assertEqual(self.run_verify(*flags), 0) + self.assertEqual(self.run_verify(*flags, "--strict"), 1) + + def test_a_listing_refuses_strict(self): + self.assertEqual(self.run_verify("--list-emulators", "--strict"), 2) diff --git a/wiki/tools.md b/wiki/tools.md index 3487836b..beddd449 100644 --- a/wiki/tools.md +++ b/wiki/tools.md @@ -83,6 +83,7 @@ python scripts/verify.py --platform batocera # single platform python scripts/verify.py --platform retroarch --verbose # with ground truth details python scripts/verify.py --emulator dolphin # single emulator python scripts/verify.py --emulator dolphin --standalone # standalone mode only +python scripts/verify.py --emulator handy --strict # exit 1 on a required file missing with no HLE python scripts/verify.py --system atari-lynx # single system python scripts/verify.py --platform retroarch --target switch # filter by hardware python scripts/verify.py --platform recalbox --region us # regional priority list