Files
libretro/tests/test_validate_pr.py
T

326 lines
13 KiB
Python

#!/usr/bin/env python3
"""The gate that inspects contributor files before a PR is reviewed.
validate.yml runs this on every added or modified file under bios/, and its
output is posted to the PR. It reads paths chosen by whoever opened the PR, so
it is an untrusted-input boundary with nothing behind it.
"""
from __future__ import annotations
import os
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import validate_pr # noqa: E402
EMPTY_HASHES: dict = {"sha1": set(), "md5": set(), "names": set()}
class _Fixture(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.tmp = Path(self._tmp.name)
self._cwd = os.getcwd()
os.chdir(self.tmp)
def tearDown(self):
os.chdir(self._cwd)
self._tmp.cleanup()
def _bios_file(self, relative: str, data: bytes = b"BIOS") -> str:
path = self.tmp / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
return relative
class FileShapeChecks(_Fixture):
def test_missing_file_fails_without_hashing(self):
result = validate_pr.validate_file("bios/nope.bin", None, EMPTY_HASHES)
self.assertFalse(result.passed)
self.assertEqual(result.sha1, "")
def test_empty_file_fails(self):
rel = self._bios_file("bios/Sony/PS/empty.bin", b"")
result = validate_pr.validate_file(rel, None, EMPTY_HASHES)
self.assertFalse(result.passed)
self.assertIn("empty", result.to_markdown().lower())
def test_executable_extension_is_refused(self):
rel = self._bios_file("bios/Sony/PS/payload.exe")
result = validate_pr.validate_file(rel, None, EMPTY_HASHES)
self.assertFalse(result.passed)
self.assertIn("Blocked file extension", result.to_markdown())
def test_oversized_file_is_refused(self):
rel = self._bios_file("bios/Sony/PS/big.bin")
original = validate_pr.MAX_FILE_SIZE
validate_pr.MAX_FILE_SIZE = 2
try:
result = validate_pr.validate_file(rel, None, EMPTY_HASHES)
finally:
validate_pr.MAX_FILE_SIZE = original
self.assertFalse(result.passed)
self.assertIn("too large", result.to_markdown())
def test_ordinary_file_passes(self):
rel = self._bios_file("bios/Sony/PlayStation/scph.bin")
result = validate_pr.validate_file(rel, None, EMPTY_HASHES)
self.assertTrue(result.passed, result.to_markdown())
self.assertEqual(len(result.sha1), 40)
class AFileOnlyACoreReadsIsKnown(_Fixture):
"""A file only an emulator profile declares (a standalone firmware, a
game engine's data) was reported "not referenced in any platform
config - needs manual review"."""
def test_the_profiles_that_declare_the_hash_are_named(self):
import hashlib # noqa: PLC0415
emulators = self.tmp / "emulators"
emulators.mkdir()
data = b"engine data"
(emulators / "engine.yml").write_text(
"emulator: Engine\ntype: standalone\nsystems: [x]\nfiles:\n"
f" - name: \"data.bin\"\n sha1: \"{hashlib.sha1(data).hexdigest()}\"\n"
)
known = validate_pr.load_profile_hashes(str(emulators))
result = validate_pr.validate_file(
self._bios_file("bios/Maker/Engine/data.bin", data), None, EMPTY_HASHES, known,
)
self.assertIn(("PASS", "Hash matches a file the emulator profile declares: engine"),
result.checks)
self.assertFalse(any("not referenced" in message for _s, message in result.checks))
class SymlinksAreRefusedBeforeReading(_Fixture):
"""A symlink must be rejected without its target being read.
The check used to run after compute_hashes, so a link to an endless
device would be read until the CI job timed out, and a link outside the
checkout would be hashed and reported as though it were contributed.
"""
def test_symlink_is_refused(self):
self._bios_file("bios/Sony/PS/real.bin")
link = self.tmp / "bios" / "Sony" / "PS" / "link.bin"
link.symlink_to(self.tmp / "bios" / "Sony" / "PS" / "real.bin")
result = validate_pr.validate_file(
"bios/Sony/PS/link.bin", None, EMPTY_HASHES
)
self.assertFalse(result.passed)
self.assertIn("Symlink", result.to_markdown())
def test_symlink_target_is_never_hashed(self):
self._bios_file("bios/Sony/PS/real.bin")
link = self.tmp / "bios" / "Sony" / "PS" / "link.bin"
link.symlink_to(self.tmp / "bios" / "Sony" / "PS" / "real.bin")
calls = []
real = validate_pr.compute_hashes
validate_pr.compute_hashes = lambda p, *a, **k: (
calls.append(p) or real(p, *a, **k)
)
try:
validate_pr.validate_file("bios/Sony/PS/link.bin", None, EMPTY_HASHES)
finally:
validate_pr.compute_hashes = real
self.assertEqual(calls, [], "a symlink target must not be read")
@unittest.skipUnless(os.path.exists("/dev/zero"), "needs /dev/zero")
def test_a_link_to_an_endless_device_does_not_hang(self):
link = self.tmp / "bios" / "Sony" / "PS" / "zero.bin"
link.parent.mkdir(parents=True, exist_ok=True)
link.symlink_to("/dev/zero")
result = validate_pr.validate_file(
"bios/Sony/PS/zero.bin", None, EMPTY_HASHES
)
self.assertFalse(result.passed)
class HashRecognition(_Fixture):
def test_a_declared_sha1_is_recognised(self):
rel = self._bios_file("bios/Sony/PS/known.bin")
probe = validate_pr.validate_file(rel, None, EMPTY_HASHES)
known = {"sha1": {probe.sha1}, "md5": set(), "names": set()}
result = validate_pr.validate_file(rel, None, known)
self.assertIn("SHA1 matches known platform requirement", result.to_markdown())
def test_a_declared_md5_is_recognised(self):
rel = self._bios_file("bios/Sony/PS/known.bin")
probe = validate_pr.validate_file(rel, None, EMPTY_HASHES)
known = {"sha1": set(), "md5": {probe.md5}, "names": set()}
self.assertIn(
"MD5 matches known platform requirement",
validate_pr.validate_file(rel, None, known).to_markdown(),
)
def test_a_known_name_with_another_hash_is_flagged_as_a_variant(self):
rel = self._bios_file("bios/Sony/PS/known.bin")
known = {"sha1": set(), "md5": set(), "names": {"known.bin"}}
markdown = validate_pr.validate_file(rel, None, known).to_markdown()
self.assertIn("may be a variant", markdown)
self.assertIn(".variants/known.bin.", markdown)
def test_an_unreferenced_file_asks_for_review(self):
rel = self._bios_file("bios/Sony/PS/mystery.bin")
markdown = validate_pr.validate_file(rel, None, EMPTY_HASHES).to_markdown()
self.assertIn("needs manual review", markdown)
def test_a_duplicate_of_a_known_file_is_reported(self):
rel = self._bios_file("bios/Sony/PS/dupe.bin")
probe = validate_pr.validate_file(rel, None, EMPTY_HASHES)
db = {"files": {probe.sha1: {"path": "bios/Sony/PS/original.bin"}}}
markdown = validate_pr.validate_file(rel, db, EMPTY_HASHES).to_markdown()
self.assertIn("Duplicate", markdown)
self.assertIn("original.bin", markdown)
# A duplicate is worth saying out loud but must not block the PR.
self.assertTrue(validate_pr.validate_file(rel, db, EMPTY_HASHES).passed)
def test_the_file_the_database_holds_is_not_its_own_duplicate(self):
"""The documented example on a tracked file warned "Duplicate:
identical file already exists at" the very path being validated."""
rel = self._bios_file("bios/Sony/PS/held.bin")
probe = validate_pr.validate_file(rel, None, EMPTY_HASHES)
db = {"files": {probe.sha1: {"path": rel}}}
markdown = validate_pr.validate_file(rel, db, EMPTY_HASHES).to_markdown()
self.assertNotIn("Duplicate", markdown)
self.assertIn("Already in the database at this path", markdown)
class PlacementBySystem(_Fixture):
"""Depth alone passed a PS1 BIOS filed under bios/Nintendo/Game Boy/:
a declared hash names its system, and the files of that system already
held name its folder."""
def _known(self, sha1: str) -> dict:
return {"sha1": {sha1}, "md5": set(), "names": set(),
"folders": {sha1: {"bios/Sony/PlayStation/"}}}
def test_a_file_under_another_maker_is_told_where_its_system_lives(self):
rel = self._bios_file("bios/Nintendo/Game Boy/scph.bin")
probe = validate_pr.validate_file(rel, None, EMPTY_HASHES)
markdown = validate_pr.validate_file(rel, None, self._known(probe.sha1)).to_markdown()
self.assertIn("bios/Sony/PlayStation/", markdown)
self.assertNotIn("Correct placement", markdown)
def test_the_folder_of_its_system_passes(self):
rel = self._bios_file("bios/Sony/PlayStation/scph.bin")
probe = validate_pr.validate_file(rel, None, EMPTY_HASHES)
markdown = validate_pr.validate_file(rel, None, self._known(probe.sha1)).to_markdown()
self.assertIn("Correct placement: bios/Sony/PlayStation/", markdown)
def test_systems_spread_over_makers_name_no_home(self):
platforms = self.tmp / "platforms"
platforms.mkdir()
(platforms / "_registry.yml").write_text(
"platforms:\n p:\n config: p.yml\n status: active\n", encoding="utf-8")
(platforms / "p.yml").write_text(
"platform: P\nsystems:\n arcade:\n files:\n"
" - {name: a.zip, sha1: aaaa}\n - {name: b.zip, sha1: bbbb}\n",
encoding="utf-8",
)
db = {"files": {"aaaa": {"path": "bios/Arcade/MAME/a.zip"},
"bbbb": {"path": "bios/Apple/Apple II/b.zip"}},
"indexes": {"by_md5": {}}}
known = validate_pr.load_platform_hashes(str(platforms), db)
self.assertEqual(known["folders"], {})
class Placement(_Fixture):
def test_manufacturer_and_console_directories_are_accepted(self):
rel = self._bios_file("bios/Sega/Saturn/sega_101.bin")
self.assertIn(
"Correct placement: bios/Sega/Saturn/",
validate_pr.validate_file(rel, None, EMPTY_HASHES).to_markdown(),
)
def test_a_file_at_the_bios_root_is_flagged(self):
rel = self._bios_file("bios/loose.bin")
self.assertIn(
"bios/Manufacturer/Console/",
validate_pr.validate_file(rel, None, EMPTY_HASHES).to_markdown(),
)
def test_a_file_outside_bios_is_flagged(self):
rel = self._bios_file("elsewhere/loose.bin")
self.assertIn(
"not under bios/",
validate_pr.validate_file(rel, None, EMPTY_HASHES).to_markdown(),
)
class Reporting(_Fixture):
def test_markdown_carries_every_digest_and_the_verdict(self):
rel = self._bios_file("bios/Sega/Saturn/sega.bin")
result = validate_pr.validate_file(rel, None, EMPTY_HASHES)
markdown = result.to_markdown()
for value in (result.sha1, result.md5, result.crc32):
self.assertIn(value, markdown)
self.assertIn("✅", markdown)
def test_a_failure_is_marked_in_the_heading(self):
rel = self._bios_file("bios/Sega/Saturn/bad.exe")
self.assertIn("❌", validate_pr.validate_file(rel, None, EMPTY_HASHES).to_markdown())
class PlatformHashLoading(_Fixture):
def test_missing_directory_yields_empty_sets(self):
known = validate_pr.load_platform_hashes(str(self.tmp / "absent"))
self.assertEqual(known, {"sha1": set(), "md5": set(), "names": set(), "folders": {}})
def test_declared_hashes_and_names_are_collected(self):
platforms = self.tmp / "platforms"
platforms.mkdir()
(platforms / "_registry.yml").write_text(
"platforms:\n demo:\n status: active\n"
)
(platforms / "demo.yml").write_text(
"platform: Demo\n"
"systems:\n"
" test-sys:\n"
" files:\n"
" - name: boot.bin\n"
f" sha1: {'a' * 40}\n"
f" md5: {'b' * 32}\n"
)
known = validate_pr.load_platform_hashes(str(platforms))
self.assertIn("a" * 40, known["sha1"])
self.assertIn("b" * 32, known["md5"])
self.assertIn("boot.bin", known["names"])
class FlagsAreAppliedOrRefused(unittest.TestCase):
"""Files named beside --changed were dropped for the git diff and the run
said "No changed BIOS files detected" about files it never read; --json
beside --markdown printed JSON and dropped the markdown."""
def _run(self, *argv: str):
import subprocess # noqa: PLC0415
return subprocess.run(
[sys.executable, "scripts/validate_pr.py", *argv], cwd=REPO_ROOT,
capture_output=True, text=True, timeout=300, check=False,
)
def test_files_beside_changed_are_refused(self):
result = self._run("--changed", "bios/Sony/PlayStation/scph5501.bin", "--json")
self.assertEqual(result.returncode, 2)
self.assertIn("--changed", result.stderr)
def test_two_output_formats_are_refused(self):
result = self._run("--json", "--markdown", "bios/Sony/PlayStation/scph5501.bin")
self.assertEqual(result.returncode, 2)
self.assertIn("not allowed with", result.stderr)
if __name__ == "__main__":
unittest.main()