diff --git a/scripts/verify.py b/scripts/verify.py index 1721800a..68eb6087 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -1595,8 +1595,11 @@ def _print_undeclared_section(result: dict, verbose: bool) -> None: core_missing_req = len(req_not_in_repo) + len(req_hle_not_in_repo) core_missing_opt = len(opt_not_in_repo) + # Required gaps are always named below, the optional ones on request. + listed = "" if verbose or not core_missing_opt else " (--verbose lists them)" print( - f" Core files: {core_in_pack} in pack, {core_missing_req} required missing, {core_missing_opt} optional missing" + f" Core files: {core_in_pack} in pack, {core_missing_req} required missing, " + f"{core_missing_opt} optional missing{listed}" ) if bundled: names = ", ".join(sorted({u["name"] for u in bundled})) @@ -1608,6 +1611,9 @@ def _print_undeclared_section(result: dict, verbose: bool) -> None: _print_undeclared_entry(u, "MISSING (required)", verbose) for u in req_hle_not_in_repo: _print_undeclared_entry(u, "MISSING (required, HLE fallback)", verbose) + if verbose: + for u in opt_not_in_repo: + _print_undeclared_entry(u, "MISSING (optional)", verbose) if game_data: gd_missing = [u for u in game_data if not u["in_repo"]] @@ -1617,7 +1623,11 @@ def _print_undeclared_section(result: dict, verbose: bool) -> None: ) gd_unplaced = [u for u in game_data if u["in_repo"] and not u.get("in_pack", True)] if gd_missing or gd_packed or gd_unplaced: - print(f" Game data: {gd_packed} in pack, {len(gd_missing)} missing") + listed = "" if verbose or not gd_missing else " (--verbose lists them)" + print(f" Game data: {gd_packed} in pack, {len(gd_missing)} missing{listed}") + if verbose: + for u in gd_missing: + _print_undeclared_entry(u, "MISSING (game data)", verbose) if gd_unplaced: print( f" Game data held but not packed: {len(gd_unplaced)} " diff --git a/tests/test_verify_listing.py b/tests/test_verify_listing.py index 1e78fec7..893ccec5 100644 --- a/tests/test_verify_listing.py +++ b/tests/test_verify_listing.py @@ -148,5 +148,43 @@ class ExistenceReportsWhatThePackReports(unittest.TestCase): self.assertEqual(result["status"], verify.Status.OK) self.assertIn("declared hash 9d2ff233", result["discrepancy"]) + +class TheDestinationsFlagNeedsAPack(unittest.TestCase): + """--destinations describes a platform's full pack: another mode refuses it.""" + + def test_refused_outside_platform_modes(self): + for mode in (("--emulator", "handy"), ("--platform", "retroarch", "--region", "us")): + with self.subTest(mode=mode): + result = _run("scripts/verify.py", *mode, "--destinations", "tmp/never.json") + self.assertEqual(result.returncode, 2, result.stdout[-300:]) + self.assertIn("--destinations", result.stderr) + + +class EveryGapCountedIsNamed(unittest.TestCase): + """verify --platform batocera --verbose counted 3 optional core files and + 427 game data files missing and named none of them.""" + + def test_verbose_names_them_and_the_summary_says_how(self): + import contextlib + import io + + sys.path.insert(0, str(REPO_ROOT / "scripts")) + import verify # noqa: PLC0415 + + result = {"undeclared_files": [ + {"name": "opt.rom", "emulator": "core", "required": False, "in_repo": False}, + {"name": "level.dat", "emulator": "engine", "required": True, "in_repo": False, + "category": "game_data"}, + ]} + with contextlib.redirect_stdout(io.StringIO()) as quiet: + verify._print_undeclared_section(result, verbose=False) + self.assertIn("1 optional missing (--verbose lists them)", quiet.getvalue()) + self.assertIn("1 missing (--verbose lists them)", quiet.getvalue()) + with contextlib.redirect_stdout(io.StringIO()) as verbose: + verify._print_undeclared_section(result, verbose=True) + self.assertIn("MISSING (optional): core needs opt.rom", verbose.getvalue()) + self.assertIn("MISSING (game data): engine needs level.dat", verbose.getvalue()) + + if __name__ == "__main__": unittest.main()