#!/usr/bin/env python3 """End-to-end pack integrity test. Thin unittest wrapper around generate_pack.py --verify-packs, which checks every declared platform file and every in-repo core extra against the pack, at the correct path and with the correct hash per the platform's native mode. The assertion presupposes a pack built from the current profiles. The check runs first and keeps its teeth: a failure is only downgraded to a skip when a profile landed after the pack was built, which makes the pack incomplete by construction and says nothing about pack generation. A failure on an up to date pack stays a failure. """ from __future__ import annotations import glob import os import subprocess import sys import unittest REPO_ROOT = os.path.join(os.path.dirname(__file__), "..") DIST_DIR = os.path.join(REPO_ROOT, "dist") PLATFORMS_DIR = os.path.join(REPO_ROOT, "platforms") EMULATORS_DIR = os.path.join(REPO_ROOT, "emulators") def _pack_path(platform_name: str) -> str | None: """Path of the platform's full pack, by the exact name the builder writes.""" if not os.path.isdir(DIST_DIR): return None sys.path.insert(0, os.path.join(REPO_ROOT, "scripts")) from generate_pack import _expected_pack_names names = _expected_pack_names([platform_name], PLATFORMS_DIR, [], None) for entry in sorted(names[platform_name]): path = os.path.join(DIST_DIR, entry) if os.path.isfile(path): return path return None def _registered_platforms() -> list[str]: sys.path.insert(0, os.path.join(REPO_ROOT, "scripts")) from common import list_registered_platforms return list_registered_platforms(PLATFORMS_DIR, include_archived=True) def _profiles_newer_than(path: str, platform_name: str) -> list[str]: """Profiles of this platform's emulators modified after the pack was built. A profile the platform never resolves cannot have changed its pack: a comment edited in any of 500 profiles used to disarm the integrity check of every pack on disk. """ sys.path.insert(0, os.path.join(REPO_ROOT, "scripts")) from common import load_emulator_profiles, load_platform_config, resolve_platform_cores built = os.path.getmtime(path) config = load_platform_config(platform_name, PLATFORMS_DIR) profiles = load_emulator_profiles(EMULATORS_DIR) relevant = resolve_platform_cores(config, profiles) return [ os.path.basename(p) for p in glob.glob(os.path.join(EMULATORS_DIR, "*.yml")) if os.path.splitext(os.path.basename(p))[0] in relevant and os.path.getmtime(p) > built ] class PackIntegrityTest(unittest.TestCase): """Verify each platform pack via generate_pack.py --verify-packs.""" def _verify_platform(self, platform_name: str) -> None: pack = _pack_path(platform_name) if pack is None: self.skipTest(f"no pack found for {platform_name}") result = subprocess.run( [ sys.executable, "scripts/generate_pack.py", "--platform", platform_name, "--verify-packs", "--output-dir", "dist/", ], capture_output=True, text=True, cwd=REPO_ROOT, ) if result.returncode == 0: return # A build holding the exclusive lock means the packs on disk are # half-written. Failing then reports a corrupt archive when the only # fact established is that somebody else is building, and which test # goes red depends on how far along that build is. if "is in use by another run" in (result.stdout + result.stderr): self.skipTest(f"dist/ is being written; {platform_name} not verifiable") newer = _profiles_newer_than(pack, platform_name) if newer: self.skipTest( f"{platform_name} pack predates {len(newer)} profile(s) " f"({', '.join(sorted(newer)[:3])}): rebuild before verifying\n" f"{result.stdout}" ) self.fail( f"{platform_name} pack integrity failed:\n" f"{result.stdout}\n{result.stderr}" ) def test_every_registered_platform(self): """Every platform the registry knows, not a list written by hand. Eight names were written out and ROCKNIX and MiSTer FPGA, both released, were never checked. """ for platform_name in _registered_platforms(): with self.subTest(platform=platform_name): self._verify_platform(platform_name) if __name__ == "__main__": unittest.main()