mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-06 01:53:56 -06:00
fix: take an unchecked version as a match in packs
Raze lists three sized BLOOD.RFF versions and a fourth it takes without a check, the shareware RFF the collection holds. The Batocera pack printed two DISCREPANCY lines for it that verify never showed. A slot with an unchecked sibling now accepts any copy for that core, in the validation rules and in _matches_an_alternative. When every version refuses a copy, the message names all accepted values instead of the first.
This commit is contained in:
1 parent
3d0ee19bcb
commit
0d21566183
3 files changed
+84
-1
No files matched your search
@@ -98,6 +98,7 @@ from validation import (
|
||||
validated_choice,
|
||||
agnostic_substitute,
|
||||
_build_validation_index,
|
||||
_parse_validation,
|
||||
deliverable,
|
||||
with_archive_members,
|
||||
extra_alternatives,
|
||||
@@ -623,6 +624,10 @@ def _matches_an_alternative(fe: dict, local_path: str, profiles: dict) -> bool:
|
||||
siblings = extra_alternatives(fe, profiles)
|
||||
if len(siblings) < 2:
|
||||
return False
|
||||
# A sibling the code takes without a check (Raze's "BLOOD: Unknown
|
||||
# Version") accepts whatever copy fills the slot.
|
||||
if any(not _parse_validation(entry.get("validation")) for entry in siblings):
|
||||
return True
|
||||
actual = compute_hashes(local_path, frozenset({"sha1", "md5", "crc32"}))
|
||||
for entry in siblings:
|
||||
for algorithm in ("sha1", "md5", "crc32"):
|
||||
|
||||
+29
-1
@@ -8,6 +8,7 @@ truth data for reporting.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import zipfile
|
||||
from collections.abc import Callable, Iterator, Mapping, Sequence
|
||||
|
||||
@@ -161,6 +162,11 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
Raises ValueError if two profiles declare conflicting values.
|
||||
"""
|
||||
index: dict[str, dict] = {}
|
||||
# A slot where one version is taken without any check (Raze's "BLOOD:
|
||||
# Unknown Version", which holds the shareware RFF) accepts every copy for
|
||||
# that core: its validated siblings are alternatives, not requirements.
|
||||
unchecked: set[tuple[str, str, str]] = set()
|
||||
checked: set[tuple[str, str, str]] = set()
|
||||
for emu_name, profile in profiles.items():
|
||||
if profile.get("type") in ("launcher", "alias"):
|
||||
continue
|
||||
@@ -169,8 +175,11 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
if not fname:
|
||||
continue
|
||||
checks = _parse_validation(f.get("validation"))
|
||||
slot = (fname, emu_name, f.get("path") or fname)
|
||||
if not checks:
|
||||
unchecked.add(slot)
|
||||
continue
|
||||
checked.add(slot)
|
||||
if fname not in index:
|
||||
index[fname] = {
|
||||
"checks": set(),
|
||||
@@ -276,6 +285,8 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
existing["source_ref"] = pe_entry["source_ref"]
|
||||
else:
|
||||
pe[emu_name] = pe_entry
|
||||
for fname, emu_name, _path in sorted(checked & unchecked):
|
||||
index[fname]["rules"].append(_entry_rule(emu_name, {}, set()))
|
||||
# Convert sets to sorted tuples/lists for determinism
|
||||
for v in index.values():
|
||||
v["checks"] = sorted(v["checks"])
|
||||
@@ -324,6 +335,23 @@ def _emulators_for_check(
|
||||
return sorted(result)
|
||||
|
||||
|
||||
_REASON = re.compile(r"^(\w+ mismatch): got (\S+), accepted \[(.*)\]$")
|
||||
|
||||
|
||||
def _merged_reason(reasons: list[str]) -> str:
|
||||
"""One reason for every entry of a core that turned the copy away.
|
||||
|
||||
Raze lists three BLOOD.RFF sizes and the message named the first one
|
||||
alone. Reasons of one kind are merged into a single accepted list.
|
||||
"""
|
||||
parsed = [_REASON.match(reason) for reason in reasons]
|
||||
if all(parsed) and len({(m.group(1), m.group(2)) for m in parsed}) == 1:
|
||||
accepted = sorted({v for m in parsed for v in m.group(3).split(",") if v},
|
||||
key=lambda v: (len(v), v))
|
||||
return f"{parsed[0].group(1)}: got {parsed[0].group(2)}, accepted [{','.join(accepted)}]"
|
||||
return reasons[0]
|
||||
|
||||
|
||||
def check_file_validation(
|
||||
local_path: str,
|
||||
filename: str,
|
||||
@@ -363,7 +391,7 @@ def check_file_validation(
|
||||
for rule in rules
|
||||
]
|
||||
if all(reasons):
|
||||
failures[emu] = reasons[0]
|
||||
failures[emu] = _merged_reason(reasons)
|
||||
if not failures:
|
||||
return None
|
||||
first = sorted(failures)[0]
|
||||
|
||||
@@ -325,5 +325,55 @@ class AlternativesAreLookedUpBySlot(unittest.TestCase):
|
||||
self.assertEqual(built.call_count, 2)
|
||||
|
||||
|
||||
class AVersionTakenWithoutACheck(unittest.TestCase):
|
||||
"""The Batocera pack printed two DISCREPANCY lines for BLOOD.RFF that
|
||||
verify never showed: Raze lists three sized versions and a fourth,
|
||||
"Unknown Version", it takes without a check (the shareware RFF), and
|
||||
the message named one accepted size of the three."""
|
||||
|
||||
RAZE = {"files": [
|
||||
{"name": "BLOOD.RFF", "path": "BLOOD.RFF", "size": 8200353,
|
||||
"crc32": "b291418f", "validation": ["size", "crc32"]},
|
||||
{"name": "BLOOD.RFF", "path": "BLOOD.RFF", "size": 9570681,
|
||||
"crc32": "a8fdda84", "validation": ["size", "crc32"]},
|
||||
{"name": "BLOOD.RFF", "path": "BLOOD.RFF"},
|
||||
]}
|
||||
|
||||
def _file(self, tmp: str) -> str:
|
||||
path = Path(tmp) / "BLOOD.RFF"
|
||||
path.write_bytes(b"shareware")
|
||||
return str(path)
|
||||
|
||||
def test_the_unchecked_version_accepts_the_copy(self):
|
||||
from validation import check_file_validation # noqa: PLC0415
|
||||
|
||||
index = _build_validation_index({"raze": self.RAZE})
|
||||
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
||||
self.assertIsNone(check_file_validation(
|
||||
self._file(tmp), "BLOOD.RFF", index, emulators={"raze"}
|
||||
))
|
||||
|
||||
def test_the_pack_reads_it_as_an_alternative(self):
|
||||
import generate_pack # noqa: PLC0415
|
||||
|
||||
fe = {"name": "BLOOD.RFF", "source_profile": "raze", "source_path": "BLOOD.RFF"}
|
||||
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
||||
self.assertTrue(generate_pack._matches_an_alternative(
|
||||
fe, self._file(tmp), {"raze": self.RAZE}
|
||||
))
|
||||
|
||||
def test_a_refusal_names_every_accepted_size(self):
|
||||
from validation import check_file_validation # noqa: PLC0415
|
||||
|
||||
checked = {"files": self.RAZE["files"][:2]}
|
||||
index = _build_validation_index({"raze": checked})
|
||||
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
||||
reason, emulators = check_file_validation(
|
||||
self._file(tmp), "BLOOD.RFF", index, emulators={"raze"}
|
||||
)
|
||||
self.assertEqual(emulators, ["raze"])
|
||||
self.assertEqual(reason, "size mismatch: got 9, accepted [8200353,9570681]")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user