Files
libretro/tests/test_validate_pr.py
Abdessamad Derraz 0b5c534af4 fix: settle a contributed path before reading it
validate_pr.py inspects paths chosen by whoever opened the pull
request, and it hashed the file before deciding whether it was a
symlink. A link to /dev/zero was read until the job timed out, and a
link out of the checkout was hashed and reported as though its target
had been contributed. The shape is now settled first, and a test that
used to hang the run covers it.

The gate had no tests at all, and neither did the 3DS crypto reached
by dynamic import from validation.py: RSA PKCS#1 v1.5, AES-128-CBC and
ECDSA over GF(2^233), all written by hand. Coverage goes from 0 to 95%
on the curve, 0 to 55% on the gate, 9 to 35% on the rest. The curve
tests check against the published SEC 2 parameters rather than against
the module: the generator satisfies the curve equation and the group
order takes it to infinity.
2026-08-11 01:39:29 +02:00

228 lines
8.8 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 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)
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()})
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"])
if __name__ == "__main__":
unittest.main()