mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 12:33:53 -06:00
With no required firmware among the bundled entries the line read '20 (20 more, --verbose lists them)'. It now reads '20 (--verbose lists them)', and reading-reports.md quotes the run as it prints.
334 lines
14 KiB
Python
334 lines
14 KiB
Python
"""verify.py lists what generate_pack.py lists for the same request."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
|
|
|
|
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: each with rc 0, each letting the caller believe the flag
|
|
applied.
|
|
"""
|
|
|
|
MATRIX = (
|
|
(("--list-emulators",), "--standalone"),
|
|
(("--list-systems",), "--json"),
|
|
(("--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)
|
|
|
|
def test_the_lock_stack_is_not_rebound(self):
|
|
"""The --destinations branch reused `held`, the ExitStack that holds
|
|
the data directory locks, for its dict: a later enter_context in
|
|
that mode raised AttributeError."""
|
|
import ast
|
|
|
|
tree = ast.parse((REPO_ROOT / "scripts" / "verify.py").read_text(encoding="utf-8"))
|
|
body = next(n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name == "_main")
|
|
stack = body.args.args[0].arg
|
|
rebound = [
|
|
node.lineno for node in ast.walk(body)
|
|
if isinstance(node, (ast.Assign, ast.AnnAssign, ast.AugAssign))
|
|
for target in (node.targets if isinstance(node, ast.Assign) else [node.target])
|
|
if isinstance(target, ast.Name) and target.id == stack
|
|
]
|
|
self.assertEqual(rebound, [])
|
|
|
|
|
|
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())
|
|
|
|
|
|
class BundledGameDataIsNotMissing(unittest.TestCase):
|
|
"""cross_reference put Abuse's abuse.lsp, defaults.prp and english.lsp
|
|
under bundled while verify called them missing game data: 24 of the 427
|
|
Batocera counted."""
|
|
|
|
def test_bundled_game_data_is_counted_apart(self):
|
|
import contextlib
|
|
import io
|
|
|
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
import verify # noqa: PLC0415
|
|
|
|
result = {"undeclared_files": [
|
|
{"name": "abuse.lsp", "emulator": "Abuse", "required": True,
|
|
"in_repo": False, "category": "game_data", "bundled": True,
|
|
"in_pack": False},
|
|
{"name": "level.dat", "emulator": "engine", "required": True,
|
|
"in_repo": False, "category": "game_data"},
|
|
]}
|
|
with contextlib.redirect_stdout(io.StringIO()) as out:
|
|
verify._print_undeclared_section(result, verbose=True)
|
|
self.assertIn("Game data: 0 in pack, 1 missing", out.getvalue())
|
|
self.assertNotIn("needs abuse.lsp", out.getvalue())
|
|
self.assertIn("Bundled with their emulator, not collected: 1 (abuse.lsp)",
|
|
out.getvalue())
|
|
|
|
|
|
class TheBundledLineSaysWhatItLeavesOut(unittest.TestCase):
|
|
"""'Bundled with their emulator, not collected: 154' listed 14 names,
|
|
the required firmware, and nothing said 140 more existed."""
|
|
|
|
def test_the_hidden_names_are_counted(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": name, "emulator": "e", "required": required, "in_repo": False,
|
|
"bundled": True, "in_pack": False}
|
|
for name, required in (("fw.bin", True), ("a.frag", False), ("b.dll", False))
|
|
]}
|
|
with contextlib.redirect_stdout(io.StringIO()) as quiet:
|
|
verify._print_undeclared_section(result, verbose=False)
|
|
self.assertIn(
|
|
"Bundled with their emulator, not collected: 3 "
|
|
"(fw.bin, 2 more, --verbose lists them)",
|
|
quiet.getvalue(),
|
|
)
|
|
with contextlib.redirect_stdout(io.StringIO()) as verbose:
|
|
verify._print_undeclared_section(result, verbose=True)
|
|
self.assertIn(
|
|
"Bundled with their emulator, not collected: 3 (a.frag, b.dll, fw.bin)",
|
|
verbose.getvalue(),
|
|
)
|
|
result["undeclared_files"] = result["undeclared_files"][1:]
|
|
with contextlib.redirect_stdout(io.StringIO()) as unnamed:
|
|
verify._print_undeclared_section(result, verbose=False)
|
|
self.assertIn("Bundled with their emulator, not collected: 2 (--verbose lists them)",
|
|
unnamed.getvalue())
|
|
|
|
|
|
class TheCheckLineSaysWhatItIs(unittest.TestCase):
|
|
"""Without --verbose each missing core file was followed by a bare
|
|
'[size]' line, nine of them in verify --all."""
|
|
|
|
def test_the_checks_read_as_the_verbose_line(self):
|
|
import contextlib # noqa: PLC0415
|
|
import io # noqa: PLC0415
|
|
|
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
import verify # noqa: PLC0415
|
|
|
|
entry = {"name": "FBASIC30.ROM", "emulator": "XM7", "checks": ["size"]}
|
|
with contextlib.redirect_stdout(io.StringIO()) as out:
|
|
verify._print_undeclared_entry(entry, "MISSING (required)", False, set())
|
|
self.assertEqual(out.getvalue().splitlines(), [
|
|
" MISSING (required): XM7 needs FBASIC30.ROM",
|
|
" validates size",
|
|
])
|
|
|
|
|
|
class CoreCopiesWithoutAGapEntryAreCounted(unittest.TestCase):
|
|
"""verify --platform recalbox --system nec-pc-engine printed no Core
|
|
files line while the pack held six geargrafx syscard copies: the gap
|
|
report was empty, and the section returned before reading the pack's
|
|
count."""
|
|
|
|
def test_the_packed_count_is_printed(self):
|
|
import contextlib # noqa: PLC0415
|
|
import io # noqa: PLC0415
|
|
|
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
import verify # noqa: PLC0415
|
|
|
|
for packed, expected in (({"bios": 6, "game_data": 0}, "Core files: 6 in pack"),
|
|
({"bios": 0, "game_data": 3}, "Game data: 3 in pack")):
|
|
out = io.StringIO()
|
|
with contextlib.redirect_stdout(out):
|
|
verify._print_undeclared_section(
|
|
{"undeclared_files": [], "packed_extras": packed}, verbose=False
|
|
)
|
|
with self.subTest(packed=packed):
|
|
self.assertIn(expected, out.getvalue())
|
|
out = io.StringIO()
|
|
with contextlib.redirect_stdout(out):
|
|
verify._print_undeclared_section(
|
|
{"undeclared_files": [], "packed_extras": {"bios": 0, "game_data": 0}}, False
|
|
)
|
|
self.assertEqual(out.getvalue(), "")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|