fix: prefer among the entries a reduced pack keeps

This commit is contained in:
Abdessamad Derraz committed 2026-10-09 21:59:38 +02:00
1 parent 3dc353dc73
commit 606d91ebab
2 files changed
+112 -6

No files matched your search

+9 -6
View File
@@ -723,10 +723,13 @@ def generate_pack(
preferred_entries: dict[str, int] = {} preferred_entries: dict[str, int] = {}
if source != "truth": if source != "truth":
# A manifest lists the full set and carries no data directories, so # Decided among the declarations this pack keeps: a preferred entry
# the preference is resolved with neither narrowing nor a cache. # that --required-only drops must not silence its required twin, or
# the destination leaves the pack without a trace (RetroDECK's four
# Atari 5200 ROMs, optional under atari-400-800 and required under
# atari-5200). No data directories: the choice is about the baseline.
preferred_entries = _preferred_entries( preferred_entries = _preferred_entries(
pack_systems, db, bios_dir, base_dest, False, pack_systems, db, bios_dir, base_dest, required_only,
zip_contents, None, offline, zip_contents, None, offline,
) )
@@ -3187,10 +3190,10 @@ def generate_manifest(
# constrained sibling claim it; without the same rule here the # constrained sibling claim it; without the same rule here the
# manifest named whatever answered to the name, so the pack and the # manifest named whatever answered to the name, so the pack and the
# installer disagreed about which file a destination means. # installer disagreed about which file a destination means.
# A manifest lists the full set and carries no data directories, so # Decided among the declarations this manifest keeps, as the pack
# the preference is resolved with neither narrowing nor a cache. # does; a manifest carries no data directories.
preferred_entries = _preferred_entries( preferred_entries = _preferred_entries(
pack_systems, db, bios_dir, base_dest, False, pack_systems, db, bios_dir, base_dest, required_only,
zip_contents, None, offline, zip_contents, None, offline,
) )
for sys_id, system in sorted(pack_systems.items()): for sys_id, system in sorted(pack_systems.items()):
+103
View File
@@ -0,0 +1,103 @@
"""A required declaration is never silenced by its optional twin.
RetroDECK declares the four Atari 5200 ROMs optional under atari-400-800 and
required under atari-5200, same md5. The preferred declaration for a shared
destination was chosen over the full set, then applied to a loop that had
already dropped the optional entries: under --required-only the preferred
(optional) entry was skipped for being optional and the required one for not
being preferred, and the destination left the pack, the status counts and
the manifest without a trace.
"""
from __future__ import annotations
import hashlib
import sys
import tempfile
import unittest
import zipfile
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import common # noqa: E402
import generate_db # noqa: E402
import generate_pack as builder # noqa: E402
class RequiredTwins(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.root = Path(self._tmp.name)
self.bios = self.root / "bios" / "Atari"
self.platforms = self.root / "platforms"
self.emulators = self.root / "emulators"
for directory in (self.bios, self.platforms, self.emulators):
directory.mkdir(parents=True)
payload = b"atari os"
(self.bios / "ATARIOSB.ROM").write_bytes(payload)
md5 = hashlib.md5(payload).hexdigest()
sha1 = hashlib.sha1(payload).hexdigest()
files = {
sha1: {
"path": str(self.bios / "ATARIOSB.ROM"), "name": "ATARIOSB.ROM",
"size": len(payload), "sha1": sha1, "md5": md5,
"sha256": hashlib.sha256(payload).hexdigest(), "crc32": "00000008",
}
}
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
platform = {
"platform": "Twins",
"verification_mode": "md5",
"base_destination": "bios",
"cores": [],
"systems": {
# Sorted first: the optional declaration is the preferred one.
"atari-400-800": {"files": [
{"name": "ATARIOSB.ROM", "destination": "ATARIOSB.ROM",
"md5": md5, "required": False},
]},
"atari-5200": {"files": [
{"name": "ATARIOSB.ROM", "destination": "ATARIOSB.ROM",
"md5": md5, "required": True},
]},
},
}
(self.platforms / "twins.yml").write_text(yaml.dump(platform))
(self.platforms / "_registry.yml").write_text(
yaml.dump({"platforms": {"twins": {"status": "active"}}})
)
common._platform_config_cache.clear()
common._emulator_profiles_cache.clear()
def tearDown(self):
common._platform_config_cache.clear()
self._tmp.cleanup()
def test_the_required_only_pack_carries_the_required_file(self):
out = self.root / "dist"
out.mkdir()
zip_path = builder.generate_pack(
"twins", str(self.platforms), self.db, str(self.root / "bios"), str(out),
emulators_dir=str(self.emulators), emu_profiles={},
required_only=True, offline=True,
)
with zipfile.ZipFile(zip_path) as archive:
self.assertIn("ATARIOSB.ROM", archive.namelist())
def test_the_required_only_manifest_lists_it(self):
manifest = builder.generate_manifest(
"twins", str(self.platforms), self.db, str(self.root / "bios"),
str(self.platforms / "_registry.yml"),
emulators_dir=str(self.emulators), emu_profiles={},
required_only=True, offline=True,
)
listed = {entry["dest"] for entry in manifest["files"]}
self.assertIn("ATARIOSB.ROM", listed)
if __name__ == "__main__":
unittest.main()