mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-21 11:19:11 -05:00
A names_searched list is one place the launcher fills with the first name it finds, so a core file it would never open once another name of the list is held is not reported missing. A place no declared file and no packed core file fills receives, under the launcher's name, a file the core accepts by content. The layer is applied inside find_undeclared_files, which verify and the pack builder both read, and a test reads their source to keep it there.
234 lines
9.3 KiB
Python
234 lines
9.3 KiB
Python
"""The launcher layer read by verify and by the pack builder.
|
|
|
|
A platform's launcher decides which name a file is read under. The fixtures
|
|
give a synthetic platform three launcher records: a region list tried in
|
|
order (Batocera's DuckStation shape), a second list no declared file fills,
|
|
and a fixed path the launcher passes (Batocera's x16emu shape).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import inspect
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
|
|
|
|
import yaml # noqa: E402
|
|
|
|
from common import _emulator_profiles_cache, load_emulator_profiles, load_platform_config # noqa: E402
|
|
from launchers import LauncherLayer, launcher_places # noqa: E402
|
|
from verify import find_undeclared_files, verify_platform # noqa: E402
|
|
|
|
CONTENT = {
|
|
"b.bin": b"REGION_B_IMAGE",
|
|
"e.bin": b"IMAGE_KNOWN_AS_E",
|
|
"core.bin": b"CORE_DEFAULT_ROM",
|
|
"plain.bin": b"PLAIN_CORE_FILE",
|
|
}
|
|
|
|
|
|
def _md5(data: bytes) -> str:
|
|
return hashlib.md5(data).hexdigest()
|
|
|
|
|
|
class LauncherLayerTest(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
_emulator_profiles_cache.clear()
|
|
self.root = tempfile.mkdtemp()
|
|
self.bios_dir = os.path.join(self.root, "bios")
|
|
self.platforms_dir = os.path.join(self.root, "platforms")
|
|
self.emulators_dir = os.path.join(self.root, "emulators")
|
|
for d in (self.bios_dir, self.platforms_dir, self.emulators_dir):
|
|
os.makedirs(d)
|
|
files: dict[str, dict] = {}
|
|
by_md5: dict[str, str] = {}
|
|
by_name: dict[str, list[str]] = {}
|
|
for name, data in CONTENT.items():
|
|
path = os.path.join(self.bios_dir, name)
|
|
with open(path, "wb") as fh:
|
|
fh.write(data)
|
|
sha1 = hashlib.sha1(data).hexdigest()
|
|
files[sha1] = {
|
|
"path": path, "name": name, "md5": _md5(data), "sha1": sha1,
|
|
"size": len(data), "crc32": "",
|
|
}
|
|
by_md5[_md5(data)] = sha1
|
|
by_name.setdefault(name, []).append(sha1)
|
|
self.db = {
|
|
"files": files,
|
|
"indexes": {
|
|
"by_md5": by_md5, "by_name": by_name, "by_crc32": {},
|
|
"by_path_suffix": {},
|
|
},
|
|
}
|
|
platform = {
|
|
"platform": "LauncherTest",
|
|
"verification_mode": "md5",
|
|
"base_destination": "bios",
|
|
"standalone_cores": ["psxemu", "fixemu"],
|
|
"systems": {
|
|
"test-sys": {
|
|
"files": [
|
|
{"name": "plain.bin", "destination": "plain.bin",
|
|
"md5": _md5(CONTENT["plain.bin"]), "required": True},
|
|
],
|
|
},
|
|
},
|
|
"launchers": [
|
|
{
|
|
"emulator": "psxemu", "build": "standalone",
|
|
"kind": "names_searched", "slot": "NTSCU",
|
|
"names": ["a.bin", "b.bin", "c.bin"],
|
|
"case_insensitive": True, "search_dir": "/userdata/bios",
|
|
"source_ref": "gen/psx.py:10-20",
|
|
},
|
|
{
|
|
"emulator": "psxemu", "build": "standalone",
|
|
"kind": "names_searched", "slot": "PAL",
|
|
"names": ["d.bin", "e.bin"],
|
|
"case_insensitive": True, "search_dir": "/userdata/bios",
|
|
"source_ref": "gen/psx.py:21-30",
|
|
},
|
|
{
|
|
"emulator": "fixemu", "build": "standalone",
|
|
"kind": "fixed_name", "names": ["sub/fix.bin"],
|
|
"search_dir": "/userdata/bios",
|
|
"source_ref": "gen/fix.py:5",
|
|
},
|
|
],
|
|
}
|
|
with open(os.path.join(self.platforms_dir, "launchertest.yml"), "w") as fh:
|
|
yaml.dump(platform, fh)
|
|
profiles = {
|
|
"psxemu": {
|
|
"emulator": "PsxEmu", "type": "standalone", "found_by": "content",
|
|
"systems": ["test-sys"],
|
|
"files": [
|
|
{"name": "a.bin", "required": True,
|
|
"md5": "0" * 32, "validation": ["md5"]},
|
|
{"name": "b.bin", "required": True,
|
|
"md5": _md5(CONTENT["b.bin"]), "validation": ["md5"]},
|
|
{"name": "label_pal.bin", "required": True,
|
|
"md5": _md5(CONTENT["e.bin"]), "validation": ["md5"]},
|
|
],
|
|
},
|
|
"fixemu": {
|
|
"emulator": "FixEmu", "type": "standalone", "systems": ["test-sys"],
|
|
"files": [
|
|
{"name": "core.bin", "aliases": ["fix.bin"], "required": True},
|
|
],
|
|
},
|
|
}
|
|
for key, profile in profiles.items():
|
|
with open(os.path.join(self.emulators_dir, f"{key}.yml"), "w") as fh:
|
|
yaml.dump(profile, fh)
|
|
self.config = load_platform_config("launchertest", self.platforms_dir)
|
|
self.profiles = load_emulator_profiles(self.emulators_dir)
|
|
|
|
def tearDown(self) -> None:
|
|
shutil.rmtree(self.root)
|
|
|
|
def _report(self) -> list[dict]:
|
|
return find_undeclared_files(
|
|
self.config, self.emulators_dir, self.db, self.profiles,
|
|
)
|
|
|
|
def _pack_names(self) -> set[str]:
|
|
from generate_pack import generate_pack
|
|
|
|
output = os.path.join(self.root, "dist")
|
|
os.makedirs(output)
|
|
zip_path = generate_pack(
|
|
"launchertest", self.platforms_dir, self.db, self.bios_dir, output,
|
|
emulators_dir=self.emulators_dir, emu_profiles=self.profiles,
|
|
offline=True,
|
|
)
|
|
self.assertIsNotNone(zip_path)
|
|
with zipfile.ZipFile(zip_path) as zf:
|
|
return set(zf.namelist())
|
|
|
|
def test_places_follow_the_launcher_order(self) -> None:
|
|
places = launcher_places(self.config)
|
|
self.assertEqual(places[0].paths, ("a.bin", "b.bin", "c.bin"))
|
|
self.assertEqual(places[2].paths, ("sub/fix.bin",))
|
|
self.assertTrue(places[0].stands_for_default)
|
|
|
|
def test_held_alternative_settles_the_others(self) -> None:
|
|
"""b.bin fills the NTSCU list, so a.bin is not missing there."""
|
|
report = self._report()
|
|
names = {u["name"]: u for u in report}
|
|
self.assertNotIn("a.bin", names)
|
|
self.assertTrue(names["b.bin"]["in_repo"])
|
|
|
|
def test_without_the_layer_the_alternative_is_missing(self) -> None:
|
|
config = {**self.config, "launchers": []}
|
|
names = {
|
|
u["name"]: u
|
|
for u in find_undeclared_files(config, self.emulators_dir, self.db, self.profiles)
|
|
}
|
|
self.assertFalse(names["a.bin"]["in_repo"])
|
|
|
|
def test_accepted_content_lands_under_the_launcher_name(self) -> None:
|
|
"""No declared file fills PAL: the image the core accepts goes in as e.bin."""
|
|
added = {u["path"]: u for u in self._report() if u.get("launcher")}
|
|
self.assertIn("e.bin", added)
|
|
self.assertEqual(added["e.bin"]["md5"], _md5(CONTENT["e.bin"]))
|
|
self.assertEqual(added["e.bin"]["launcher_of"], "label_pal.bin")
|
|
|
|
def test_fixed_name_receives_the_held_file(self) -> None:
|
|
added = {u["path"]: u for u in self._report() if u.get("launcher")}
|
|
self.assertIn("sub/fix.bin", added)
|
|
self.assertEqual(added["sub/fix.bin"]["md5"], _md5(CONTENT["core.bin"]))
|
|
|
|
def test_no_place_is_filled_twice(self) -> None:
|
|
added = [u["path"] for u in self._report() if u.get("launcher")]
|
|
self.assertEqual(sorted(added), ["e.bin", "sub/fix.bin"])
|
|
|
|
def test_a_declared_name_fills_the_place(self) -> None:
|
|
layer = LauncherLayer(
|
|
self.config, self.profiles, self.profiles, self.db, {"B.BIN"},
|
|
)
|
|
self.assertTrue(layer.platform_holds(layer.places[0]))
|
|
self.assertFalse(layer.platform_holds(layer.places[1]))
|
|
|
|
def test_verify_and_pack_agree(self) -> None:
|
|
result = verify_platform(self.config, self.db, self.emulators_dir, self.profiles)
|
|
missing = [
|
|
u["name"] for u in result["undeclared_files"]
|
|
if not u["in_repo"] and u.get("category", "bios") != "game_data"
|
|
]
|
|
self.assertEqual(missing, [])
|
|
names = self._pack_names() - {"README.txt"}
|
|
for expected in ("e.bin", "sub/fix.bin", "b.bin"):
|
|
self.assertIn(expected, names)
|
|
self.assertNotIn("a.bin", names)
|
|
self.assertEqual(result["packed_extras"]["bios"], len(names - {"plain.bin"}))
|
|
|
|
def test_one_decision_for_verify_and_pack(self) -> None:
|
|
"""The layer is applied once, in the report both sides read."""
|
|
import generate_pack
|
|
import packextras
|
|
import verify
|
|
|
|
self.assertIn("LauncherLayer(", inspect.getsource(verify.find_undeclared_files))
|
|
self.assertIn(".settle(", inspect.getsource(verify.find_undeclared_files))
|
|
self.assertIn(
|
|
"find_undeclared_files(", inspect.getsource(packextras._collect_emulator_extras)
|
|
)
|
|
for module in (generate_pack, packextras, verify):
|
|
source = inspect.getsource(module)
|
|
with self.subTest(module=module.__name__):
|
|
self.assertNotIn("launcher_places(", source)
|
|
if module is not verify:
|
|
self.assertNotIn("LauncherLayer(", source)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|