mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 04:39:06 -05:00
A profile entry without a hash matched the collection by name, but only primary names were looked up, and which copy of identical bytes is primary follows path order. After the refile, project64's 64DD_IPL_v12_JPN.bin was a copy, so 64dd_ipl_J.bin and 64dd_ipl_U.bin left the name index. The lookup now counts every copy's name, and a name held by two contents still names neither.
255 lines
10 KiB
Python
255 lines
10 KiB
Python
"""A profile's aliases attach to the file its name designates, or to none.
|
|
|
|
generate_db matched an alias-carrying entry by name through a dict that
|
|
kept the last file of that name. quasi88's disk.rom aliases (N88SUB.ROM,
|
|
n88sub.rom) went to a Tandy CoCo disk.rom, and a name lookup for the PC-88
|
|
disk ROM then served the CoCo one.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import types
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
import generate_db # noqa: E402
|
|
|
|
|
|
class _NoCoreInfo:
|
|
def fetch_requirements(self):
|
|
return []
|
|
|
|
|
|
class AliasesNeedAnUnambiguousName(unittest.TestCase):
|
|
def _aliases(self, files: dict) -> dict:
|
|
stub = types.ModuleType("scraper.coreinfo_scraper")
|
|
stub.Scraper = _NoCoreInfo
|
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
|
previous = os.getcwd()
|
|
os.chdir(tmp)
|
|
try:
|
|
Path("emulators").mkdir()
|
|
Path("emulators/x.yml").write_text(
|
|
'files:\n - name: "disk.rom"\n aliases: ["n88sub.rom"]\n'
|
|
)
|
|
with mock.patch.dict(sys.modules, {"scraper.coreinfo_scraper": stub}):
|
|
return generate_db._collect_all_aliases(files)
|
|
finally:
|
|
os.chdir(previous)
|
|
|
|
@staticmethod
|
|
def _file(name: str, path: str, md5: str) -> dict:
|
|
return {"name": name, "path": path, "md5": md5}
|
|
|
|
def test_homonyms_take_no_alias(self):
|
|
files = {
|
|
"a": self._file("disk.rom", "bios/NEC/PC-98/disk.rom", "m1"),
|
|
"b": self._file("disk.rom", "bios/Tandy/CoCo/disk.rom", "m2"),
|
|
}
|
|
aliases = self._aliases(files)
|
|
named = {a["name"] for entries in aliases.values() for a in entries}
|
|
self.assertNotIn("n88sub.rom", named)
|
|
|
|
def test_a_unique_name_still_carries_its_aliases(self):
|
|
files = {"a": self._file("disk.rom", "bios/NEC/PC-88/disk.rom", "m1")}
|
|
aliases = self._aliases(files)
|
|
self.assertEqual([a["name"] for a in aliases.get("a", [])], ["n88sub.rom"])
|
|
|
|
|
|
class NoNetworkInTheBuild(unittest.TestCase):
|
|
def test_generate_db_reads_no_remote_source(self):
|
|
"""core-info was fetched on every run, offline ones included."""
|
|
source = (REPO_ROOT / "scripts" / "generate_db.py").read_text(encoding="utf-8")
|
|
self.assertNotIn("coreinfo_scraper", source)
|
|
self.assertNotIn("urllib.request", source)
|
|
|
|
def test_hash_proven_entry_registers_its_own_name(self):
|
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
|
previous = os.getcwd()
|
|
os.chdir(tmp)
|
|
try:
|
|
Path("emulators").mkdir()
|
|
Path("emulators/g.yml").write_text(
|
|
'files:\n - name: "writer.rom"\n sha1: "s1"\n'
|
|
)
|
|
aliases = generate_db._collect_all_aliases(
|
|
{"s1": {"name": "WRITER.ROM", "path": "bios/WRITER.ROM", "md5": "m"}}
|
|
)
|
|
finally:
|
|
os.chdir(previous)
|
|
self.assertEqual([a["name"] for a in aliases.get("s1", [])], ["writer.rom"])
|
|
|
|
class AcceptedRevisionLists(unittest.TestCase):
|
|
def test_a_list_of_sha1_names_every_held_revision(self):
|
|
"""A profile accepting several revisions writes sha1 as a list."""
|
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
|
previous = os.getcwd()
|
|
os.chdir(tmp)
|
|
try:
|
|
Path("emulators").mkdir()
|
|
Path("emulators/m.yml").write_text(
|
|
'files:\n - name: "MT32_CONTROL.ROM"\n'
|
|
' sha1: ["s1", "s2", "absent"]\n'
|
|
)
|
|
aliases = generate_db._collect_all_aliases({
|
|
"s1": {"name": "ctrl_1_04.rom", "path": "a", "md5": "m1"},
|
|
"s2": {"name": "ctrl_1_05.rom", "path": "b", "md5": "m2"},
|
|
})
|
|
finally:
|
|
os.chdir(previous)
|
|
for sha in ("s1", "s2"):
|
|
self.assertEqual([a["name"] for a in aliases.get(sha, [])], ["MT32_CONTROL.ROM"])
|
|
|
|
class CrossNamingStaysInItsProfile(unittest.TestCase):
|
|
def test_an_alias_naming_a_sibling_entry_is_not_indexed(self):
|
|
"""dosbox-x aliases MT32_CONTROL.ROM and CM32L_CONTROL.ROM to each other."""
|
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
|
previous = os.getcwd()
|
|
os.chdir(tmp)
|
|
try:
|
|
Path("emulators").mkdir()
|
|
Path("emulators/d.yml").write_text(
|
|
'files:\n'
|
|
' - name: "MT32_CONTROL.ROM"\n sha1: "mt"\n aliases: ["CM32L_CONTROL.ROM"]\n'
|
|
' - name: "CM32L_CONTROL.ROM"\n sha1: "cm"\n aliases: ["MT32_CONTROL.ROM"]\n'
|
|
)
|
|
aliases = generate_db._collect_all_aliases({
|
|
"mt": {"name": "mt32_control.rom", "path": "a", "md5": "m1"},
|
|
"cm": {"name": "cm32l_control.rom", "path": "b", "md5": "m2"},
|
|
})
|
|
finally:
|
|
os.chdir(previous)
|
|
self.assertEqual([a["name"] for a in aliases["mt"]], ["MT32_CONTROL.ROM"])
|
|
self.assertEqual([a["name"] for a in aliases["cm"]], ["CM32L_CONTROL.ROM"])
|
|
|
|
class AnUnreadableSourceStopsTheRun(unittest.TestCase):
|
|
"""A profile with a YAML error was skipped in silence: the database was
|
|
written without its aliases and the command exited 0."""
|
|
|
|
def test_the_file_is_named_and_nothing_collected(self):
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
|
|
import generate_db
|
|
|
|
tmp_root = Path(__file__).resolve().parents[1] / "tmp"
|
|
tmp_root.mkdir(exist_ok=True)
|
|
root = Path(tempfile.mkdtemp(dir=tmp_root))
|
|
self.addCleanup(shutil.rmtree, root, True)
|
|
previous = os.getcwd()
|
|
os.chdir(root)
|
|
self.addCleanup(os.chdir, previous)
|
|
Path("emulators").mkdir()
|
|
Path("emulators/broken.yml").write_text("files: [\n - name: a\n")
|
|
with self.assertRaises(generate_db.UnreadableSource) as caught:
|
|
generate_db._collect_all_aliases({})
|
|
self.assertIn("broken.yml", str(caught.exception))
|
|
|
|
|
|
class DrasticNamesTheDSDumps(unittest.TestCase):
|
|
"""DraStic opens nds_bios_arm9.bin, nds_bios_arm7.bin and nds_firmware.bin
|
|
and checks their size only. Its profile listed nine other names none of
|
|
its binaries contains, which were the only way the collection answered
|
|
its own three names."""
|
|
|
|
def _aliases(self, files: dict) -> dict:
|
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
|
previous = os.getcwd()
|
|
os.chdir(tmp)
|
|
try:
|
|
Path("emulators").mkdir()
|
|
return generate_db._collect_all_aliases(files)
|
|
finally:
|
|
os.chdir(previous)
|
|
|
|
def test_the_collection_answers_drastic_names(self):
|
|
files = {
|
|
"a9": {"name": "NDS_Bios9.bin", "path": "bios/Nintendo/DS/NDS_Bios9.bin", "md5": "m9"},
|
|
"a7": {"name": "NDS_Bios7.bin", "path": "bios/Nintendo/DS/NDS_Bios7.bin", "md5": "m7"},
|
|
"fw": {"name": "NDS_Lite_Firmware.bin",
|
|
"path": "bios/Nintendo/DS/NDS_Lite_Firmware.bin", "md5": "mf"},
|
|
}
|
|
aliases = self._aliases(files)
|
|
named = {sha: {a["name"] for a in entries} for sha, entries in aliases.items()}
|
|
self.assertIn("nds_bios_arm9.bin", named["a9"])
|
|
self.assertIn("nds_bios_arm7.bin", named["a7"])
|
|
self.assertIn("nds_firmware.bin", named["fw"])
|
|
|
|
def test_the_profile_carries_no_name_drastic_never_opens(self):
|
|
import yaml
|
|
|
|
profile = yaml.safe_load((REPO_ROOT / "emulators" / "drastic.yml").read_text())
|
|
self.assertEqual(
|
|
[e["name"] for e in profile["files"] if e.get("aliases")], [],
|
|
)
|
|
|
|
|
|
class AliasesFollowContentNotPrimaryCopy(unittest.TestCase):
|
|
"""Two copies of one IPL dump: 64DD_IPL.bin and 64DD_IPL_v12_JPN.bin.
|
|
The scan keeps the first by path order as primary and lists the other as
|
|
a copy. project64 names the second and declares no hash, so its aliases
|
|
attached only while that copy happened to be primary."""
|
|
|
|
PROFILE = (
|
|
'files:\n - name: "64DD_IPL_v12_JPN.bin"\n'
|
|
' aliases: ["64DD_IPL_JP.n64", "64dd_ipl_J.bin"]\n'
|
|
)
|
|
|
|
def _aliases(self, primary: str, copy: str) -> dict:
|
|
files = {"s": {"name": primary, "path": f"bios/N64DD/{primary}", "md5": "m"}}
|
|
copies = {"s": [{"name": copy, "path": f"bios/N64DD/{copy}"}]}
|
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
|
previous = os.getcwd()
|
|
os.chdir(tmp)
|
|
try:
|
|
Path("emulators").mkdir()
|
|
Path("emulators/p.yml").write_text(self.PROFILE)
|
|
return generate_db._collect_all_aliases(files, copies)
|
|
finally:
|
|
os.chdir(previous)
|
|
|
|
def test_either_copy_primary_gives_the_same_names(self):
|
|
names = []
|
|
for primary, copy in (
|
|
("64DD_IPL.bin", "64DD_IPL_v12_JPN.bin"),
|
|
("64DD_IPL_v12_JPN.bin", "64DD_IPL.bin"),
|
|
):
|
|
aliases = self._aliases(primary, copy)
|
|
names.append({a["name"] for a in aliases.get("s", [])})
|
|
self.assertEqual(names[0], names[1])
|
|
self.assertEqual(names[0], {"64DD_IPL_JP.n64", "64dd_ipl_J.bin"})
|
|
|
|
def test_a_copy_of_other_content_makes_the_name_ambiguous(self):
|
|
files = {
|
|
"a": {"name": "BS-X.bin", "path": "bios/SNES/BS-X.bin", "md5": "m1"},
|
|
"b": {"name": "Satellaview_BS-X.sfc", "path": "bios/SNES/x.sfc", "md5": "m2"},
|
|
}
|
|
copies = {"b": [{"name": "BS-X.bin", "path": "bios/Satellaview/BS-X.bin"}]}
|
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
|
previous = os.getcwd()
|
|
os.chdir(tmp)
|
|
try:
|
|
Path("emulators").mkdir()
|
|
Path("emulators/s.yml").write_text(
|
|
'files:\n - name: "BS-X.bin"\n aliases: ["BS-X.bios"]\n'
|
|
)
|
|
aliases = generate_db._collect_all_aliases(files, copies)
|
|
finally:
|
|
os.chdir(previous)
|
|
self.assertEqual(aliases, {})
|
|
|
|
def test_the_build_passes_the_scan_copies(self):
|
|
source = (REPO_ROOT / "scripts" / "generate_db.py").read_text(encoding="utf-8")
|
|
self.assertIn("_collect_all_aliases(files, aliases)", source)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|