diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index 929bb348..2ba1d25c 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -1860,23 +1860,22 @@ def generate_md5_pack( emu_display = "" if emulator_name and emulators_dir: profiles = load_emulator_profiles(emulators_dir, skip_aliases=False) - if emulator_name in profiles: - # The same gate as --emulator: a core with no standalone build - # has no standalone layout, and a pack named for one would be - # the libretro pack under a name that promises otherwise. - try: - (_name, profile), = select_emulator_profiles( - [emulator_name], profiles, standalone - ) - except ProfileSelectionError as exc: - print(f"Error: {exc}", file=sys.stderr) - return None - emu_display = profile.get("emulator", emulator_name) - emu_pack_structure = profile.get("pack_structure") - for fe in profile.get("files", []): - plat_file_index[fe.get("name", "").lower()] = fe - for alias in fe.get("aliases", []): - plat_file_index[alias.lower()] = fe + # The same gate as --emulator, unknown names included: a typo or a + # buildbot name gave a generic pack in the layout of no emulator. A + # core with no standalone build has no standalone layout either. + try: + (_name, profile), = select_emulator_profiles( + [emulator_name], profiles, standalone + ) + except ProfileSelectionError as exc: + print(f"Error: {exc}", file=sys.stderr) + return None + emu_display = profile.get("emulator", emulator_name) + emu_pack_structure = profile.get("pack_structure") + for fe in profile.get("files", []): + plat_file_index[fe.get("name", "").lower()] = fe + for alias in fe.get("aliases", []): + plat_file_index[alias.lower()] = fe context_name = plat_display if platform_name else (emu_display or "Custom") # --standalone changes the destination layout, so a run with it must not diff --git a/tests/test_md5_pack_context.py b/tests/test_md5_pack_context.py new file mode 100644 index 00000000..4b9fa92a --- /dev/null +++ b/tests/test_md5_pack_context.py @@ -0,0 +1,54 @@ +"""A custom pack built from hashes follows the emulator it names, or refuses. + +`--from-md5 --emulator` accepted any name: an unknown one produced a generic +pack in the layout of no emulator, rc 0, where `--emulator` alone refuses it. +With a known one and `--standalone`, a profile without pack_structure kept +the bare file names under a pack called Standalone. +""" + +from __future__ import annotations + +import json +import subprocess +import sys +import tempfile +import unittest +import zipfile +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent + + +def _pack(*options: str) -> tuple[subprocess.CompletedProcess, list[str]]: + with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as out: + proc = subprocess.run( + [sys.executable, "scripts/generate_pack.py", *options, "--offline", + "--output-dir", out], + cwd=REPO_ROOT, capture_output=True, text=True, timeout=600, check=False, + ) + names = [] + for archive in Path(out).glob("*.zip"): + with zipfile.ZipFile(archive) as zf: + names.extend(zf.namelist()) + return proc, names + + +class TheEmulatorContextIsChecked(unittest.TestCase): + @classmethod + def setUpClass(cls): + db_path = REPO_ROOT / "database.json" + if not db_path.exists(): + raise unittest.SkipTest("database.json is not built") + (REPO_ROOT / "tmp").mkdir(exist_ok=True) + cls.db = json.loads(db_path.read_text(encoding="utf-8")) + + def test_an_unknown_emulator_is_refused(self): + md5 = next(iter(self.db["files"].values()))["md5"] + proc, names = _pack("--from-md5", md5, "--emulator", "no_such_core") + self.assertNotEqual(proc.returncode, 0, proc.stdout) + self.assertIn("not found", proc.stderr) + self.assertEqual(names, []) + + +if __name__ == "__main__": + unittest.main()