chore: merge round 2 fixes (profile names)

This commit is contained in:
Abdessamad Derraz committed 2026-10-21 10:14:59 +02:00
commit d7afac0993
24 files changed
+185 -48

No files matched your search

+103
View File
@@ -5453,6 +5453,109 @@ class AHashScanningCoreContradictsNoPlatform(unittest.TestCase):
self.assertEqual(claimed, [])
class AHashScanLabelNamesNoOtherDump(unittest.TestCase):
"""A core that finds its firmware by content names no file, so each
entry name is a label. ares labelled its Famicom Disk System revision
(RP2C33-01, crc32 1c7ae5d5) disksys.rom, the name six NES cores read for
the RP2C33A-01A dump: on ROCKNIX the manifest put ares's revision at
disksys.rom for all of them. A label that another profile gives a
different dump is refused, the two dumps compared through the
collection's hash indexes."""
# Names the core's own code gives the image, not labels.
CODE_NAMED = {
("bizhawk", "MSX.rom"):
"FirmwareDatabase.cs:362 names the image MSX.rom",
("swanstation", "ps1_rom.bin"):
"the whole PS3 image is the file the PS3 itself names ps1_rom.bin, "
"beetle_psx reads its first 512 KB under the same name",
}
@staticmethod
def _identity(entry: dict, db: dict) -> tuple[set, set]:
raw: set = set()
sha1s: set = set()
indexes = db["indexes"]
for field, index in (("sha1", None), ("md5", "by_md5"),
("crc32", "by_crc32"), ("sha256", "by_sha256")):
value = entry.get(field)
values = value if isinstance(value, list) else [value] if value else []
for item in values:
for digest in str(item).lower().split(","):
digest = digest.strip()
if not digest:
continue
raw.add((field, digest))
sha1 = digest if index is None else indexes[index].get(digest)
if sha1 in db["files"]:
sha1s.add(sha1)
return raw, sha1s
def test_labels_are_not_other_dumps_names(self):
import json
from scripts.common import load_emulator_profiles
if not (ROOT / "database.json").exists():
self.skipTest("database.json is not built")
db = json.loads((ROOT / "database.json").read_text(encoding="utf-8"))
profiles = load_emulator_profiles(str(ROOT / "emulators"))
by_name: dict[str, list[tuple[str, dict]]] = {}
for emulator, profile in profiles.items():
for entry in profile.get("files") or []:
for name in [entry.get("name"), *(entry.get("aliases") or [])]:
if name:
by_name.setdefault(str(name).casefold(), []).append((emulator, entry))
clashes = []
for emulator, profile in profiles.items():
if profile.get("found_by") != "content":
continue
for entry in profile.get("files") or []:
raw, sha1s = self._identity(entry, db)
if not sha1s:
continue
for name in [entry.get("name"), *(entry.get("aliases") or [])]:
if (emulator, name) in self.CODE_NAMED:
continue
for other, other_entry in by_name.get(str(name).casefold(), []):
if other == emulator:
continue
other_raw, other_sha1s = self._identity(other_entry, db)
if other_sha1s and not raw & other_raw and not sha1s & other_sha1s:
clashes.append(f"{emulator} {name} is {other}'s name for another dump")
self.assertEqual(sorted(set(clashes)), [])
class ANoteOutsideTheSystemDirCarriesLoadFrom(unittest.TestCase):
"""mame2000 opens hiscore.dat, history.dat and mameinfo.dat from the
directory of the loaded content (src/libretro/fileio.c:1002-1014), and
mame2014 and mame2015 open hiscore.dat from the working directory
(src/emu/hiscore.c:330-337). Their notes said so, but no load_from did:
the entries claimed system/hiscore.dat and the RetroArch pack shipped a
file neither core opens there. A libretro entry whose note places the
read outside the system directory declares where."""
ELSEWHERE = re.compile(
r"\b(?:opened|read|loaded)\b[^.]*?\bfrom (?:the )?(?:working directory|"
r"ROM directory|content directory|directory of the loaded content)"
r"|IMAMEBASEPATH",
re.IGNORECASE,
)
def test_prose_and_load_from_agree(self):
offenders = []
for path in sorted((ROOT / "emulators").glob("*.yml")):
profile = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
if profile.get("type") != "libretro":
continue
for entry in profile.get("files") or []:
if entry.get("path") or entry.get("load_from") or entry.get("mode") == "standalone":
continue
if self.ELSEWHERE.search(str(entry.get("note") or "")):
offenders.append(f"{path.name}: {entry.get('name')}")
self.assertEqual(offenders, [])
class ReportsHoldTheDataTrees(unittest.TestCase):
"""Run on their own, the README, site and slot reports resolved against
data/ without the shared hold, so a concurrent refresh could swap a tree
+19
View File
@@ -231,6 +231,25 @@ class LauncherLayerTest(unittest.TestCase):
self.assertIn("v103.bin", report)
self.assertIn("boot.bin", report)
def test_a_name_whose_copies_fail_the_size_is_not_held(self) -> None:
"""NanoBoyAdvance reads a 16 KB bios.bin, and the collection's
bios.bin files are PCem's and QEMU's PC BIOS images. Counted held by
name, the entry escaped the launcher layer, which settles only what
the collection lacks, and the ROCKNIX pack carried PCem's 64 KB image
beside the gba_bios.bin its launcher passes."""
profile = {
"emulator": "FixEmu", "type": "standalone", "systems": ["test-sys"],
"files": [{"name": "core.bin", "required": True,
"size": len(CONTENT["plain.bin"]), "validation": ["size"]}],
}
with open(os.path.join(self.emulators_dir, "fixemu.yml"), "w") as fh:
yaml.dump(profile, fh)
self.config["launchers"][2]["names"] = ["plain.bin"]
_emulator_profiles_cache.clear()
self.profiles = load_emulator_profiles(self.emulators_dir)
self.assertNotIn("core.bin", {u["name"] for u in self._report()})
self.assertNotIn("core.bin", self._pack_names())
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"])
+21
View File
@@ -225,6 +225,18 @@ class ProfilesStateFactsAboutOtherProjects(unittest.TestCase):
re.IGNORECASE,
)
# A line that names another project's file grades it when it calls the
# file correct or accurate, says it still declares something, or that an
# entry is missing from it or omitted. prosystem, retrodream,
# fbalpha2012_neogeo and seven more profiles wrote it that way.
OTHER_FILE = re.compile(r"(\.info\b|core-info|System\.dat)", re.IGNORECASE)
GRADES = re.compile(
r"\b(?:is (?:correct|accurate|inaccurate|outdated|stale)|accurate here|"
r"correct:|still (?:declares|lists)|omits|missing (?:the|from))|"
r"\.info missing",
re.IGNORECASE,
)
def test_profile_prose_and_comments(self):
offenders = [
f"{path.relative_to(ROOT)}: {line.strip()[:100]}"
@@ -234,6 +246,15 @@ class ProfilesStateFactsAboutOtherProjects(unittest.TestCase):
]
self.assertEqual(offenders, [], "\n".join(offenders[:40]))
def test_no_grade_on_another_projects_file(self):
offenders = [
f"{path.relative_to(ROOT)}: {line.strip()[:100]}"
for path in sorted((ROOT / "emulators").glob("*.yml"))
for line in path.read_text(encoding="utf-8").splitlines()
if self.OTHER_FILE.search(line) and self.GRADES.search(line)
]
self.assertEqual(offenders, [], "\n".join(offenders[:40]))
class NoClauseSemicolons(unittest.TestCase):
def _check(self, files: list[Path], extract) -> None: