Files
libretro/tests/test_path_conflict_status.py

228 lines
9.0 KiB
Python

"""A declaration that conflicts with a packed path is counted by what it resolves to.
RetroArch declares SGB1.sfc as a file and SGB1.sfc/<rom> as files inside it.
The builder ships one shape and counted every conflicting declaration OK,
resolved or not; verify.py resolves each on its own. A required file absent
from the collection read as covered in the pack report and as missing in verify.
"""
from __future__ import annotations
import contextlib
import hashlib
import io
import re
import sys
import tempfile
import unittest
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import common # noqa: E402
import generate_db # noqa: E402
import generate_pack as builder # noqa: E402
from verify import verify_platform # noqa: E402
class ConflictingDeclarations(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.root = Path(self._tmp.name)
bios = self.root / "bios" / "Nintendo"
self.platforms = self.root / "platforms"
self.emulators = self.root / "emulators"
for directory in (bios, self.platforms, self.emulators):
directory.mkdir(parents=True)
payload = b"super game boy"
(bios / "SGB1.sfc").write_bytes(payload)
sha1 = hashlib.sha1(payload).hexdigest()
files = {
sha1: {
"path": str(bios / "SGB1.sfc"), "name": "SGB1.sfc",
"size": len(payload), "sha1": sha1,
"md5": hashlib.md5(payload).hexdigest(),
"sha256": hashlib.sha256(payload).hexdigest(), "crc32": "00000001",
}
}
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
platform = {
"platform": "Conflict",
"verification_mode": "existence",
"base_destination": "system",
"cores": [],
"systems": {
"nintendo-sgb": {"files": [
{"name": "SGB1.sfc", "destination": "SGB1.sfc", "required": True},
{"name": "nothere.rom", "destination": "SGB1.sfc/nothere.rom",
"required": True},
]},
},
}
(self.platforms / "conflict.yml").write_text(yaml.dump(platform))
(self.platforms / "_registry.yml").write_text(
yaml.dump({"platforms": {"conflict": {"status": "active"}}})
)
common._platform_config_cache.clear()
common._emulator_profiles_cache.clear()
def tearDown(self):
common._platform_config_cache.clear()
self._tmp.cleanup()
def test_the_pack_and_verify_count_alike(self):
out = self.root / "dist"
out.mkdir()
report = io.StringIO()
with contextlib.redirect_stdout(report):
builder.generate_pack(
"conflict", str(self.platforms), self.db, str(self.root / "bios"),
str(out), emulators_dir=str(self.emulators), emu_profiles={},
offline=True,
)
packed = re.search(r"(\d+)/(\d+) files OK", report.getvalue())
self.assertIsNotNone(packed, report.getvalue())
config = common.load_platform_config("conflict", str(self.platforms))
verified = verify_platform(config, self.db, str(self.emulators))
counts = verified["status_counts"]
self.assertEqual(counts.get("missing"), 1)
self.assertEqual(
(int(packed.group(1)), int(packed.group(2))),
(counts.get("ok", 0), verified["total_files"]),
)
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())
class TheEmulatorPackShipsWhatTheCoreAccepts(unittest.TestCase):
"""BasiliskII wants a 512 KB to 1 MB ROM. The only ROM held was 128 KB:
the emulator pack shipped it silently and verify --emulator announced
1/1 OK above the line saying the core would refuse it."""
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.root = Path(self._tmp.name)
bios = self.root / "bios" / "Apple"
self.emulators = self.root / "emulators"
for directory in (bios, self.emulators):
directory.mkdir(parents=True)
payload = b"r" * 131072
(bios / "ROM").write_bytes(payload)
sha1 = hashlib.sha1(payload).hexdigest()
files = {sha1: {
"path": str(bios / "ROM"), "name": "ROM", "size": len(payload),
"sha1": sha1, "md5": hashlib.md5(payload).hexdigest(),
"sha256": hashlib.sha256(payload).hexdigest(), "crc32": "00000003",
}}
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
profile = {
"emulator": "Mac", "type": "libretro", "cores": ["mac"],
"systems": ["apple-macintosh"],
"files": [{"name": "ROM", "required": True, "min_size": 524288,
"max_size": 1048576, "validation": ["size"]}],
}
(self.emulators / "mac.yml").write_text(yaml.dump(profile))
common._emulator_profiles_cache.clear()
def tearDown(self):
common._emulator_profiles_cache.clear()
self._tmp.cleanup()
def test_the_refused_rom_is_named_not_shipped(self):
import zipfile
out = self.root / "dist"
out.mkdir()
report = io.StringIO()
with contextlib.redirect_stdout(report):
zip_path = builder.generate_emulator_pack(
["mac"], str(self.emulators), self.db, str(self.root / "bios"),
str(out), offline=True,
)
self.assertIn("REFUSED: ROM", report.getvalue())
with zipfile.ZipFile(zip_path) as archive:
self.assertNotIn("ROM", archive.namelist())
def test_verify_does_not_call_it_ok(self):
from verify import Severity, verify_emulator
result = verify_emulator(["mac"], str(self.emulators), self.db)
self.assertEqual(result["severity_counts"][Severity.OK], 0)
if __name__ == "__main__":
unittest.main()