diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index 606de817..a55448d4 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -2019,6 +2019,7 @@ def _validate_args(args, parser): parser.error("--source requires --platform or --all") if has_from_md5: for flag, given in ( + ("--manifest", args.manifest), ("--target", args.target), ("--required-only", args.required_only), ("--source", source_given), @@ -2431,27 +2432,70 @@ def _refuse_unapplied_flags(args, parser) -> None: parser.error("--one-per-slot is incompatible with --manifest-targets") _refuse_for_all_variants(args, parser) - if args.verify_packs and not args.all_variants: - # Checks packs already on disk against the platform's own list, - # narrowed by region and target; it narrows by nothing else. - for flag, given in ( - ("--one-per-slot", args.one_per_slot), - ("--required-only", args.required_only), - ("--source", args.source != "full"), - ("--system", args.system), - ): - if given: - parser.error(f"{flag} is incompatible with --verify-packs") - if args.manifest_targets: - # One manifest per hardware target, from the target files themselves: - # it reads none of the narrowing flags. - for flag, given in ( - ("--target", args.target), - ("--required-only", args.required_only), - ("--source", args.source != "full"), - ): - if given: - parser.error(f"{flag} is incompatible with --manifest-targets") + mode = next( + ( + name for name, on in ( + ("--manifest-targets", args.manifest_targets), + ("--verify-packs", args.verify_packs and not args.all_variants), + ("--list", args.list), + ("--list-emulators", args.list_emulators), + ("--list-systems", args.list_systems), + ("--list-targets", args.list_targets), + ) if on + ), + None, + ) + if mode is None: + return + given = _given_flags(args) + for flag in sorted(given - QUICK_MODE_FLAGS[mode] - {mode}): + parser.error(f"{flag} is incompatible with {mode}") + + +# What each mode that returns early reads. Anything else given beside it is +# refused: the mode would answer about an artifact the caller did not name. +QUICK_MODE_FLAGS: dict[str, set[str]] = { + # Checks packs already on disk against the platform's own list, narrowed + # by region and target and by nothing else. + "--verify-packs": {"--platform", "--all", "--target", "--region", "--include-archived"}, + # One manifest per hardware target, from the target files themselves. + "--manifest-targets": set(), + "--list": set(), + "--list-emulators": set(), + "--list-systems": {"--platform"}, + "--list-targets": {"--platform"}, +} + + +def _given_flags(args) -> set[str]: + """Flags that select or narrow what a run produces, as typed.""" + flags = { + "--platform": args.platform, + "--all": args.all, + "--emulator": args.emulator, + "--system": args.system, + "--standalone": args.standalone, + "--split": args.split, + "--group-by": args.group_by != "system", + "--manifest": args.manifest, + "--manifest-targets": args.manifest_targets, + "--verify-packs": args.verify_packs, + "--all-variants": args.all_variants, + "--from-md5": args.from_md5, + "--from-md5-file": args.from_md5_file, + "--required-only": args.required_only, + "--source": args.source != "full", + "--target": args.target, + "--region": args.region, + "--one-per-slot": args.one_per_slot, + "--list": args.list, + "--list-emulators": args.list_emulators, + "--list-systems": args.list_systems, + "--list-targets": args.list_targets, + "--include-archived": args.include_archived, + "--refresh-data": args.refresh_data, + } + return {flag for flag, on in flags.items() if on} def main(): diff --git a/scripts/verify.py b/scripts/verify.py index 5283f1c1..3b03bf3f 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -35,6 +35,7 @@ import slots sys.path.insert(0, os.path.dirname(__file__)) from common import ( PROFILE_IDENTITY_FIELDS, + list_platform_system_ids, build_target_cores_cache, build_zip_contents_index, check_inside_zip, @@ -1716,11 +1717,36 @@ def main(): except ValueError as exc: parser.error(str(exc)) + # A listing mode reads --platform at most; anything else that narrows is + # refused rather than printed past (generate_pack applies the same table). + listing = next( + (flag for flag, on in ( + ("--list-emulators", args.list_emulators), + ("--list-systems", args.list_systems), + ("--list-targets", args.list_targets), + ) if on), + None, + ) + if listing: + reads_platform = listing != "--list-emulators" + for flag, on in ( + ("--platform", args.platform and not reads_platform), + ("--all", args.all), + ("--emulator", args.emulator), + ("--system", args.system), + ("--region", getattr(args, "region", None)), + ("--target", getattr(args, "target", None)), + ): + if on: + parser.error(f"{flag} is incompatible with {listing}") if args.list_emulators: list_emulator_profiles(args.emulators_dir) return if args.list_systems: - list_system_ids(args.emulators_dir) + if args.platform: + list_platform_system_ids(args.platform, args.platforms_dir) + else: + list_system_ids(args.emulators_dir) return if args.list_targets: diff --git a/tests/test_e2e.py b/tests/test_e2e.py index eba45804..e9590b00 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -5992,6 +5992,18 @@ struct BurnDriver BurnDrvneogeo = { "refuse"), (["--all", "--all-variants", "--verify-packs"], ["--split"], "refuse"), + # Every flag a quick mode does not read, from one table. + (["--platform", "retroarch", "--verify-packs"], ["--split"], "refuse"), + (["--platform", "retroarch", "--verify-packs"], ["--manifest"], "refuse"), + (["--platform", "retroarch", "--verify-packs"], ["--emulator", "handy"], + "refuse"), + (["--manifest-targets"], ["--platform", "batocera"], "refuse"), + (["--manifest-targets"], ["--system", "sony-playstation"], "refuse"), + (["--manifest-targets"], ["--all"], "refuse"), + (["--platform", "retroarch", "--from-md5", "d8f1"], ["--manifest"], + "refuse"), + (["--list-systems"], ["--region", "us"], "refuse"), + (["--list-emulators"], ["--target", "switch"], "refuse"), ] for mode, flag, expected in matrix: with self.subTest(mode=mode, flag=flag): diff --git a/tests/test_verify_listing.py b/tests/test_verify_listing.py new file mode 100644 index 00000000..f144d7d3 --- /dev/null +++ b/tests/test_verify_listing.py @@ -0,0 +1,32 @@ +"""verify.py lists what generate_pack.py lists for the same request.""" + +from __future__ import annotations + +import subprocess +import sys +import unittest +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] + + +def _run(*argv: str) -> subprocess.CompletedProcess: + return subprocess.run( + [sys.executable, *argv], capture_output=True, text=True, cwd=REPO_ROOT, timeout=300 + ) + + +class ListingsAgree(unittest.TestCase): + def test_list_systems_honours_platform(self): + verify = _run("scripts/verify.py", "--list-systems", "--platform", "bizhawk") + pack = _run("scripts/generate_pack.py", "--list-systems", "--platform", "bizhawk") + self.assertEqual(verify.returncode, 0) + self.assertEqual(verify.stdout, pack.stdout) + + def test_listing_refuses_a_narrowing_flag(self): + result = _run("scripts/verify.py", "--list-emulators", "--region", "us") + self.assertNotEqual(result.returncode, 0) + + +if __name__ == "__main__": + unittest.main()