Files
libretro/tests/test_verify_listing.py
T
Abdessamad Derraz 7c6af30a0c fix: name the file existence mode finds
verify_entry_existence returned no path, so RetroArch, Lakka and
RetroPie pages showed no Served by line. It now returns the resolved
path as the hash modes do. verify.py --all prints the same bytes and
its JSON is unchanged.
2026-10-23 07:16:05 +02:00

205 lines
8.4 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"])
def test_the_file_found_is_named(self):
"""RetroArch, Lakka and RetroPie pages showed no "Served by" line:
existence mode returned no path, where the hash modes return one."""
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import verify # noqa: PLC0415
found = verify.verify_entry_existence({"name": "a.bin"}, "bios/X/Y/a.bin")
self.assertEqual(found["path"], "bios/X/Y/a.bin")
self.assertNotIn("path", verify.verify_entry_existence({"name": "a.bin"}, None))
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()