Files
libretro/tests/test_foreign_tree_names.py
T
Abdessamad Derraz 057fd2d0f6 fix: isolate every test module however it runs
tests/__init__.py keeps a run offline, its scratch under tmp/ and its
installer cache its own, but only discover -s tests -t . imported it.
discover tests ran with RETROBIOS_OFFLINE unset, scratch on the /tmp
tmpfs and the user's real installer cache, the setting in which e2e
fixtures had written into the shared .cache/large. Every test module now
imports the package first, and a test fails on a module that does not.
test_verify_listing.py took its line with the previous commit.
2026-11-06 15:14:05 +01:00

240 lines
10 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 tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
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)
class TheGapReportAsksAsTheEmulator(unittest.TestCase):
"""dosbox-x's et4000.bin, no hash: pcem's et4000.BIN was its only
casing. The resolver, asked as dosbox-x, refuses it and the pack and the
installer leave the file out, while the gap report, verify's core files
and the manifest's held flag counted it available."""
_hold = ForeignTreeNames._hold
def setUp(self):
ForeignTreeNames.setUp(self)
self._hold("bios/Other/pcem/roms/et4000.BIN", b"E" * 64)
self.entry = {"name": "et4000.bin", "required": True}
def _source(self, owner: str) -> str | None:
from cross_reference import entry_source
by_name = self.db["indexes"]["by_name"]
index = {
"by_name": by_name, "by_name_lower": {k.lower(): k for k in by_name},
"by_path_suffix": {}, "by_md5": {}, "by_sha256": {}, "by_crc32": {},
"db_files": self.db["files"], "data_names": None,
}
return entry_source({**self.entry, "source_profile": owner}, index)
def test_another_tree_casing_is_not_held(self):
self.assertIsNone(self._source("dosbox-x"))
entry = {**self.entry, "source_profile": "dosbox-x"}
self.assertEqual(resolve_local_file(entry, self.db), (None, "not_found"))
def test_the_tree_owner_holds_it(self):
self.assertEqual(self._source("pcem"), "bios")
def test_core_files_follow_the_resolver(self):
from verify import find_undeclared_files
profiles = {"dosbox-x": {
"emulator": "DOSBox-X", "type": "libretro", "systems": ["dos"],
"files": [self.entry],
}}
config = {"platform": "P", "verification_mode": "md5", "cores": ["dosbox-x"],
"systems": {"dos": {"files": []}}}
(found,) = [
u for u in find_undeclared_files(config, "emulators", self.db, profiles)
if u["name"] == "et4000.bin"
]
self.assertFalse(found["in_repo"])
if __name__ == "__main__":
unittest.main()