From 0b46880604cb8575067618dd6b8e999823ffc492 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 6 Oct 2026 12:53:10 +0200 Subject: [PATCH] fix: show unsourceable entries apart on the site --- scripts/generate_site.py | 20 ++++++++--- tests/test_site_availability.py | 62 +++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 tests/test_site_availability.py diff --git a/scripts/generate_site.py b/scripts/generate_site.py index b177260f..1bcd12cf 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -1873,11 +1873,15 @@ def _file_badges(f: dict, in_repo: bool) -> list[str]: badges.append( 'optional' ) - if not in_repo: + if not in_repo and f.get("unsourceable"): + badges.append( + 'unsourceable' + ) + elif not in_repo: badges.append( 'missing' ) - elif in_repo: + else: badges.append( 'in repo' ) @@ -2236,7 +2240,9 @@ def _availability_check(db: dict, data_names): def _file_available(f: dict) -> bool: """Check if a file is available using the same resolution as cross_reference.""" - if not f.get("name"): + # cross_reference sets these aside before resolving: the file found + # under the name belongs to another game or release. + if not f.get("name") or f.get("unsourceable"): return False if f.get("storage", "") in ("release", "large_file"): return True @@ -2367,14 +2373,18 @@ def generate_emulator_page( bios_zips = [f for f in files if f.get("category") == "bios_zip"] in_repo_count = sum(1 for f in files if _file_available(f)) - missing_count = len(files) - in_repo_count + unsourceable_count = sum(1 for f in files if f.get("unsourceable")) + missing_count = len(files) - in_repo_count - unsourceable_count req_count = sum(1 for f in files if f.get("required")) opt_count = len(files) - req_count hle_count = sum(1 for f in files if f.get("hle_fallback")) parts = [f"**{len(files)} files**"] parts.append(f"{req_count} required, {opt_count} optional") - parts.append(f"{in_repo_count} in repo, {missing_count} missing") + held = f"{in_repo_count} in repo, {missing_count} missing" + if unsourceable_count: + held += f", {unsourceable_count} unsourceable" + parts.append(held) if hle_count: parts.append(f"{hle_count} with HLE fallback") lines.append(" | ".join(parts)) diff --git a/tests/test_site_availability.py b/tests/test_site_availability.py new file mode 100644 index 00000000..19012687 --- /dev/null +++ b/tests/test_site_availability.py @@ -0,0 +1,62 @@ +"""An emulator page never shows an unsourceable entry as held. + +The page resolved each entry by name without the `unsourceable:` check +cross_reference runs first: ioquake3's pak0.pk3 wore a green "in repo" +badge, served by the pak0.pk3 of another game, and 52 such entries read as +held across the site while the gaps page listed them as known absences. +""" + +from __future__ import annotations + +import sys +import unittest +from pathlib import Path + +import yaml + +REPO_ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(REPO_ROOT / "scripts")) + +from generate_site import _availability_check, _file_badges + +SHA1 = "a" * 40 +DB = { + "files": {SHA1: {"name": "pak0.pk3", "sha1": SHA1, "path": "bios/x/pak0.pk3"}}, + "indexes": {"by_name": {"pak0.pk3": [SHA1]}}, +} + + +class UnsourceableIsNotHeld(unittest.TestCase): + def test_a_homonym_does_not_stand_in(self): + available = _availability_check(DB, set()) + self.assertTrue(available({"name": "pak0.pk3"})) + self.assertFalse(available({"name": "pak0.pk3", "unsourceable": "retail data"})) + + def test_the_badge_says_why(self): + entry = {"name": "pak0.pk3", "unsourceable": "retail data"} + badges = " ".join(_file_badges(entry, False)) + self.assertIn("unsourceable", badges) + self.assertNotIn(">missing<", badges) + self.assertNotIn("in repo", badges) + + def test_no_profile_entry_is_shown_held(self): + from common import load_database + + db_path = REPO_ROOT / "database.json" + if not db_path.exists(): + self.skipTest("database.json is not built") + available = _availability_check(load_database(str(db_path)), set()) + shown_held = [] + for path in sorted((REPO_ROOT / "emulators").glob("*.yml")): + document = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + shown_held.extend( + f"{path.stem}: {entry['name']}" + for entry in document.get("files") or [] + if isinstance(entry, dict) and entry.get("unsourceable") + and available(entry) + ) + self.assertEqual(shown_held, []) + + +if __name__ == "__main__": + unittest.main()