mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 13:33:24 -05:00
A declared hash that the local dump contradicts is not one situation. An existence platform never reads the bytes, so withholding the file lets an upstream list error remove something the frontend would have loaded; a hash platform would reject it, so shipping it is pointless. The mode now decides, at every point that had an opinion: pack building, core complement, emulator packs, manifests, conformance and _intentional_hash_exclusion. verify.find_undeclared_files follows, since verify and generate_pack must agree file for file. Also here: resolution reports which evidence matched rather than a flat "exact", a path or filename can no longer override a declared hash, and safe_extract_zip treats a Windows backslash as the separator it is instead of refusing the archive.
158 lines
6.0 KiB
Python
158 lines
6.0 KiB
Python
"""Tests for the TorrentZip builder against real MAME romsets."""
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
|
|
REPO_ROOT = os.path.join(os.path.dirname(__file__), "..")
|
|
sys.path.insert(0, os.path.join(REPO_ROOT, "scripts"))
|
|
|
|
from torrentzip import ( # noqa: E402
|
|
build_torrentzip,
|
|
identify_romset,
|
|
is_torrentzip,
|
|
rebuild_torrentzip,
|
|
)
|
|
|
|
|
|
class TorrentZipTest(unittest.TestCase):
|
|
"""The format is deterministic: same members always give the same bytes."""
|
|
|
|
def test_deterministic_output(self):
|
|
members = [("b.rom", b"second"), ("a.rom", b"first")]
|
|
self.assertEqual(build_torrentzip(members), build_torrentzip(members))
|
|
|
|
def test_member_order_is_irrelevant(self):
|
|
a = build_torrentzip([("a.rom", b"one"), ("b.rom", b"two")])
|
|
b = build_torrentzip([("b.rom", b"two"), ("a.rom", b"one")])
|
|
self.assertEqual(a, b)
|
|
|
|
def test_case_insensitive_sorting(self):
|
|
data = build_torrentzip([("B.rom", b"x"), ("a.rom", b"y")])
|
|
with zipfile.ZipFile(_as_file(self, data)) as zf:
|
|
self.assertEqual([i.filename for i in zf.infolist()], ["a.rom", "B.rom"])
|
|
|
|
def test_fixed_timestamp_and_signature(self):
|
|
data = build_torrentzip([("a.rom", b"payload")])
|
|
with zipfile.ZipFile(_as_file(self, data)) as zf:
|
|
self.assertEqual(zf.infolist()[0].date_time, (1996, 12, 24, 23, 32, 0))
|
|
self.assertTrue(zf.comment.decode().startswith("TORRENTZIPPED-"))
|
|
|
|
def test_roundtrip_readable_content(self):
|
|
data = build_torrentzip([("a.rom", b"hello"), ("b.rom", b"world")])
|
|
with zipfile.ZipFile(_as_file(self, data)) as zf:
|
|
self.assertEqual(zf.read("a.rom"), b"hello")
|
|
self.assertEqual(zf.read("b.rom"), b"world")
|
|
|
|
def test_rebuild_matches_real_mame_set(self):
|
|
"""Rebuilding a shipped MAME romset reproduces it byte for byte."""
|
|
candidates = [
|
|
os.path.join(REPO_ROOT, "bios", "Arcade", "Arcade", "naomi2.zip"),
|
|
os.path.join(REPO_ROOT, "bios", "Arcade", "MAME", "naomi2.zip"),
|
|
]
|
|
checked = 0
|
|
for path in candidates:
|
|
if not os.path.exists(path) or not is_torrentzip(path):
|
|
continue
|
|
with open(path, "rb") as fh:
|
|
original = fh.read()
|
|
self.assertEqual(rebuild_torrentzip(path), original, path)
|
|
checked += 1
|
|
if not checked:
|
|
self.skipTest("no TorrentZip romset available")
|
|
|
|
def test_source_renamed_sets_stay_reproducible(self):
|
|
"""Archives whose member names we corrected must rebuild identically.
|
|
|
|
Renaming a ROM by hand produced archives that matched no upstream set
|
|
and could not be regenerated from their contents either. The corrected
|
|
primaries carry the MAME 0.289 names and TorrentZip bytes, so anyone
|
|
holding the ROMs reproduces them exactly.
|
|
"""
|
|
corrected = [
|
|
os.path.join(REPO_ROOT, "bios", "Arcade", "MAME", "manager.zip"),
|
|
os.path.join(REPO_ROOT, "bios", "Arcade", "Arcade", "sys573.zip"),
|
|
os.path.join(REPO_ROOT, "bios", "Arcade", "Arcade", "cedmag.zip"),
|
|
os.path.join(
|
|
REPO_ROOT, "bios", "Entex", "Adventure Vision", "advision.zip"
|
|
),
|
|
]
|
|
checked = 0
|
|
for path in corrected:
|
|
if not os.path.exists(path):
|
|
continue
|
|
self.assertTrue(is_torrentzip(path), path)
|
|
with open(path, "rb") as fh:
|
|
original = fh.read()
|
|
self.assertEqual(rebuild_torrentzip(path), original, path)
|
|
checked += 1
|
|
if not checked:
|
|
self.skipTest("corrected romsets not present in this checkout")
|
|
|
|
def test_source_renamed_sets_carry_the_source_names(self):
|
|
"""The member names come from MAME, not from a YAML round-trip."""
|
|
expected = {
|
|
os.path.join(REPO_ROOT, "bios", "Arcade", "MAME", "manager.zip"): {
|
|
"01", "23",
|
|
},
|
|
os.path.join(
|
|
REPO_ROOT, "bios", "Entex", "Adventure Vision", "advision.zip"
|
|
): {"ins8048-11kdp_n.u5", "cop411l-kcn_n.u8"},
|
|
}
|
|
checked = 0
|
|
for path, names in expected.items():
|
|
if not os.path.exists(path):
|
|
continue
|
|
with zipfile.ZipFile(path) as archive:
|
|
members = {
|
|
info.filename
|
|
for info in archive.infolist()
|
|
if not info.is_dir()
|
|
}
|
|
self.assertEqual(members, names, path)
|
|
checked += 1
|
|
if not checked:
|
|
self.skipTest("corrected romsets not present in this checkout")
|
|
|
|
def test_identify_romset_selects_matching_recipe(self):
|
|
atoms = {"3610a686": b"alpha", "9d0d1b46": b"beta"}
|
|
import zlib
|
|
|
|
atoms = {
|
|
f"{zlib.crc32(b'alpha') & 0xffffffff:08x}": b"alpha",
|
|
f"{zlib.crc32(b'beta') & 0xffffffff:08x}": b"beta",
|
|
}
|
|
crc_a, crc_b = list(atoms)
|
|
wanted = hashlib.md5(
|
|
build_torrentzip([("a.rom", b"alpha"), ("b.rom", b"beta")])
|
|
).hexdigest()
|
|
recipes = {
|
|
"v1": [("a.rom", crc_a)],
|
|
"v2": [("a.rom", crc_a), ("b.rom", crc_b)],
|
|
}
|
|
self.assertEqual(identify_romset(recipes, atoms, wanted), "v2")
|
|
self.assertIsNone(identify_romset(recipes, atoms, "0" * 32))
|
|
|
|
def test_identify_romset_skips_recipes_with_absent_atoms(self):
|
|
atoms = {"aaaaaaaa": b"x"}
|
|
recipes = {"v1": [("a.rom", "ffffffff")]}
|
|
self.assertIsNone(identify_romset(recipes, atoms, "0" * 32))
|
|
|
|
|
|
def _as_file(test: unittest.TestCase, data: bytes) -> str:
|
|
"""Write bytes to a temp file cleaned up with the test."""
|
|
fd, path = tempfile.mkstemp(suffix=".zip")
|
|
os.close(fd)
|
|
with open(path, "wb") as fh:
|
|
fh.write(data)
|
|
test.addCleanup(os.unlink, path)
|
|
return path
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|