Files
libretro/tests/test_core_file_fills_declaration.py
T
Abdessamad Derraz 4231902a6c docs: write prose without dashes or semicolons
Profiles, the wiki, comments, docstrings, command output and schema descriptions carried em-dashes, clause-joining semicolons, unicode arrows, box-drawing banners and check-mark glyphs. Each sentence was rewritten rather than having its character swapped. Hashes, paths, source_ref values and every cited file:line are unchanged, which a comparison of each parsed profile against the previous revision confirms.

validate_pr comments now say PASS and FAIL in words, and source_ref values in a profile's structured blocks render as code. test_prose_style fails if any of these comes back, in tracked files or in the generated site.
2026-10-19 06:47:31 +02:00

381 lines
17 KiB
Python

"""A core's file at a free platform destination answers an existence check.
The builder packs a core extra at a destination the platform declares and
does not hold, and in existence mode counts the declaration met: the
frontend sees a file at the path. verify kept it MISSING, so the pack said
1/1 OK and verify 0/1 for the same platform.
"""
from __future__ import annotations
import hashlib
import contextlib
import io
import shutil
import sys
import tempfile
import unittest
import zipfile
from contextlib import redirect_stdout
from pathlib import Path
from unittest import mock
import yaml
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from common import _emulator_profiles_cache, load_platform_config # noqa: E402
from generate_pack import generate_pack # noqa: E402
from verify import verify_platform # noqa: E402
class ACoreFileFillsAFreeDeclaration(unittest.TestCase):
def setUp(self):
_emulator_profiles_cache.clear()
(REPO_ROOT / "tmp").mkdir(exist_ok=True)
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, self.root, True)
bios = self.root / "bios" / "Maker" / "Console"
bios.mkdir(parents=True)
data = b"HELD BOOT ROM"
(bios / "boot.bin").write_bytes(data)
sha1 = hashlib.sha1(data).hexdigest()
md5 = hashlib.md5(data).hexdigest()
self.db = {
"files": {sha1: {
"path": str(bios / "boot.bin"), "name": "boot.bin",
"md5": md5, "crc32": "", "size": len(data),
}},
"indexes": {
"by_md5": {md5: sha1}, "by_name": {"boot.bin": [sha1]},
"by_crc32": {}, "by_path_suffix": {},
},
}
self.platforms = self.root / "platforms"
self.emulators = self.root / "emulators"
self.platforms.mkdir()
self.emulators.mkdir()
(self.platforms / "fakeplat.yml").write_text(yaml.safe_dump({
"platform": "FakePlat",
"verification_mode": "existence",
"base_destination": "system",
"cores": ["fakecore"],
"systems": {"console-a": {"files": [
{"name": "zzz_absent.bin", "destination": "zzz_absent.bin",
"required": True},
]}},
}))
# The entry's name differs from the destination it fills, as
# descriptive names do ("MDA font ROM" at mda.rom).
(self.emulators / "fakecore.yml").write_text(yaml.safe_dump({
"emulator": "FakeCore", "type": "libretro", "cores": ["fakecore"],
"systems": ["console-a"],
"files": [{"name": "Fake boot ROM", "path": "zzz_absent.bin",
"sha1": sha1, "required": True}],
}))
self.config = load_platform_config("fakeplat", str(self.platforms))
def test_verify_counts_what_the_pack_counts(self):
result = verify_platform(
self.config, self.db, str(self.emulators),
)
out = self.root / "out"
with redirect_stdout(io.StringIO()):
zip_path = generate_pack(
"fakeplat", str(self.platforms), self.db, str(self.root / "bios"),
str(out), emulators_dir=str(self.emulators), offline=True,
)
with zipfile.ZipFile(zip_path) as zf:
self.assertIn("zzz_absent.bin", zf.namelist())
self.assertEqual(result["status_counts"], {"ok": 1})
self.assertEqual(
[d["status"] for d in result["details"]], ["ok"],
)
class AFileDeclaredUnderAnotherName(unittest.TestCase):
"""The platform declares ROM1 with the md5 of the core's ROM. The
builder packs ROM at the core's path, but verify read the enriched
declarations, took ROM as declared and never counted it."""
def test_the_report_lists_what_the_builder_packs(self):
_emulator_profiles_cache.clear()
root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, root, True)
bios = root / "bios" / "Apple" / "IIgs"
bios.mkdir(parents=True)
data = b"IIGS ROM BYTES"
(bios / "ROM").write_bytes(data)
sha1, md5 = hashlib.sha1(data).hexdigest(), hashlib.md5(data).hexdigest()
db = {
"files": {sha1: {"path": str(bios / "ROM"), "name": "ROM", "md5": md5,
"crc32": "", "size": len(data)}},
"indexes": {"by_md5": {md5: sha1}, "by_name": {"ROM": [sha1]},
"by_crc32": {}, "by_path_suffix": {}},
}
(root / "platforms").mkdir()
(root / "emulators").mkdir()
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
"platform": "FakePlat", "verification_mode": "existence",
"base_destination": "system", "cores": ["gs"],
"systems": {"apple-iigs": {"files": [
{"name": "ROM1", "destination": "ROM1", "md5": md5},
]}},
}))
(root / "emulators" / "gs.yml").write_text(yaml.safe_dump({
"emulator": "GS", "type": "libretro", "cores": ["gs"],
"systems": ["apple-iigs"],
"files": [{"name": "ROM", "path": "gs/ROM", "md5": md5, "required": True}],
}))
config = load_platform_config("fakeplat", str(root / "platforms"))
result = verify_platform(config, db, str(root / "emulators"))
with redirect_stdout(io.StringIO()):
zip_path = generate_pack(
"fakeplat", str(root / "platforms"), db, str(root / "bios"),
str(root / "out"), emulators_dir=str(root / "emulators"), offline=True,
)
with zipfile.ZipFile(zip_path) as zf:
self.assertIn("gs/ROM", zf.namelist())
reported = [(u["name"], u["in_repo"]) for u in result["undeclared_files"]]
self.assertIn(("ROM", True), reported)
class AMissingDataDirectoryFailsTheRun(unittest.TestCase):
"""A refresh refused by a quota left the PPSSPP pack with 2 files and
the run exited 0: nothing downstream checks data directories."""
def test_the_pack_records_it_and_main_exits_non_zero(self):
import generate_pack as gp
root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, root, True)
(root / "platforms").mkdir()
(root / "emulators").mkdir()
(root / "bios").mkdir()
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
"platform": "FakePlat", "verification_mode": "existence",
"base_destination": "system", "cores": [],
"systems": {"console-a": {"files": [], "data_directories": [
{"ref": "assets", "destination": "assets"},
]}},
}))
db = {"files": {}, "indexes": {"by_md5": {}, "by_name": {}, "by_crc32": {},
"by_path_suffix": {}}}
gp._MISSING_DATA_DIRS.clear()
self.addCleanup(gp._MISSING_DATA_DIRS.clear)
with redirect_stdout(io.StringIO()):
gp.generate_pack(
"fakeplat", str(root / "platforms"), db, str(root / "bios"),
str(root / "out"), emulators_dir=str(root / "emulators"),
data_registry={"assets": {"local_cache": str(root / "absent")}},
offline=True,
)
self.assertEqual(gp._MISSING_DATA_DIRS, {"assets"})
with mock.patch.object(gp, "_main", lambda held: None), \
contextlib.redirect_stderr(io.StringIO()), \
self.assertRaises(SystemExit) as stop:
gp.main()
self.assertEqual(stop.exception.code, 1)
class AFileNobodyHoldsIsAnOmission(unittest.TestCase):
"""DevilutionX's diabdat.mpq, required and not in the collection, was
in neither the manifest's files nor its omitted_files: the installer
showed no notice and said everything was verified."""
def test_the_manifest_names_it(self):
from generate_pack import generate_manifest
_emulator_profiles_cache.clear()
root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, root, True)
for folder in ("platforms", "emulators", "bios"):
(root / folder).mkdir()
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
"platform": "FakePlat", "verification_mode": "existence",
"base_destination": "system", "cores": ["devil"],
"systems": {"diablo": {"files": []}},
}))
(root / "platforms" / "_registry.yml").write_text(yaml.safe_dump({
"platforms": {"fakeplat": {"config": "fakeplat.yml", "status": "active"}},
}))
(root / "emulators" / "devil.yml").write_text(yaml.safe_dump({
"emulator": "Devil", "type": "libretro", "cores": ["devil"],
"systems": ["diablo"],
"files": [{"name": "diabdat.mpq", "required": True,
"sha1": "1" * 40, "category": "game_data"}],
}))
db = {"files": {}, "indexes": {"by_md5": {}, "by_name": {}, "by_crc32": {},
"by_path_suffix": {}}}
manifest = generate_manifest(
"fakeplat", str(root / "platforms"), db, str(root / "bios"),
str(root / "platforms" / "_registry.yml"),
emulators_dir=str(root / "emulators"), offline=True,
)
omitted = {o["name"]: o for o in manifest["omitted_files"]}
self.assertIn("diabdat.mpq", omitted)
self.assertTrue(omitted["diabdat.mpq"]["required"])
self.assertEqual(omitted["diabdat.mpq"]["reason"], "not_found")
class AMemberNameIsNotALooseFile(unittest.TestCase):
"""verify counted a core file "in pack" when its name existed only as a
member of some ZIP (naomi2.zip's 315-6146.bin), but the builder, which reads
no archive member names, shipped nothing."""
def test_verify_reads_undeclared_files_as_the_builder_does(self):
import cross_reference
_emulator_profiles_cache.clear()
root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, root, True)
(root / "platforms").mkdir()
(root / "emulators").mkdir()
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
"platform": "FakePlat", "verification_mode": "existence",
"base_destination": "system", "cores": ["naomi2core"],
"systems": {"sega-naomi2": {"files": []}},
}))
(root / "emulators" / "naomi2core.yml").write_text(yaml.safe_dump({
"emulator": "N2", "type": "libretro", "cores": ["naomi2core"],
"systems": ["sega-naomi2"],
"files": [{"name": "315-6146.bin", "required": True}],
}))
db = {"files": {}, "indexes": {"by_md5": {}, "by_name": {}, "by_crc32": {},
"by_path_suffix": {}}}
config = load_platform_config("fakeplat", str(root / "platforms"))
with mock.patch.object(cross_reference, "_build_supplemental_index",
lambda *a, **k: {"315-6146.bin"}):
result = verify_platform(config, db, str(root / "emulators"))
self.assertEqual(
[(u["name"], u["in_repo"]) for u in result["undeclared_files"]],
[("315-6146.bin", False)],
)
class TwoDeclarationsOfOnePath(unittest.TestCase):
"""Two systems declare one destination under two names, no hash, one
held. The builder packed the held file and kept the first verdict it
read, while verify kept the worst, on the raw destination. Read either way
round, both must count the path once and met."""
def _run(self, first: str, second: str, spelling: str):
_emulator_profiles_cache.clear()
root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, root, True)
bios = root / "bios" / "Maker" / "Console"
bios.mkdir(parents=True)
data = b"SHARED BOOT ROM"
(bios / "boot.bin").write_bytes(data)
sha1, md5 = hashlib.sha1(data).hexdigest(), hashlib.md5(data).hexdigest()
db = {
"files": {sha1: {"path": str(bios / "boot.bin"), "name": "boot.bin",
"md5": md5, "crc32": "", "size": len(data)}},
"indexes": {"by_md5": {md5: sha1}, "by_name": {"boot.bin": [sha1]},
"by_crc32": {}, "by_path_suffix": {}},
}
for folder in ("platforms", "emulators"):
(root / folder).mkdir()
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
"platform": "FakePlat", "verification_mode": "existence",
"base_destination": "system", "cores": [],
"systems": {
"a-system": {"files": [{"name": first, "destination": "shared.bin"}]},
"b-system": {"files": [{"name": second, "destination": spelling}]},
},
}))
config = load_platform_config("fakeplat", str(root / "platforms"))
result = verify_platform(config, db, str(root / "emulators"))
out = io.StringIO()
with redirect_stdout(out):
zip_path = generate_pack(
"fakeplat", str(root / "platforms"), db, str(root / "bios"),
str(root / "out"), emulators_dir=str(root / "emulators"), offline=True,
)
with zipfile.ZipFile(zip_path) as zf:
self.assertIn("shared.bin", zf.namelist())
return result["status_counts"], out.getvalue()
def test_either_order_counts_one_met_path(self):
for first, second in (("boot.bin", "absent.bin"), ("absent.bin", "boot.bin")):
for spelling in ("shared.bin", "./shared.bin"):
with self.subTest(first=first, spelling=spelling):
counts, printed = self._run(first, second, spelling)
self.assertEqual(counts, {"ok": 1})
self.assertIn("1/1 files OK", printed)
class ANamelessWhitelistEntry(unittest.TestCase):
"""EmuDeck lists md5 values with no file name. The builder looked the
raw string up in by_md5: an uppercase value or a comma list was
missing in the pack and met in verify."""
def test_the_pack_counts_what_verify_counts(self):
_emulator_profiles_cache.clear()
root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, root, True)
bios = root / "bios" / "Sony" / "PlayStation"
bios.mkdir(parents=True)
data = b"PSX BIOS BYTES"
(bios / "scph.bin").write_bytes(data)
sha1, md5 = hashlib.sha1(data).hexdigest(), hashlib.md5(data).hexdigest()
db = {
"files": {sha1: {"path": str(bios / "scph.bin"), "name": "scph.bin",
"md5": md5, "crc32": "", "size": len(data)}},
"indexes": {"by_md5": {md5: sha1}, "by_name": {"scph.bin": [sha1]},
"by_crc32": {}, "by_path_suffix": {}},
}
for folder in ("platforms", "emulators"):
(root / folder).mkdir()
values = [md5.upper(), f"{'0' * 32},{md5}", "f" * 32]
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
"platform": "FakePlat", "verification_mode": "md5",
"base_destination": "bios", "cores": [],
"systems": {"sony-playstation": {"files": [
{"name": f"sony-playstation:{v}", "destination": "", "md5": v}
for v in values
]}},
}))
config = load_platform_config("fakeplat", str(root / "platforms"))
result = verify_platform(config, db, str(root / "emulators"))
out = io.StringIO()
with redirect_stdout(out):
generate_pack(
"fakeplat", str(root / "platforms"), db, str(root / "bios"),
str(root / "out"), emulators_dir=str(root / "emulators"), offline=True,
)
self.assertEqual(result["status_counts"], {"ok": 2, "missing": 1})
self.assertIn("2/3 files OK", out.getvalue())
class TheManifestResolvesLikeThePack(unittest.TestCase):
"""The builder resolved core files with the data directories and the
manifest without: blueMSX's pack carried its own MSX2P.rom while the
manifest named another dump for the installer to place."""
def test_every_platform_resolution_reads_the_data_directories(self):
import ast
source = (REPO_ROOT / "scripts" / "generate_pack.py").read_text(encoding="utf-8")
missing = []
for fn in ast.walk(ast.parse(source)):
if not isinstance(fn, ast.FunctionDef) or fn.name == "generate_md5_pack":
continue
for node in ast.walk(fn):
if (
isinstance(node, ast.Call)
and getattr(node.func, "id", None) == "resolve_file"
and "data_dir_registry" not in {k.arg for k in node.keywords}
):
missing.append(f"{fn.name}:{node.lineno}")
self.assertEqual(missing, [])
if __name__ == "__main__":
unittest.main()