Files
libretro/tests/test_foreign_tree_names.py
T
Abdessamad Derraz 3b0112001e fix: serve a data dir file at its platform path
AmberELEC and EmuELEC declare blueMSX's Machines/<model>/config.ini and
Shared Roms/RS232.ROM with no hash. The name step served msx-emu's
ColecoVision config.ini in 192 machine folders and the 16 KB RS232.ROM,
while data/bluemsx held blueMSX's own file at that very path. A platform
declaration with no hash now reads its destination's tail in the data
directories before the name step. Profile entries keep the foreign-tree
redirect, so Dolphin's dsp_coef.bin dump still outranks the free copy.

Manifests: amberelec and emuelec omit 192 config.ini entries (held in
data/bluemsx only, so the installer has no URL) and serve colecodb.xml,
MSX2+/config.ini, MSXturboR/config.ini and RS232.ROM from blueMSX's bytes.
2026-11-05 19:21:26 +01:00

190 lines
8.2 KiB
Python

"""The name step does not settle on another emulator's copy.
xm6typeg asked for SCSIINROM.DAT: only XM6 Utility's synthesized image
matched the casing, under bios/Other/xm6pro68k/, and the X68000 dump stored
as scsiinrom.dat was never a candidate. blueMSX asked for RS232.ROM: its own
8 KB copy was msx-emu's, set aside as foreign, and the 16 KB file left was
served while data/bluemsx held the 8 KB one.
"""
from __future__ import annotations
import hashlib
import os
import shutil
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from common import resolve_local_file # noqa: E402
class ForeignTreeNames(unittest.TestCase):
def setUp(self):
(REPO_ROOT / "tmp").mkdir(exist_ok=True)
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, self.root, True)
# The collection's paths are relative to the repository root.
self.addCleanup(os.chdir, os.getcwd())
os.chdir(self.root)
self.db = {"files": {}, "indexes": {
"by_md5": {}, "by_name": {}, "by_crc32": {}, "by_path_suffix": {},
}}
def _hold(self, rel: str, data: bytes) -> str:
target = self.root / rel
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(data)
sha1 = hashlib.sha1(data).hexdigest()
name = rel.rsplit("/", 1)[-1]
if "/.variants/" in rel:
name = name.rsplit(".", 1)[0]
self.db["files"][sha1] = {
"path": rel, "name": name, "size": len(data),
"md5": hashlib.md5(data).hexdigest(), "crc32": "",
}
self.db["indexes"]["by_name"].setdefault(name, []).append(sha1)
return rel
def test_a_casing_only_another_tree_holds(self):
self._hold("bios/Other/xm6pro68k/.variants/SCSIINROM.DAT.47404fbb", b"X" * 64)
own = self._hold("bios/Sharp/X68000/scsiinrom.dat", b"D" * 64)
entry = {"name": "SCSIINROM.DAT", "source_emulator": "xm6typeg"}
self.assertEqual(resolve_local_file(entry, self.db)[0], own)
def test_a_set_aside_copy_sends_the_name_to_the_data_directory(self):
self._hold("bios/Microsoft/MSX/RS232.ROM", b"L" * 128)
self._hold("bios/Other/msx-emu/Machines/Shared Roms/RS232.ROM", b"S" * 64)
cache = self.root / "data" / "bluemsx"
(cache / "Machines" / "Shared Roms").mkdir(parents=True)
(cache / "Machines" / "Shared Roms" / "RS232.ROM").write_bytes(b"S" * 64)
entry = {
"name": "RS232.ROM", "path": "Machines/Shared Roms/RS232.ROM",
"size": 64, "source_emulator": "bluemsx",
}
path, status = resolve_local_file(
entry, self.db,
data_dir_registry={"bluemsx": {"local_cache": str(cache)}},
)
self.assertEqual(status, "data_dir")
self.assertEqual(Path(path).stat().st_size, 64)
def test_a_casing_only_another_tree_holds_reads_the_data_directory(self):
"""MSX2P.rom: only msx-emu's tree had the exact casing, and widening
to every casing served Microsoft/MSX/MSX2P.ROM, another dump, while
data/bluemsx held the one blueMSX ships."""
self._hold("bios/Other/msx-emu/Machines/Shared Roms/MSX2P.rom", b"B" * 64)
self._hold("bios/Microsoft/MSX/MSX2P.ROM", b"O" * 64)
cache = self.root / "data" / "bluemsx"
(cache / "Machines" / "Shared Roms").mkdir(parents=True)
(cache / "Machines" / "Shared Roms" / "MSX2P.rom").write_bytes(b"B" * 64)
entry = {
"name": "MSX2P.rom", "path": "Machines/Shared Roms/MSX2P.rom",
"source_emulator": "bluemsx",
}
path, status = resolve_local_file(
entry, self.db,
data_dir_registry={"bluemsx": {"local_cache": str(cache)}},
)
self.assertEqual(status, "data_dir")
self.assertEqual(Path(path).read_bytes(), b"B" * 64)
def _bluemsx_cache(self, rel: str, data: bytes) -> dict:
cache = self.root / "data" / "bluemsx"
(cache / rel).parent.mkdir(parents=True, exist_ok=True)
(cache / rel).write_bytes(data)
return {"bluemsx": {"local_cache": str(cache)}}
def test_a_platform_declaration_reads_the_data_directory_at_its_path(self):
"""AmberELEC declares every blueMSX machine's config.ini with no hash.
The name step met msx-emu's ColecoVision config.ini first and wrote it
into each machine folder, while data/bluemsx held the machine's own
at that very path."""
self._hold("bios/Other/msx-emu/Machines/COL - ColecoVision/config.ini", b"C" * 64)
registry = self._bluemsx_cache("Machines/MSX - Arabic/config.ini", b"A" * 64)
entry = {
"name": "config.ini",
"destination": "bios/Machines/MSX - Arabic/config.ini",
}
path, status = resolve_local_file(entry, self.db, data_dir_registry=registry)
self.assertEqual(status, "path_exact")
self.assertEqual(Path(path).read_bytes(), b"A" * 64)
def test_a_platform_rs232_rom_reads_the_data_directory_at_its_path(self):
self._hold("bios/Microsoft/MSX/RS232.ROM", b"L" * 128)
registry = self._bluemsx_cache("Machines/Shared Roms/RS232.ROM", b"S" * 64)
entry = {
"name": "RS232.ROM",
"destination": "bios/Machines/Shared Roms/RS232.ROM",
}
path, _status = resolve_local_file(entry, self.db, data_dir_registry=registry)
self.assertEqual(Path(path).read_bytes(), b"S" * 64)
def test_a_profile_dump_outranks_the_free_copy_of_its_tree(self):
"""Dolphin's dsp_coef.bin: the dump in bios/ wins over the free
replacement Sys/GC bundles at the same path."""
own = self._hold("bios/Nintendo/GameCube/.variants/dsp_coef.bin.c116d867", b"R" * 64)
cache = self.root / "data" / "dolphin-sys"
(cache / "GC").mkdir(parents=True)
(cache / "GC" / "dsp_coef.bin").write_bytes(b"F" * 64)
entry = {
"name": "dsp_coef.bin", "destination": "GC/dsp_coef.bin",
"source_profile": "dolphin",
}
path, _status = resolve_local_file(
entry, self.db, dest_hint="GC/dsp_coef.bin",
data_dir_registry={"dolphin-sys": {"local_cache": str(cache)}},
)
self.assertEqual(path, own)
def test_another_casing_in_another_tree_is_not_the_file(self):
"""reminiscence's music module reunion took skyline's
zoneinfo/Indian/Reunion, a time-zone file, on the casefold step."""
self._hold("bios/Other/skyline/tzdata/zoneinfo/Indian/Reunion", b"T" * 64)
entry = {
"name": "reunion", "aliases": ["mod.flashback-reunion"],
"category": "game_data", "source_profile": "reminiscence",
}
self.assertEqual(resolve_local_file(entry, self.db), (None, "not_found"))
class RetailDashboardTree(unittest.TestCase):
"""x1-box asked for C/xbox.xtf: the only copy at that tail sat in hakuX's
tree and was set aside, and the name step served Xenia's Xbox 360 font.
The retail dashboard both emulators import lives with the console."""
def test_partition_c_resolves_by_path_for_both_importers(self):
import yaml
from common import load_database
db_path = REPO_ROOT / "database.json"
if not db_path.exists():
self.skipTest("database.json absent")
db = load_database(str(db_path))
for profile in ("x1-box", "hakux"):
document = yaml.safe_load(
(REPO_ROOT / "emulators" / f"{profile}.yml").read_text()
)
for entry in document["files"]:
dest = str(entry.get("path") or "")
if not dest.startswith("C/"):
continue
with self.subTest(profile=profile, dest=dest):
asked = {**entry, "source_profile": profile}
cwd = os.getcwd()
os.chdir(REPO_ROOT)
try:
path, status = resolve_local_file(asked, db, dest_hint=dest)
finally:
os.chdir(cwd)
self.assertEqual(status, "path_exact", path)
if __name__ == "__main__":
unittest.main()