diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index a60158d0..069debf6 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -1085,17 +1085,21 @@ def generate_pack( else: _add_pack_member(zf, local_path, flat_dest) if file_status.get(full_dest) in ("missing", "excluded"): - previous = file_status[full_dest] - file_status[full_dest] = "ok" - if previous == "excluded": - source_name = fe.get("source_profile") or fe.get( - "source_emulator", "core profile" - ) + source_name = fe.get("source_profile") or fe.get( + "source_emulator", "core profile" + ) + if reads_file_contents(verification_mode): + # The frontend hashes what sits at the path, and the core's + # copy is not the hash it declares: the file ships for the + # core, the platform's own check still fails, as verify says. file_reasons[full_dest] = ( "platform-declared hash unavailable; " - f"packed the validated {source_name} requirement" + f"packed the validated {source_name} requirement, " + "which the platform check does not accept" ) else: + # An existence frontend is satisfied by any file at the path. + file_status[full_dest] = "ok" file_reasons.pop(full_dest, None) seen_destinations.add(full_dest) _register_path(full_dest, seen_destinations, seen_parents) diff --git a/tests/test_path_conflict_status.py b/tests/test_path_conflict_status.py index 6b71d49b..70d834cc 100644 --- a/tests/test_path_conflict_status.py +++ b/tests/test_path_conflict_status.py @@ -96,5 +96,76 @@ class ConflictingDeclarations(unittest.TestCase): ) +class AnExtraDoesNotPassAHashCheck(unittest.TestCase): + """A md5 platform declares scph1001.bin with a hash the collection lacks; + a core declares our dump at the same path. The builder ships the core's + file and called the declaration OK, while the frontend, which hashes the + file, rejects it and verify reports it untested.""" + + def setUp(self): + self._tmp = tempfile.TemporaryDirectory() + self.root = Path(self._tmp.name) + bios = self.root / "bios" / "Sony" + self.platforms = self.root / "platforms" + self.emulators = self.root / "emulators" + for directory in (bios, self.platforms, self.emulators): + directory.mkdir(parents=True) + payload = b"our psx dump" + (bios / "psx-us.bin").write_bytes(payload) + sha1 = hashlib.sha1(payload).hexdigest() + md5 = hashlib.md5(payload).hexdigest() + files = { + sha1: { + "path": str(bios / "psx-us.bin"), "name": "psx-us.bin", + "size": len(payload), "sha1": sha1, "md5": md5, + "sha256": hashlib.sha256(payload).hexdigest(), "crc32": "00000002", + } + } + self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})} + platform = { + "platform": "Hashed", + "verification_mode": "md5", + "cores": ["fxcore"], + "systems": {"sony-playstation": {"files": [ + {"name": "scph1001.bin", "destination": "scph1001.bin", + "md5": "0123456789abcdef0123456789abcdef", "required": True}, + ]}}, + } + (self.platforms / "hashed.yml").write_text(yaml.dump(platform)) + (self.platforms / "_registry.yml").write_text( + yaml.dump({"platforms": {"hashed": {"status": "active"}}}) + ) + profile = { + "emulator": "FX", "type": "libretro", "cores": ["fxcore"], + "systems": ["sony-playstation"], + "files": [{"name": "psx-us.bin", "path": "scph1001.bin", "md5": md5, + "required": True}], + } + (self.emulators / "fxcore.yml").write_text(yaml.dump(profile)) + common._platform_config_cache.clear() + common._emulator_profiles_cache.clear() + + def tearDown(self): + common._platform_config_cache.clear() + common._emulator_profiles_cache.clear() + self._tmp.cleanup() + + def test_the_declaration_stays_failed(self): + out = self.root / "dist" + out.mkdir() + report = io.StringIO() + with contextlib.redirect_stdout(report): + builder.generate_pack( + "hashed", str(self.platforms), self.db, str(self.root / "bios"), + str(out), emulators_dir=str(self.emulators), offline=True, + ) + packed = re.search(r"(\d+)/(\d+) files OK", report.getvalue()) + self.assertIsNotNone(packed, report.getvalue()) + config = common.load_platform_config("hashed", str(self.platforms)) + verified = verify_platform(config, self.db, str(self.emulators)) + self.assertEqual(verified["status_counts"].get("ok", 0), 0) + self.assertEqual(int(packed.group(1)), 0, report.getvalue()) + + if __name__ == "__main__": unittest.main()