From c7df7907443e28ccea56857d7a31a09686bf4456 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 11:47:03 +0100 Subject: [PATCH] fix: one system scope for verify and the packs A system the target leaves no core for was verified 0/0 with exit 0, and generate_pack blamed the platform for lacking it. Both read platform_scope_systems and system_scope_error now (findings 13, 14). --- scripts/common.py | 40 ++++++++++++++++++++++++++++++++++ scripts/generate_pack.py | 33 +++++++--------------------- scripts/verify.py | 26 ++++++++++++---------- tests/test_target_verify.py | 43 +++++++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 36 deletions(-) diff --git a/scripts/common.py b/scripts/common.py index 000e0404b..3ae9c8add 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -2993,6 +2993,46 @@ def filter_systems_by_target( } +def platform_scope_systems( + config: dict, + profiles: dict[str, dict], + target_cores: set[str] | None, + system_filter: list[str] | None = None, +) -> dict[str, dict]: + """The systems a pack or a report of a platform covers. + + Those a core of the target runs, then the ones a --system list keeps. + verify.py and generate_pack.py read this one function. + """ + plat_cores = resolve_platform_cores(config, profiles) if target_cores else None + systems = filter_systems_by_target( + config.get("systems", {}), profiles, target_cores, platform_cores=plat_cores + ) + return select_systems(systems, system_filter) if system_filter else systems + + +def system_scope_error( + config: dict, platform_name: str, system_filter: list[str], target: str | None +) -> str: + """Why a --system list leaves a platform no system. + + A system the platform declares but no core of the target runs was + reported as a system the platform lacks, with a lookalike to try and a + listing that refuses --target. + """ + systems = config.get("systems", {}) + declared = select_systems(systems, system_filter) + if declared and target: + return ( + f"no core {platform_name} runs on target {target} emulates " + f"{', '.join(sorted(declared))}" + ) + hint = suggest_names( + system_filter, sorted(systems), f"--platform {platform_name} --list-systems" + ) + return f"{platform_name} has no system matching {', '.join(system_filter)}. {hint}" + + def platform_declarations( config: dict, db: dict, *, enrich: bool = True ) -> dict[str, list[tuple[int | None, str]]]: diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index 9da95411a..72c81ff4e 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -53,7 +53,8 @@ from common import ( list_platform_system_ids, platform_system_names, resolve_system_arg, - select_systems, + platform_scope_systems, + system_scope_error, canonical_target_name, platform_files_target, list_registered_platforms, @@ -753,31 +754,13 @@ def generate_pack( target_cores=target_cores, source=source, ) - # Filter systems by target if specified - plat_cores = ( - resolve_platform_cores(config, emu_profiles or {}) if target_cores else None + pack_systems = platform_scope_systems( + config, emu_profiles or {}, target_cores, system_filter ) - pack_systems = filter_systems_by_target( - config.get("systems", {}), - emu_profiles or {}, - target_cores, - platform_cores=plat_cores, - ) - - if system_filter: - filtered = select_systems(pack_systems, system_filter) - if not filtered: - hint = suggest_names( - system_filter, - sorted(pack_systems), - f"--platform {platform_name} --list-systems", - ) - print( - f" WARNING: {platform_name} has no system matching " - f"{', '.join(system_filter)}. {hint}" - ) - return None - pack_systems = filtered + if system_filter and not pack_systems: + reason = system_scope_error(config, platform_name, system_filter, target_name) + print(f" WARNING: {reason}") + return None if extras_only: # The part of a split that carries the core extras no platform system # owns: the platform's own files and trees went to the other parts. diff --git a/scripts/verify.py b/scripts/verify.py index 4d67492ce..bede4c9d2 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -55,7 +55,6 @@ from common import ( compute_hashes, expand_directory_entries, platform_declarations, - filter_systems_by_target, group_identical_platforms, list_emulator_profiles, list_system_ids, @@ -65,6 +64,8 @@ from common import ( load_platform_config, resolve_system_arg, select_systems, + platform_scope_systems, + system_scope_error, md5sum, require_yaml, resolve_local_file, @@ -1218,16 +1219,7 @@ def verify_platform( validation_index = _build_validation_index(platform_profiles) validation_owners = destination_owners(platform_profiles) - if not target_cores: - plat_cores = None - verify_systems = filter_systems_by_target( - config.get("systems", {}), - profiles, - target_cores, - platform_cores=plat_cores, - ) - if system_filter: - verify_systems = select_systems(verify_systems, system_filter) + verify_systems = platform_scope_systems(config, profiles, target_cores, system_filter) details = [] file_status: dict[str, str] = {} @@ -2884,6 +2876,18 @@ def _main(held: contextlib.ExitStack) -> None: except (FileNotFoundError, ValueError) as e: print(f"ERROR: {e}", file=sys.stderr) sys.exit(1) + # A system the target leaves no core for was reported 0/0 with exit 0, + # where generate_pack refuses the same request. + if platform_systems: + config = load_platform_config(args.platform, args.platforms_dir) + if not platform_scope_systems( + config, emu_profiles, target_cores_cache.get(args.platform), platform_systems + ): + reason = system_scope_error( + config, args.platform, platform_systems, args.target + ) + print(f"ERROR: {reason}", file=sys.stderr) + sys.exit(1) # Group identical platforms (same function as generate_pack) groups = group_identical_platforms( diff --git a/tests/test_target_verify.py b/tests/test_target_verify.py index 231bd8f65..602ea572f 100644 --- a/tests/test_target_verify.py +++ b/tests/test_target_verify.py @@ -85,5 +85,48 @@ class AMistypedTargetIsRefusedFirst(unittest.TestCase): self.assertIn("Unknown target 'swich'", err.getvalue()) +class ASystemTheTargetDropsIsNamedAsSuch(unittest.TestCase): + """`--platform retroarch --system sony-playstation-2 --target switch`: + verify printed 0/0 with exit 0 under --strict, and generate_pack said + RetroArch has no such system and suggested sony-playstation.""" + + PLATFORMS = str(REPO_ROOT / "platforms") + + def test_both_tools_read_one_scope(self): + from common import ( + load_emulator_profiles, load_platform_config, platform_scope_systems, + ) + + config = load_platform_config("retroarch", self.PLATFORMS) + profiles = load_emulator_profiles(str(REPO_ROOT / "emulators")) + switch = _target_cores_for("retroarch", "switch", self.PLATFORMS) + wanted = ["sony-playstation-2"] + self.assertEqual(platform_scope_systems(config, profiles, switch, wanted), {}) + self.assertEqual(list(platform_scope_systems(config, profiles, None, wanted)), wanted) + + def test_the_message_names_the_target(self): + from common import system_scope_error + + config = {"systems": {"sony-playstation-2": {}, "sony-playstation": {}}} + self.assertEqual( + system_scope_error(config, "retroarch", ["sony-playstation-2"], "switch"), + "no core retroarch runs on target switch emulates sony-playstation-2", + ) + undeclared = system_scope_error(config, "retroarch", ["nec-pc-98"], "switch") + self.assertTrue(undeclared.startswith("retroarch has no system matching nec-pc-98.")) + + def test_verify_refuses_it(self): + import subprocess + + proc = subprocess.run( + [sys.executable, "scripts/verify.py", "--platform", "retroarch", + "--system", "sony-playstation-2", "--target", "switch", "--strict"], + capture_output=True, text=True, cwd=str(REPO_ROOT), timeout=600, check=False, + ) + self.assertEqual(proc.returncode, 1, proc.stdout[-300:]) + self.assertIn("no core retroarch runs on target switch emulates sony-playstation-2", + proc.stderr) + + if __name__ == "__main__": unittest.main()