Files
libretro/tests/test_verify_listing.py
T
Abdessamad Derraz 8b952968d6 feat: add a --no-game-data pack narrowing
The install manifest marks each game data file "category": "game_data":
a platform entry only game data profile entries read, or a core file its
profile declares game data. Firmware carries no category. Every manifest
says "categorized": true so the installer can refuse an older list.

generate_pack.py --no-game-data cuts those files from the platform list
and the core profiles before the build, names each one with the cores that
read it as game data, and tags the pack _NoGameData through _narrowings.
verify.py and the pack check take the same cut, so --verify-packs and the
report describe the pack the builder wrote. Modes without a platform
(--emulator, --system alone, --from-md5) and --all-variants refuse it.
2026-10-22 06:19:05 +02:00

195 lines
7.8 KiB
Python

"""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
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
def _run(*argv: str) -> subprocess.CompletedProcess:
return subprocess.run(
[sys.executable, *argv], capture_output=True, check=False, 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)
class VerifyRefusesWhatAModeDoesNotRead(unittest.TestCase):
"""The mode x flag matrix generate_pack has, for verify.
--list-emulators --standalone printed the full list, --list-systems --json
printed columns, --platform --include-archived covered nothing more: each
with rc 0, each letting the caller believe the flag applied.
"""
MATRIX = (
(("--list-emulators",), "--standalone"),
(("--list-systems",), "--json"),
(("--list-targets", "--platform", "retroarch"), "--include-archived"),
(("--platform", "retroarch"), "--include-archived"),
(("--emulator", "handy"), "--include-archived"),
(("--platform", "retroarch"), "--standalone"),
# Game data is categorized against a platform's list and its cores.
(("--emulator", "handy"), "--no-game-data"),
(("--system", "atari-lynx"), "--no-game-data"),
(("--list-systems",), "--no-game-data"),
)
def test_each_combination_is_refused(self):
for mode, flag in self.MATRIX:
with self.subTest(mode=mode, flag=flag):
result = _run("scripts/verify.py", *mode, flag)
self.assertEqual(result.returncode, 2, result.stdout[-300:])
self.assertIn(flag, result.stderr)
def test_emulator_mode_reads_the_given_platforms_dir(self):
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import verify # noqa: PLC0415
seen: list[str] = []
def registry(path: str) -> dict:
seen.append(str(path))
return {}
db = {"files": {}, "indexes": {"by_name": {}, "by_md5": {}}}
with mock.patch.object(verify, "load_data_dir_registry", registry):
verify.verify_emulator(
["handy"], str(REPO_ROOT / "emulators"), db, platforms_dir="elsewhere"
)
self.assertEqual(seen, ["elsewhere"])
class VerifyAppliesWhatItAccepts(unittest.TestCase):
"""--verbose under --json was accepted in every mode and changed nothing;
the refusal matrix above could not see it, since nothing was refused."""
def test_verbose_changes_the_json_in_every_mode(self):
for mode in (("--platform", "bizhawk"), ("--emulator", "handy"),
("--system", "atari-lynx")):
with self.subTest(mode=mode):
plain = _run("scripts/verify.py", *mode, "--json")
verbose = _run("scripts/verify.py", *mode, "--json", "--verbose")
self.assertEqual((plain.returncode, verbose.returncode), (0, 0))
self.assertNotEqual(plain.stdout, verbose.stdout)
class EmulatorPackReadsThePlatformsDir(unittest.TestCase):
def test_the_registry_comes_from_the_given_dir(self):
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import generate_pack # noqa: PLC0415
seen: list[str] = []
def registry(path: str) -> dict:
seen.append(str(path))
return {}
db = {"files": {}, "indexes": {"by_name": {}, "by_md5": {}}}
out = REPO_ROOT / "tmp" / "emupack-registry"
with mock.patch.object(generate_pack, "load_data_dir_registry", registry):
generate_pack.generate_emulator_pack(
["handy"], str(REPO_ROOT / "emulators"), db, "bios", str(out),
zip_contents={}, platforms_dir="elsewhere",
)
for leftover in out.glob("*"):
leftover.unlink()
if out.exists():
out.rmdir()
self.assertEqual(seen, ["elsewhere"])
class CoreArchivesAreCounted(unittest.TestCase):
def test_a_missing_required_bios_zip_is_printed(self):
import contextlib # noqa: PLC0415
import io # noqa: PLC0415
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import verify # noqa: PLC0415
result = {"undeclared_files": [
{"name": "neogeo.zip", "category": "bios_zip", "required": True,
"in_repo": False, "emulator": "fbneo"},
]}
out = io.StringIO()
with contextlib.redirect_stdout(out):
verify._print_undeclared_section(result, verbose=False)
self.assertIn("1 required missing", out.getvalue())
self.assertIn("neogeo.zip", out.getvalue())
class ExistenceReportsWhatThePackReports(unittest.TestCase):
def test_a_contradicted_hash_is_a_discrepancy(self):
"""30 RetroArch entries printed DISCREPANCY in the pack, nothing in verify."""
import tempfile # noqa: PLC0415
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import verify # noqa: PLC0415
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
path = Path(tmp) / "aes.zip"
path.write_bytes(b"local dump")
entry = {"name": "aes.zip", "md5": "9d2ff2333e23910cf56f7f785609d36a"}
result = verify.verify_entry_existence(
entry, str(path), resolve_status="hash_mismatch", platform_display="RetroArch"
)
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()