Files
libretro/tests/test_pack_core_extras.py
T
Abdessamad Derraz 7ca6adca56 fix: check narrowed packs against their own list
After --all-variants, --verify-packs printed "hashes checked during the
build" for the platform-only, truth and required-only packs and compared
none of them, so one missing a file its narrowed list requires passed.
Each pack is now held to the platform list narrowed the way it was built:
source and required_only, read from its name, reach the baseline, the
region grouping and the replay of the core extras.
2026-10-21 11:17:30 +02:00

193 lines
8.5 KiB
Python

"""The pack integrity check counts the core files the builder places.
It rebuilt the core extras with an empty set of taken paths and tested
presence alone. On a Recalbox pack it credited 68 core files at a path the
platform's own file holds, 15 of them other bytes (fuse/trdos.rom, the PS1
BIOS DuckStation reads), and printed a core count that contradicted the
builder's in the same run.
"""
from __future__ import annotations
import hashlib
import os
import sys
import tempfile
import unittest
import zipfile
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import packverify # noqa: E402
def _held(files: dict, by_name: dict, path: str, payload: bytes) -> str:
Path(path).parent.mkdir(parents=True, exist_ok=True)
Path(path).write_bytes(payload)
sha1 = hashlib.sha1(payload).hexdigest()
name = Path(path).name
files[sha1] = {"path": path, "name": name, "sha1": sha1,
"md5": hashlib.md5(payload).hexdigest(), "size": len(payload)}
by_name.setdefault(name, []).append(sha1)
return sha1
class CoreFilesAsTheBuilderPlacesThem(unittest.TestCase):
def setUp(self):
tmp = tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp")
self.addCleanup(tmp.cleanup)
previous = os.getcwd()
os.chdir(tmp.name)
self.addCleanup(os.chdir, previous)
files: dict = {}
by_name: dict = {}
_held(files, by_name, "bios/plat/trdos.rom", b"platform dump")
core_trdos = _held(files, by_name, "bios/core/trdos.rom", b"core dump")
_held(files, by_name, "bios/core/other.rom", b"core file")
self.db = {"files": files, "indexes": {
"by_name": by_name, "by_md5": {e["md5"]: s for s, e in files.items()},
"by_path_suffix": {}, "by_crc32": {}, "by_sha256": {}}}
Path("platforms").mkdir()
config = {"platform": "P", "verification_mode": "existence", "cores": ["fuse"],
"systems": {"zx": {"files": [
{"name": "trdos.rom", "destination": "fuse/trdos.rom"}]}}}
Path("platforms/p.yml").write_text(yaml.safe_dump(config), encoding="utf-8")
Path("emulators").mkdir()
self.profiles = {"fuse": {
"emulator": "fuse", "type": "libretro", "systems": ["zx"], "files": [
{"name": "trdos.rom", "path": "fuse/trdos.rom", "sha1": core_trdos},
{"name": "other.rom", "path": "core/other.rom"},
]}}
def _check(self, other: bytes) -> tuple:
with zipfile.ZipFile("pack.zip", "w") as zf:
zf.writestr("fuse/trdos.rom", b"platform dump")
zf.writestr("core/other.rom", other)
return packverify.verify_pack_against_platform(
"pack.zip", "p", "platforms", self.db, emulators_dir="emulators",
emu_profiles=self.profiles,
)
def test_a_path_the_platform_file_holds_is_not_a_core_file(self):
ok, _checked, _present, errors, _bc, _bp, core_checked, core_present, _ex = (
self._check(b"core file")
)
self.assertTrue(ok, errors)
self.assertEqual((core_checked, core_present), (1, 1))
def test_other_bytes_at_a_core_path_are_an_error(self):
ok, _checked, _present, errors, *_rest = self._check(b"something else")
self.assertFalse(ok)
self.assertTrue(
any(e.startswith("core file differs: core/other.rom") for e in errors), errors
)
class NarrowedPacksAgainstTheirOwnList(unittest.TestCase):
"""--verify-packs after --all-variants said "hashes checked during the
build" for the five narrowed packs and compared none of them: a narrowed
pack missing a file its narrowed list requires passed. Each variant is
now held to the platform list narrowed the way it was built."""
def setUp(self):
import common
import generate_db
import generate_pack
self.gp = generate_pack
tmp = tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp")
self.addCleanup(tmp.cleanup)
previous = os.getcwd()
os.chdir(tmp.name)
self.addCleanup(os.chdir, previous)
files: dict = {}
for rel in ("bios/p/req.bin", "bios/p/opt.bin", "bios/c/core.bin", "bios/c/extra.bin"):
payload = rel.encode()
Path(rel).parent.mkdir(parents=True, exist_ok=True)
Path(rel).write_bytes(payload)
sha1 = hashlib.sha1(payload).hexdigest()
files[sha1] = {"path": rel, "name": Path(rel).name, "size": len(payload),
"sha1": sha1, "md5": hashlib.md5(payload).hexdigest(),
"sha256": hashlib.sha256(payload).hexdigest(),
"crc32": "00000001"}
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
Path("platforms").mkdir()
config = {"platform": "P", "verification_mode": "existence", "cores": ["c"],
"systems": {"s": {"files": [
{"name": "req.bin", "destination": "req.bin", "required": True},
{"name": "opt.bin", "destination": "opt.bin", "required": False},
]}}}
Path("platforms/p.yml").write_text(yaml.safe_dump(config), encoding="utf-8")
Path("platforms/_registry.yml").write_text(
yaml.safe_dump({"platforms": {"p": {"status": "active"}}}), encoding="utf-8"
)
Path("emulators").mkdir()
Path("emulators/c.yml").write_text(yaml.safe_dump({
"emulator": "c", "type": "libretro", "systems": ["s"], "files": [
{"name": "core.bin", "path": "core.bin", "required": True},
{"name": "extra.bin", "path": "extra.bin", "required": False},
]}), encoding="utf-8")
common._platform_config_cache.clear()
common._emulator_profiles_cache.clear()
self.addCleanup(common._platform_config_cache.clear)
self.addCleanup(common._emulator_profiles_cache.clear)
self.profiles = common.load_emulator_profiles("emulators")
def _build(self, source: str, required: bool) -> tuple[str, set[str]]:
zip_path = self.gp.generate_pack(
"p", "platforms", self.db, "bios", "out", emulators_dir="emulators",
emu_profiles=self.profiles, source=source, required_only=required,
offline=True,
)
with zipfile.ZipFile(zip_path) as zf:
return zip_path, set(zf.namelist()) - {"README.txt"}
def _check(self, zip_path: str, source: str, required: bool) -> tuple:
return packverify.verify_pack_against_platform(
zip_path, "p", "platforms", self.db, emulators_dir="emulators",
emu_profiles=self.profiles, source=source, required_only=required,
)
def test_every_variant_passes_as_built(self):
expected = {
("full", False): {"req.bin", "opt.bin", "core.bin", "extra.bin"},
("full", True): {"req.bin", "core.bin"},
("platform", False): {"req.bin", "opt.bin"},
("platform", True): {"req.bin"},
("truth", False): {"core.bin", "extra.bin"},
("truth", True): {"core.bin"},
}
for (source, required), members in expected.items():
with self.subTest(source=source, required=required):
zip_path, held = self._build(source, required)
self.assertEqual(held, members)
ok, _c, _p, errors, *_rest = self._check(zip_path, source, required)
self.assertTrue(ok, errors)
self.assertEqual(
self.gp._pack_variant(os.path.basename(zip_path)), (source, required)
)
def test_a_narrowed_pack_missing_a_required_file_fails(self):
for source, required, lost in (
("full", True, "core.bin"), ("platform", True, "req.bin"),
("truth", False, "extra.bin"), ("truth", True, "core.bin"),
):
with self.subTest(source=source, required=required):
zip_path, held = self._build(source, required)
with zipfile.ZipFile(zip_path) as zf:
kept = {n: zf.read(n) for n in held if n != lost}
with zipfile.ZipFile(zip_path, "w") as zf:
for name, data in kept.items():
zf.writestr(name, data)
ok, _c, _p, errors, *_rest = self._check(zip_path, source, required)
self.assertFalse(ok)
self.assertTrue(any(lost in e for e in errors), errors)
if __name__ == "__main__":
unittest.main()