Files
libretro/tests/test_pin_build.py
T
Abdessamad Derraz 3011ee498e feat: record the build file behind a held pin
pin_frozen was prose, and no tool read the build file it cites, so a platform building another revision left the profile holding one nobody ships. pin_build records the platform, its repository, the revision read, the file, the variable and its value. check_freshness reads the file again where the platform's YAML now stands and reports STALE when the value moved. 36 of the 46 held profiles carry it, each value read from the build file at the platform's scraped revision. The other ten name no build file and are counted on one row: six RetroBat binary packages and four holds not tied to a platform build.
2026-11-05 19:54:48 +01:00

139 lines
5.9 KiB
Python

"""Held pins name the build file that holds them.
pin_frozen was free prose and no tool read the build file it cites: a
platform moving mame0278 to another commit would have left the profile
holding a revision no platform builds, in silence. `pin_build` records the
file, and check_freshness reads it again where the platform now stands.
"""
from __future__ import annotations
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import check_freshness as cf # noqa: E402
RECALBOX = "https://gitlab.com/recalbox/recalbox"
class HeldPinsReadTheirBuildFileAgain(unittest.TestCase):
REF = {"platform": "recalbox", "repo": RECALBOX,
"revision": "10.1.1", "path": "package/libretro-mame0278/libretro-mame0278.mk",
"variable": "LIBRETRO_MAME0278_VERSION", "value": "a" * 40}
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.addCleanup(self._tmp.cleanup)
self.platforms = Path(self._tmp.name)
def _platform(self, revision: str) -> None:
(self.platforms / "recalbox.yml").write_text(
f'source: "{RECALBOX}/-/raw/{revision}/board/es_bios.xml"\n'
)
def _check(self, lines=None, offline=False):
profiles = {"mame0278": {"pin_frozen": "Recalbox builds it.", "pin_build": [self.REF]},
"raine": {"pin_frozen": "RetroBat installs this release."}}
with mock.patch.object(cf.upstream, "fetch_file", return_value=lines) as fetched:
findings = cf.check_pin_builds(profiles, self.platforms, "cache", offline)
return {f.subject: f for f in findings}, fetched
def test_the_variable_forms_the_platforms_write(self):
cases = [
(["LIBRETRO_MAME0278_VERSION = fba5074"], "LIBRETRO_MAME0278_VERSION", "fba5074"),
(['PKG_VERSION="37cddf2" #v0.6.3'], "PKG_VERSION", "37cddf2"),
(['rp_module_repo="file $__archive_url/advancemame-1.4.tar.gz"'], "rp_module_repo",
"file $__archive_url/advancemame-1.4.tar.gz"),
(["function f() {", ' local version="20250829"', ' version="20250302"'],
"version", "20250829"),
(["OTHER_VERSION = 1"], "PKG_VERSION", None),
]
for lines, variable, expected in cases:
with self.subTest(variable=variable):
self.assertEqual(cf.build_variable(lines, variable), expected)
def test_the_platform_read_at_the_same_revision_needs_no_network(self):
self._platform("10.1.1")
found, fetched = self._check()
fetched.assert_not_called()
self.assertEqual(found["mame0278 (recalbox)"].status, cf.OK)
def test_a_new_revision_building_another_commit_is_stale(self):
self._platform("10.2")
found, fetched = self._check([f"LIBRETRO_MAME0278_VERSION = {'b' * 40}"])
self.assertEqual(fetched.call_args.args[1:3],
("10.2", "package/libretro-mame0278/libretro-mame0278.mk"))
row = found["mame0278 (recalbox)"]
self.assertEqual(row.status, cf.STALE)
self.assertIn("now builds " + "b" * 40, row.detail)
def test_a_new_revision_building_the_same_commit_is_ok(self):
self._platform("10.2")
found, _ = self._check([f"LIBRETRO_MAME0278_VERSION = {'a' * 40}"])
self.assertEqual(found["mame0278 (recalbox)"].status, cf.OK)
def test_a_build_file_gone_is_stale(self):
self._platform("10.2")
found, _ = self._check(None)
self.assertEqual(found["mame0278 (recalbox)"].status, cf.STALE)
self.assertIn("is gone", found["mame0278 (recalbox)"].detail)
def test_offline_and_uncached_is_skipped(self):
self._platform("10.2")
profiles = {"mame0278": {"pin_frozen": "x", "pin_build": [self.REF]}}
cold = cf.upstream.NotCachedError("not cached")
with mock.patch.object(cf.upstream, "fetch_file", side_effect=cold):
(row,) = cf.check_pin_builds(profiles, self.platforms, "cache", True)
self.assertEqual(row.status, cf.SKIPPED)
def test_a_hold_with_no_build_file_is_counted(self):
self._platform("10.1.1")
found, _ = self._check()
row = found["pin_frozen without pin_build"]
self.assertEqual(row.status, cf.SKIPPED)
self.assertEqual(row.items, ["raine"])
class EveryBuildReferenceNamesThePin(unittest.TestCase):
"""Reads the repository: a pin_build value is what the platform builds,
so it is the profile's source_commit or names its core_version, and the
platform's own YAML was scraped from that repository."""
def test_the_values_agree(self):
from common import load_emulator_profiles
disagree = []
for name, profile in load_emulator_profiles(str(REPO_ROOT / "emulators")).items():
references = profile.get("pin_build") or []
if not references:
continue
pins = profile.get("source_commit")
pins = set(pins.values()) if isinstance(pins, dict) else {pins}
versions = profile.get("core_version")
versions = (
{str(v) for v in versions.values()} if isinstance(versions, dict)
else {str(versions)} if versions else set()
)
def names_it(value: str) -> bool:
token = value.split()[-1].rsplit("/", 1)[-1].removesuffix(".tar.gz")
return value in pins or any(token in v or v in value for v in versions)
if not any(names_it(str(r["value"])) for r in references):
disagree.append(name)
for reference in references:
if not cf.pin_build_revision(reference, REPO_ROOT / "platforms"):
disagree.append(f"{name}: {reference['platform']} is not read from "
f"{reference['repo']}")
self.assertEqual(disagree, [])
if __name__ == "__main__":
unittest.main()