fix: list what the emulator pack writes

verify --emulator listed only OK destinations, while the emulator pack
also writes a copy whose hash the profile contradicts, loose or archive.
pack_destinations now records the paths the replay places, and both
branches resolve through packresolve.resolve_file as the pack does.
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 09:58:09 +02:00
1 parent 4ceca6acc5
commit 084dff84f4
2 files changed
+54 -5

No files matched your search

+17 -5
View File
@@ -1816,6 +1816,7 @@ def verify_emulator(
`system_ids` keeps the entries of those systems, as the system pack does.
"""
from packextras import expand_agnostic_entries
from packresolve import resolve_file
from packpaths import (
_archive_destination,
_register_path,
@@ -1846,6 +1847,10 @@ def verify_emulator(
file_severity: dict[str, str] = {}
dest_to_name: dict[str, str] = {}
data_dir_notices: list[str] = []
# The paths the emulator pack writes a profile file to. A copy whose hash
# the profile contradicts is written too (UNTESTED here, a DISCREPANCY in
# the pack), so the status alone does not say what the pack holds.
packed_dests: set[str] = set()
# The emulator pack withdraws these, and the report withdraws the same.
region_drops: set[str] = set()
@@ -1936,11 +1941,13 @@ def verify_emulator(
archive = file_entry.get("archive")
if archive and archive not in seen_archives:
seen_archives.add(archive)
local_path, resolve_status = resolve_local_file(
local_path, resolve_status = resolve_file(
archive_entry(archive, files),
db,
DEFAULT_BIOS_DIR,
zip_contents,
data_dir_registry=data_registry,
offline=True,
)
required = any(
f.get("archive") == archive and f.get("required", True)
@@ -1997,6 +2004,7 @@ def verify_emulator(
)
elif local_path:
occupy(dest, local_path, emu_name)
packed_dests.add(dest)
dest_to_name[dest] = archive
cur = result["status"]
prev = file_status.get(dest)
@@ -2023,13 +2031,18 @@ def verify_emulator(
continue
dest_hint = file_entry.get("path", "")
local_path, resolve_status = resolve_local_file(
# The emulator pack's resolution, release assets included.
local_path, resolve_status = resolve_file(
{**with_archive_members(file_entry, files), "source_profile": emu_name},
db,
DEFAULT_BIOS_DIR,
zip_contents,
dest_hint=dest_hint,
data_dir_registry=data_registry,
offline=True,
)
if resolve_status in ("not_found", "user_provided", "external"):
local_path = None
name = file_entry.get("name", "")
required = file_entry.get("required", True)
hle = file_entry.get("hle_fallback", False)
@@ -2159,6 +2172,7 @@ def verify_emulator(
block_reason = f"the pack holds {owner}'s {other_dest} at this path"
elif not block and packed is not None:
occupy(dest, packed, emu_name)
packed_dests.add(dest)
for result, required, hle in alternatives:
if block_reason and result["status"] != Status.MISSING:
result["status"] = Status.MISSING
@@ -2226,9 +2240,7 @@ def verify_emulator(
"total_files": total,
# The paths the emulator pack fills with the profile's files, for the
# pipeline's comparison with the pack built in memory.
"pack_destinations": sorted(
dest for dest, status in file_status.items() if status == Status.OK
),
"pack_destinations": sorted(packed_dests),
"severity_counts": counts,
"status_counts": status_counts,
"details": details,
+37
View File
@@ -52,6 +52,23 @@ def _db(path: Path, name: str) -> dict:
}
def _pack_files(root: Path, emulators: Path, db: dict) -> set[str]:
"""The paths the emulator pack writes, built in memory as the pipeline does."""
import generate_pack
record = generate_pack.PackRecord()
cwd = os.getcwd()
os.chdir(root)
try:
generate_pack.generate_emulator_pack(
["demo"], str(emulators), db, "bios", "", False, {}, offline=True,
record=record,
)
finally:
os.chdir(cwd)
return set(record.files)
class HashMismatchIsNotCoverage(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
@@ -113,6 +130,17 @@ class HashMismatchIsNotCoverage(unittest.TestCase):
self.assertIn("reason", detail)
self.assertIn("hash", detail["reason"].lower())
def test_the_contradicted_copy_the_pack_writes_is_a_pack_destination(self):
"""The pack writes it with a DISCREPANCY, so verify's list of what
the pack holds names it too: step 5 compared the two and failed."""
self._profile("f" * 32)
result = self._run()
self.assertEqual(result["pack_destinations"], ["shared.rom"])
self.assertEqual(
set(result["pack_destinations"]),
_pack_files(self.root, self.emulators, self.db),
)
def test_a_matching_hash_is_still_ok(self):
self._profile(hashlib.md5(self.rom.read_bytes()).hexdigest())
detail = next(
@@ -230,6 +258,15 @@ class ArchiveHashMismatchIsNotCoverage(unittest.TestCase):
"an archive whose bytes contradict its declared hash counted as covered",
)
def test_the_contradicted_archive_the_pack_writes_is_a_pack_destination(self):
self._profile("f" * 32)
result = self._run()
self.assertEqual(result["pack_destinations"], ["romset.zip"])
self.assertEqual(
set(result["pack_destinations"]),
_pack_files(self.root, self.emulators, self.db),
)
def test_a_matching_container_hash_is_still_ok(self):
self._profile(hashlib.md5(self.archive.read_bytes()).hexdigest())
self.assertEqual(set(self._statuses(self._run())), {verify.Status.OK})