diff --git a/scripts/verify.py b/scripts/verify.py index 8ece9575..5fae7154 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -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, diff --git a/tests/test_verify_emulator_evidence.py b/tests/test_verify_emulator_evidence.py index c8a20e9e..b8f498c3 100644 --- a/tests/test_verify_emulator_evidence.py +++ b/tests/test_verify_emulator_evidence.py @@ -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})