mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 21:43:23 -05:00
fix: judge core extras by content where the builder does
The gap analysis answered from the name index, so a core extra whose local copy contradicts its declared hash counted as held. Under a digest mode the builder drops exactly that file, so the coverage report described a pack that would not contain it: seven files across Batocera, Recalbox and RetroBat. An entry that states what its content should be is now resolved by content; the name still answers for entries that declare nothing to check against, and existence mode is unchanged because there the frontend never opens the file and the pack does carry it.
This commit is contained in:
1 parent
b28f8d12a3
commit
f998f4d77a
2 files changed
+121
-6
No files matched your search
+26
-6
@@ -52,7 +52,7 @@ from common import (
|
||||
)
|
||||
|
||||
yaml = require_yaml()
|
||||
from nativemode import reads_file_contents
|
||||
from nativemode import hash_mismatch_excludes_file, reads_file_contents
|
||||
from validation import (
|
||||
_build_validation_index,
|
||||
_parse_validation,
|
||||
@@ -351,6 +351,12 @@ def find_undeclared_files(
|
||||
if declared_names is None:
|
||||
declared_names = expand_platform_declared_names(config, db)
|
||||
|
||||
# Whether the builder drops a file whose local copy contradicts its
|
||||
# declared hash, which decides if such a copy counts as held here.
|
||||
shipped_on_mismatch = hash_mismatch_excludes_file(
|
||||
config.get("verification_mode")
|
||||
)
|
||||
|
||||
# Collect data_directory refs
|
||||
declared_dd: set[str] = set()
|
||||
for sys_id, system in config.get("systems", {}).items():
|
||||
@@ -503,6 +509,14 @@ def find_undeclared_files(
|
||||
storage = f.get("storage", "")
|
||||
if storage in ("release", "large_file"):
|
||||
in_repo = True
|
||||
elif shipped_on_mismatch and (f.get("md5") or f.get("sha1")):
|
||||
# The entry states what its content should be and the builder
|
||||
# drops a copy that contradicts it, so content decides here
|
||||
# too. A name match would not do: generic names collide across
|
||||
# systems, and answering yes on one describes a pack that will
|
||||
# not contain the file.
|
||||
_lp, _st = resolve_local_file(f, db, dest_hint=dest)
|
||||
in_repo = _lp is not None and _st != "hash_mismatch"
|
||||
else:
|
||||
in_repo = _name_in_index(
|
||||
fname, by_name, by_path_suffix, data_names, by_name_lower,
|
||||
@@ -515,13 +529,19 @@ def find_undeclared_files(
|
||||
)
|
||||
if not in_repo:
|
||||
# Hash fallback: the repo may hold the content under a
|
||||
# different filename (exos21.rom vs exos21.bin)
|
||||
# generate_pack ships a core extra whose local copy
|
||||
# contradicts the declared hash and reports the divergence.
|
||||
# verify must agree with the builder or the two reports
|
||||
# disagree on the same file.
|
||||
# different filename (exos21.rom vs exos21.bin).
|
||||
#
|
||||
# A copy contradicting the declared hash counts as held
|
||||
# only where the builder would ship it. Under existence
|
||||
# the frontend never opens the file, so the pack carries
|
||||
# it and reports the divergence; under a digest mode the
|
||||
# frontend would reject it, the builder leaves it out, and
|
||||
# calling it available here would describe a pack that
|
||||
# does not contain it.
|
||||
_lp, _st = resolve_local_file(f, db, dest_hint=dest)
|
||||
in_repo = _st != "not_found" and _lp is not None
|
||||
if in_repo and _st == "hash_mismatch" and shipped_on_mismatch:
|
||||
in_repo = False
|
||||
|
||||
checks = _parse_validation(f.get("validation"))
|
||||
undeclared.append(
|
||||
|
||||
@@ -116,5 +116,100 @@ class BothConsumersAgree(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class GapAnalysisAgreesWithTheBuilder(unittest.TestCase):
|
||||
""""Available" must mean the pack will carry it.
|
||||
|
||||
find_undeclared_files answered from the name index, so a core extra whose
|
||||
local copy contradicts its declared hash counted as held. Under a digest
|
||||
mode the builder drops exactly that file, so the coverage report described
|
||||
a pack that would not contain it: seven such files across three platforms.
|
||||
"""
|
||||
|
||||
def _fixture(self):
|
||||
import hashlib
|
||||
import tempfile
|
||||
|
||||
tmp = tempfile.TemporaryDirectory()
|
||||
root = Path(tmp.name)
|
||||
(root / "emulators").mkdir()
|
||||
rom = root / "collide.rom"
|
||||
rom.write_bytes(b"THE BYTES THE COLLECTION HOLDS")
|
||||
sha1 = hashlib.sha1(rom.read_bytes()).hexdigest()
|
||||
db = {
|
||||
"files": {
|
||||
sha1: {
|
||||
"path": str(rom),
|
||||
"name": "collide.rom",
|
||||
"size": rom.stat().st_size,
|
||||
"sha1": sha1,
|
||||
"md5": hashlib.md5(rom.read_bytes()).hexdigest(),
|
||||
"sha256": hashlib.sha256(rom.read_bytes()).hexdigest(),
|
||||
"crc32": "00000000",
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"by_name": {"collide.rom": [sha1]},
|
||||
"by_md5": {hashlib.md5(rom.read_bytes()).hexdigest(): sha1},
|
||||
"by_sha256": {},
|
||||
"by_crc32": {},
|
||||
"by_path_suffix": {},
|
||||
},
|
||||
}
|
||||
(root / "emulators" / "demo.yml").write_text(
|
||||
"emulator: demo\n"
|
||||
"type: libretro\n"
|
||||
"display_name: Demo\n"
|
||||
"systems: [demo-system]\n"
|
||||
"cores: [demo]\n"
|
||||
"files:\n"
|
||||
" - name: collide.rom\n"
|
||||
" system: demo-system\n"
|
||||
" required: true\n"
|
||||
" md5: \"" + "f" * 32 + "\"\n"
|
||||
)
|
||||
return tmp, root, db
|
||||
|
||||
def _in_repo(self, mode: str) -> bool:
|
||||
import common
|
||||
from verify import find_undeclared_files
|
||||
|
||||
tmp, root, db = self._fixture()
|
||||
try:
|
||||
common._emulator_profiles_cache.clear()
|
||||
profiles = common.load_emulator_profiles(str(root / "emulators"))
|
||||
config = {
|
||||
"platform": "Demo",
|
||||
"verification_mode": mode,
|
||||
"cores": ["demo"],
|
||||
"systems": {},
|
||||
}
|
||||
found = find_undeclared_files(
|
||||
config, str(root / "emulators"), db, emu_profiles=profiles
|
||||
)
|
||||
entry = next(e for e in found if e["name"] == "collide.rom")
|
||||
return bool(entry["in_repo"])
|
||||
finally:
|
||||
common._emulator_profiles_cache.clear()
|
||||
tmp.cleanup()
|
||||
|
||||
def test_a_digest_mode_does_not_call_a_contradicted_copy_available(self):
|
||||
self.assertFalse(
|
||||
self._in_repo("md5"),
|
||||
"the builder drops this file, so the report must not count it",
|
||||
)
|
||||
|
||||
def test_existence_mode_still_counts_it(self):
|
||||
"""The frontend never opens the file, so the pack carries it."""
|
||||
self.assertTrue(self._in_repo("existence"))
|
||||
|
||||
def test_the_two_modes_answer_the_way_the_shared_predicate_says(self):
|
||||
for mode in nativemode.MODES:
|
||||
with self.subTest(mode=mode):
|
||||
self.assertEqual(
|
||||
self._in_repo(mode),
|
||||
not nativemode.hash_mismatch_excludes_file(mode),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user