mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-18 09:23:26 -05:00
fix: let shared groups follow the profiles
This commit is contained in:
1 parent
dda057f45f
commit
768f9b583f
2 files changed
+62
-211
No files matched your search
+12
-19
@@ -12,70 +12,63 @@ shared_groups:
|
||||
np2kai:
|
||||
- name: bios.rom
|
||||
destination: np2kai/bios.rom
|
||||
required: true
|
||||
required: false
|
||||
sha1: "76a7360502fa0296ea93b4c537174610a834d367"
|
||||
md5: "cd237e16e7e77c06bb58540e9e9fca68"
|
||||
crc32: "0a682b93"
|
||||
size: 98304
|
||||
- name: itf.rom
|
||||
destination: np2kai/itf.rom
|
||||
required: true
|
||||
sha1: "a2fb11c000ed7c976520622cfb7940ed6ddc904e"
|
||||
md5: "72ea51443070f0e9212bfc9b793ee28e"
|
||||
crc32: "c1815325"
|
||||
size: 32768
|
||||
- name: sound.rom
|
||||
destination: np2kai/sound.rom
|
||||
required: true
|
||||
required: false
|
||||
sha1: "d5dbc4fea3b8367024d363f5351baecd6adcd8ef"
|
||||
md5: "524473c1a5a03b17e21d86a0408ff827"
|
||||
crc32: "fe9f57f2"
|
||||
size: 16384
|
||||
- name: font.bmp
|
||||
destination: np2kai/font.bmp
|
||||
required: true
|
||||
required: false
|
||||
sha1: "b4f14e58030ed40fff2dc312b58ea4440bdf8cc5"
|
||||
md5: "7da1e5b7c482d4108d22a5b09631d967"
|
||||
crc32: "d271798b"
|
||||
size: 524350
|
||||
- name: 2608_bd.wav
|
||||
destination: np2kai/2608_bd.wav
|
||||
required: true
|
||||
required: false
|
||||
sha1: "0a56c142ef40cec50f3ee56a6e42d0029c9e2818"
|
||||
md5: "d94546e70f17fd899be8df3544ab6cbb"
|
||||
crc32: "bb038714"
|
||||
size: 19192
|
||||
- name: 2608_sd.wav
|
||||
destination: np2kai/2608_sd.wav
|
||||
required: true
|
||||
required: false
|
||||
sha1: "3c79663ef74c0b0439d13351326eb1c52a657008"
|
||||
md5: "d71004351c8bbfdad53b18222c061d49"
|
||||
crc32: "542cc58c"
|
||||
size: 15558
|
||||
- name: 2608_top.wav
|
||||
destination: np2kai/2608_top.wav
|
||||
required: true
|
||||
required: false
|
||||
sha1: "aa4a8f766a86b830687d5083fd3b9db0652f46fc"
|
||||
md5: "593cff6597ab9380d822b8f824fd2c28"
|
||||
crc32: "ab73a296"
|
||||
size: 57016
|
||||
- name: 2608_hh.wav
|
||||
destination: np2kai/2608_hh.wav
|
||||
required: true
|
||||
required: false
|
||||
sha1: "12f676cef249b82480b6f19c454e234b435ca7b6"
|
||||
md5: "08c54a0c1f774a5538a848a6665a34b4"
|
||||
crc32: "9898b372"
|
||||
size: 36722
|
||||
- name: 2608_tom.wav
|
||||
destination: np2kai/2608_tom.wav
|
||||
required: true
|
||||
required: false
|
||||
sha1: "9513fb4a3f41e75a972a273a5104cbd834c1e2c5"
|
||||
md5: "96a4ead13f364734f79b0c58af2f0e1f"
|
||||
crc32: "93c57bba"
|
||||
size: 23092
|
||||
- name: 2608_rim.wav
|
||||
destination: np2kai/2608_rim.wav
|
||||
required: true
|
||||
required: false
|
||||
sha1: "c65592330c9dd84011151daed52f9aec926b7e56"
|
||||
md5: "465ea0768b27da404aec45dfc501404b"
|
||||
crc32: "638864c1"
|
||||
@@ -100,13 +93,13 @@ shared_groups:
|
||||
md5: "d675a2ca186c6efcd6277b835de4c7e5"
|
||||
- name: N88EXT1.ROM
|
||||
destination: quasi88/N88EXT1.ROM
|
||||
required: true
|
||||
required: false
|
||||
md5: "e844534dfe5744b381444dbe61ef1b66"
|
||||
- name: N88EXT2.ROM
|
||||
destination: quasi88/N88EXT2.ROM
|
||||
required: true
|
||||
required: false
|
||||
md5: "6548fa45061274dee1ea8ae1e9e93910"
|
||||
- name: N88EXT3.ROM
|
||||
destination: quasi88/N88EXT3.ROM
|
||||
required: true
|
||||
required: false
|
||||
md5: "fc4b76a402ba501e6ba6de4b3e8b4273"
|
||||
+50
-192
@@ -1,9 +1,10 @@
|
||||
"""A shared group adds files, never a second name for a native one.
|
||||
"""_shared.yml is curation of the code, not of System.dat.
|
||||
|
||||
The quasi88 group declared N88SUB.ROM beside the disk.rom System.dat
|
||||
already names for nec-pc-88, same bytes: the RetroArch pack carried the
|
||||
2 KB ROM twice under quasi88/, and the group read as twelve required files
|
||||
where four did the work. Alternative names are aliases in the profile.
|
||||
The np2kai group declared itf.rom required, a file the core never opens
|
||||
(bios.c:579 sits behind the #else of BIOS_SIMULATE, always defined), and
|
||||
bios.rom, sound.rom and the rhythm WAVs required where the code falls back.
|
||||
System.dat masked the group; a rescrape dropping itf.rom would have brought
|
||||
it back as a required file of RetroArch, Lakka and RetroPie.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -12,201 +13,58 @@ import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
|
||||
from common import list_registered_platforms, load_platform_config # noqa: E402
|
||||
|
||||
PLATFORMS = str(REPO_ROOT / "platforms")
|
||||
from common import load_emulator_profiles # noqa: E402
|
||||
from safeparse import yaml_load # noqa: E402
|
||||
|
||||
|
||||
def _md5s(entry: dict) -> set[str]:
|
||||
raw = entry.get("md5") or ""
|
||||
values = raw if isinstance(raw, list) else str(raw).split(",")
|
||||
return {v.strip().lower() for v in values if v and v.strip()}
|
||||
|
||||
|
||||
class SharedGroupsAddOnly(unittest.TestCase):
|
||||
def test_no_shared_file_repeats_a_native_file_of_the_same_directory(self):
|
||||
shared = yaml.safe_load((REPO_ROOT / "platforms" / "_shared.yml").read_text())
|
||||
groups = shared.get("shared_groups") or {}
|
||||
repeated = []
|
||||
for platform in list_registered_platforms(PLATFORMS, include_archived=True):
|
||||
config = load_platform_config(platform, PLATFORMS)
|
||||
for system in config.get("systems", {}).values():
|
||||
included = [
|
||||
fe for group in system.get("includes", []) for fe in groups.get(group, [])
|
||||
]
|
||||
if not included:
|
||||
class SharedGroupsFollowTheProfiles(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
with open(REPO_ROOT / "platforms" / "_shared.yml", encoding="utf-8") as f:
|
||||
cls.groups = (yaml_load(f) or {}).get("shared_groups", {})
|
||||
profiles = load_emulator_profiles(str(REPO_ROOT / "emulators"))
|
||||
cls.by_destination: dict[str, list[dict]] = {}
|
||||
for profile in profiles.values():
|
||||
for entry in profile.get("files") or []:
|
||||
destination = entry.get("path") or entry.get("name")
|
||||
if not destination:
|
||||
continue
|
||||
group_keys = {
|
||||
(fe.get("name"), fe.get("destination", fe.get("name"))) for fe in included
|
||||
}
|
||||
native: dict[tuple[str, str], str] = {}
|
||||
for fe in system.get("files", []):
|
||||
if (fe.get("name"), fe.get("destination", fe.get("name"))) in group_keys:
|
||||
continue
|
||||
dest = fe.get("destination") or fe.get("name", "")
|
||||
directory = dest.rsplit("/", 1)[0] if "/" in dest else ""
|
||||
for md5 in _md5s(fe):
|
||||
native.setdefault((directory, md5), fe.get("name", ""))
|
||||
for fe in included:
|
||||
dest = fe.get("destination") or fe.get("name", "")
|
||||
directory = dest.rsplit("/", 1)[0] if "/" in dest else ""
|
||||
for md5 in _md5s(fe):
|
||||
other = native.get((directory, md5))
|
||||
if other and other.lower() != fe.get("name", "").lower():
|
||||
repeated.append(f"{platform}:{fe['name']} = {other}")
|
||||
self.assertEqual(repeated, [])
|
||||
# quasi88 reads N88EXT0.ROM before n88_0.rom: an alias in
|
||||
# the entry's directory is the same declaration.
|
||||
folder = destination.rpartition("/")[0]
|
||||
for name in [destination] + [
|
||||
f"{folder}/{alias}" if folder else str(alias)
|
||||
for alias in entry.get("aliases") or []
|
||||
]:
|
||||
cls.by_destination.setdefault(name.casefold(), []).append(entry)
|
||||
|
||||
class ADeclaredAliasSettlesTheRequirement(unittest.TestCase):
|
||||
"""quasi88 reads n88sub.rom or disk.rom; System.dat names disk.rom. The
|
||||
gap pass compared the entry's name alone and the pack carried the same
|
||||
2 KB ROM a second time as quasi88/n88sub.rom."""
|
||||
def _declarations(self, entry: dict) -> list[dict]:
|
||||
names = [entry.get("destination") or entry["name"]]
|
||||
return [d for n in names for d in self.by_destination.get(n.casefold(), [])]
|
||||
|
||||
def test_a_platform_naming_an_alias_has_met_the_entry(self):
|
||||
import os # noqa: PLC0415
|
||||
import tempfile # noqa: PLC0415
|
||||
def test_every_entry_is_a_file_a_profile_declares(self):
|
||||
phantoms = [
|
||||
f"{group}: {entry['name']}"
|
||||
for group, entries in self.groups.items()
|
||||
for entry in entries
|
||||
if not self._declarations(entry)
|
||||
]
|
||||
self.assertEqual(phantoms, [])
|
||||
|
||||
from common import _emulator_profiles_cache # noqa: PLC0415
|
||||
from verify import find_undeclared_files # noqa: PLC0415
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
Path(tmp, "emulators").mkdir()
|
||||
Path(tmp, "emulators", "quasi88.yml").write_text(
|
||||
"emulator: QUASI88\ntype: libretro\nsystems: [nec-pc-88]\ncores: [quasi88]\n"
|
||||
"files:\n - name: n88sub.rom\n path: quasi88/n88sub.rom\n"
|
||||
" aliases: [N88SUB.ROM, disk.rom]\n required: true\n"
|
||||
)
|
||||
_emulator_profiles_cache.clear()
|
||||
self.addCleanup(_emulator_profiles_cache.clear)
|
||||
config = {"platform": "P", "verification_mode": "existence", "cores": "all_libretro",
|
||||
"base_destination": "system",
|
||||
"systems": {"nec-pc-88": {"files": [{"name": "disk.rom", "destination": "quasi88/disk.rom"}]}}}
|
||||
db = {"files": {}, "indexes": {"by_name": {}, "by_md5": {}, "by_crc32": {}, "by_path_suffix": {}}}
|
||||
previous = os.getcwd()
|
||||
os.chdir(tmp)
|
||||
self.addCleanup(os.chdir, previous)
|
||||
undeclared = find_undeclared_files(config, "emulators", db, data_names=set())
|
||||
self.assertEqual([u["name"] for u in undeclared], [])
|
||||
|
||||
|
||||
class ASameNamedFileOfAnotherSizeSettlesNothing(unittest.TestCase):
|
||||
"""RetroArch declares Galaksija's ROM1.BIN, a 4 KiB ROM; DOSBox reads a
|
||||
32 KiB SC-55 ROM1.BIN and checks its size. The gap pass settled DOSBox's
|
||||
entry on the name alone: the report never listed SC-55/ROM1.BIN, and a
|
||||
missing dump would have shown no gap."""
|
||||
|
||||
PROFILE = (
|
||||
"emulator: DOSBox\ntype: libretro\nsystems: [dos]\ncores: [dosbox]\n"
|
||||
"files:\n - name: ROM1.BIN\n path: SC-55/ROM1.BIN\n"
|
||||
" size: 32768\n validation: [size]\n"
|
||||
)
|
||||
|
||||
def undeclared(self, declared_size, destination="galaksija/ROM1.BIN", md5=None, db_size=None):
|
||||
import os # noqa: PLC0415
|
||||
import tempfile # noqa: PLC0415
|
||||
|
||||
from common import _emulator_profiles_cache # noqa: PLC0415
|
||||
from verify import find_undeclared_files # noqa: PLC0415
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
Path(tmp, "emulators").mkdir()
|
||||
Path(tmp, "emulators", "dosbox.yml").write_text(self.PROFILE)
|
||||
_emulator_profiles_cache.clear()
|
||||
self.addCleanup(_emulator_profiles_cache.clear)
|
||||
config = {"platform": "P", "verification_mode": "existence", "cores": "all_libretro",
|
||||
"base_destination": "system",
|
||||
"systems": {"galaksija": {"files": [
|
||||
{"name": "ROM1.BIN", "destination": destination,
|
||||
"size": declared_size, "md5": md5}]}}}
|
||||
db = {"files": {}, "indexes": {"by_name": {}, "by_md5": {}, "by_crc32": {}, "by_path_suffix": {}}}
|
||||
if md5:
|
||||
db["files"]["a" * 40] = {"name": "ROM1.BIN", "size": db_size}
|
||||
db["indexes"]["by_md5"][md5] = "a" * 40
|
||||
previous = os.getcwd()
|
||||
os.chdir(tmp)
|
||||
self.addCleanup(os.chdir, previous)
|
||||
return [u["path"] for u in find_undeclared_files(config, "emulators", db, data_names=set())]
|
||||
|
||||
def test_a_declaration_at_a_size_the_core_rejects_leaves_the_gap_open(self):
|
||||
self.assertEqual(self.undeclared(4096), ["SC-55/ROM1.BIN"])
|
||||
|
||||
def test_a_declaration_at_an_accepted_size_settles_it(self):
|
||||
self.assertEqual(self.undeclared(32768), [])
|
||||
|
||||
def test_a_declaration_without_a_size_still_settles_it(self):
|
||||
self.assertEqual(self.undeclared(None), [])
|
||||
|
||||
def test_the_size_of_the_file_its_hash_names_stands_in(self):
|
||||
"""Batocera writes no size: its md5 names the file, and the file's size
|
||||
decides. MT32_CONTROL.ROM at 64 KiB covered DOSBox's 128 KiB entries."""
|
||||
self.assertEqual(
|
||||
self.undeclared(None, md5="b" * 32, db_size=4096), ["SC-55/ROM1.BIN"]
|
||||
)
|
||||
|
||||
def test_a_declaration_filling_the_entry_slot_settles_it(self):
|
||||
"""Same destination, other size: which bytes fill the slot is the slot
|
||||
arbitration's call, and the builder leaves a taken slot alone."""
|
||||
self.assertEqual(self.undeclared(4096, destination="SC-55/ROM1.BIN"), [])
|
||||
|
||||
|
||||
class AnAliasAnswersOnlyWhereTheCoreReadsIt(unittest.TestCase):
|
||||
"""CLK reads MSX/MSX.ROM and answers to MSX.ROM; RetroDECK declares
|
||||
bios/MSX.ROM at the root for fMSX. The alias settled CLK's entry and the
|
||||
pack left CLK without an MSX BIOS: the builder copies a file declared
|
||||
elsewhere to a core's path under the core's own name, never an alias."""
|
||||
|
||||
PROFILE = (
|
||||
"emulator: CLK\ntype: libretro\nsystems: [msx]\ncores: [clk]\n"
|
||||
"files:\n - name: msx.rom\n aliases: [MSX.ROM]\n path: MSX/MSX.ROM\n"
|
||||
" required: true\n"
|
||||
)
|
||||
|
||||
def undeclared(self, destination, base_destination="", size=None):
|
||||
import os # noqa: PLC0415
|
||||
import tempfile # noqa: PLC0415
|
||||
|
||||
from common import _emulator_profiles_cache # noqa: PLC0415
|
||||
from verify import find_undeclared_files # noqa: PLC0415
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
Path(tmp, "emulators").mkdir()
|
||||
Path(tmp, "emulators", "clk.yml").write_text(self.PROFILE)
|
||||
_emulator_profiles_cache.clear()
|
||||
self.addCleanup(_emulator_profiles_cache.clear)
|
||||
config = {"platform": "P", "verification_mode": "md5", "cores": ["clk"],
|
||||
"base_destination": base_destination,
|
||||
"systems": {"msx": {"files": [
|
||||
{"name": "MSX.ROM", "destination": destination, "size": size},
|
||||
{"name": "other.rom", "destination": "bios/other.rom"}]}}}
|
||||
db = {"files": {}, "indexes": {"by_name": {}, "by_md5": {}, "by_crc32": {}, "by_path_suffix": {}}}
|
||||
previous = os.getcwd()
|
||||
os.chdir(tmp)
|
||||
try:
|
||||
return [u["path"] for u in find_undeclared_files(config, "emulators", db, data_names=set())]
|
||||
finally:
|
||||
os.chdir(previous)
|
||||
|
||||
def test_an_alias_declared_at_another_path_leaves_the_entry_open(self):
|
||||
self.assertEqual(self.undeclared("bios/MSX.ROM"), ["MSX/MSX.ROM"])
|
||||
|
||||
def test_an_alias_declared_where_the_core_reads_settles_it(self):
|
||||
"""RetroDECK writes bios/ into every destination; core paths start below it."""
|
||||
self.assertEqual(self.undeclared("bios/MSX/MSX.ROM"), [])
|
||||
|
||||
def test_an_alias_in_place_at_a_rejected_size_leaves_it_open(self):
|
||||
"""RetroBat's root DISK.ROM is the 16 KiB MSX disk ROM; gsplus reads a
|
||||
256-byte DISK.ROM or c600.rom there and checks the size."""
|
||||
self.PROFILE = (
|
||||
"emulator: GSplus\ntype: libretro\nsystems: [msx]\ncores: [clk]\n"
|
||||
"files:\n - name: c600.rom\n aliases: [MSX.ROM]\n"
|
||||
" size: 256\n validation: [size]\n"
|
||||
)
|
||||
self.assertEqual(self.undeclared("MSX.ROM", "bios", size=16384), ["c600.rom"])
|
||||
self.assertEqual(self.undeclared("MSX.ROM", "bios", size=256), [])
|
||||
def test_required_follows_the_code(self):
|
||||
disagreements = []
|
||||
for group, entries in self.groups.items():
|
||||
for entry in entries:
|
||||
declared = self._declarations(entry)
|
||||
if not declared:
|
||||
continue
|
||||
required = any(d.get("required") for d in declared)
|
||||
if bool(entry.get("required", True)) != required:
|
||||
disagreements.append(f"{group}: {entry['name']}")
|
||||
self.assertEqual(disagreements, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user