From dde6229775ab6c757a2f74e5531aadac1c91bb3b Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 11:55:53 +0100 Subject: [PATCH] fix: count a core's release asset as packed verify replayed the core extras offline: a release asset an online build fetched and packed took no path in its report (finding 2). --- scripts/packextras.py | 15 ++++++++- scripts/verify.py | 2 +- tests/test_verify_release_asset.py | 53 ++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/scripts/packextras.py b/scripts/packextras.py index 23d13e4b1..9bb31ada8 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -244,6 +244,7 @@ class ExtraResolver: offline: bool | None = True, *, profiles: dict, + pending_assets: bool = False, ) -> None: self.profiles = profiles self.db = db @@ -253,10 +254,13 @@ class ExtraResolver: self.validation_index = validation_index self.validation_owners = validation_owners self.offline = offline + # verify's replay: never downloads, and counts the release asset an + # online build fetches, as it does for a platform entry. + self.pending_assets = pending_assets def resolve(self, fe: dict, dest: str) -> tuple[str | None, str, str]: """(local copy or None, resolver status, the core's disagreement).""" - from packresolve import resolve_file + from packresolve import pending_release_asset, resolve_file, run_is_offline from validation import core_size_refusal, extra_alternatives, validated_choice local_path, status = resolve_file( @@ -264,6 +268,15 @@ class ExtraResolver: dest_hint=fe.get("destination", ""), data_dir_registry=self.data_registry, offline=self.offline, ) + if ( + self.pending_assets and status in ("not_found", "hash_mismatch") + and not run_is_offline() + ): + # The asset the online builder fetches in resolve_file. Its record + # answers the declaration, and there are no bytes on disk to check. + asset = pending_release_asset(fe, self.db) + if asset: + return asset, "release_asset", "" if status in ("not_found", "external", "user_provided") or not local_path: return None, status, "" local_path, disagreement = validated_choice( diff --git a/scripts/verify.py b/scripts/verify.py index 196288bc5..67aef9f42 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -1463,7 +1463,7 @@ def verify_platform( region_drops, report_for_builder, ExtraResolver( db, DEFAULT_BIOS_DIR, zip_contents, data_dir_registry, validation_index, - validation_owners, profiles=profiles, + validation_owners, profiles=profiles, pending_assets=True, ), placed_kind, system_filter, diff --git a/tests/test_verify_release_asset.py b/tests/test_verify_release_asset.py index 34e104f99..58150b375 100644 --- a/tests/test_verify_release_asset.py +++ b/tests/test_verify_release_asset.py @@ -123,6 +123,59 @@ class ReleaseAssetNotCachedMd5(ReleaseAssetNotCached): mode = "md5" +class CoreExtraReleaseAssetNotCached(ReleaseAssetNotCached): + """The same asset read by a core and declared by no platform entry: the + builder fetched and packed it online, verify's replay of the extras + resolved it offline and left its path out of the pack.""" + + def setUp(self): + super().setUp() + self.config["systems"]["sys"]["files"] = [] + self.config["cores"] = ["bigcore"] + Path("platforms/big.yml").write_text(yaml.dump(self.config)) + self.profiles = {"bigcore": { + "emulator": "Big Core", "type": "libretro", "systems": ["sys"], + "files": [{"name": "big.bin", "required": True, "sha1": SHA1}], + }} + Path("emulators/bigcore.yml").write_text(yaml.dump(self.profiles["bigcore"])) + common._platform_config_cache.clear() + common._emulator_profiles_cache.clear() + + def _report(self) -> dict: + return verify_platform(self.config, self.db, "emulators", self.profiles) + + def test_counted_as_fetched_when_the_pack_is_built(self): + report = self._report() + self.assertIn("big.bin", report["pack_destinations"]) + self.assertEqual(report["packed_extras"]["bios"], 1) + + def test_its_path_is_a_pack_destination(self): + cache = Path("cache/big.bin") + cache.parent.mkdir() + cache.write_bytes(PAYLOAD) + with mock.patch.object( + packresolve, "fetch_large_file", return_value=str(cache) + ): + zip_path = builder.generate_pack( + "big", "platforms", self.db, "bios", "out", + emulators_dir="emulators", emu_profiles=self.profiles, offline=False, + ) + with zipfile.ZipFile(zip_path) as zf: + packed = {n for n in zf.namelist() if not n.endswith("/")} + self.assertIn("big.bin", packed) + self.assertIn("big.bin", self._report()["pack_destinations"]) + + def test_offline_counts_it_missing(self): + packresolve.set_offline(True) + self.assertNotIn("big.bin", self._report()["pack_destinations"]) + + def test_an_asset_with_other_content_is_not_counted(self): + self.profiles["bigcore"]["files"][0]["sha1"] = "0" * 40 + self.assertNotIn("big.bin", self._report()["pack_destinations"]) + + test_the_online_build_packs_it = None + + class OfflinePipelineVerifiesOffline(unittest.TestCase): """The offline pipeline builds packs without release assets: its verify step has to count them the same way, or step 5 compares two answers."""