Files
libretro/tests/test_site_integration.py
T
Abdessamad Derraz 602945faf0 feat: logos, build stamps and exact wording
- The installer says "Every file on the list is in place." where it
  said "Everything is in place.": a check covers the files the
  collection holds, and the required ones it lacks follow.
- The FAQ, first install, legal and home pages state only what the
  code shows (PlayStation 1 and Saturn run without a BIOS on some
  emulators, save files are never touched, records are added).
- MinUI and MyMinUI show their own boot logos, and the Android,
  Linux handheld and iPhone tiles show the Android robot, Tux and a
  plain device icon, each credited in sources.json.
- Each Markdown copy, llms.txt and llms-full.txt name the commit
  they were built from, version.json the current one, and the
  answering rules tell assistants to fetch a stale copy again.
- The deploy lists the pages whose words changed (digests.json) and
  posts them to IndexNow once the site is live.
2026-11-09 10:47:37 +01:00

287 lines
14 KiB
Python

"""What separate fixes left between them once merged.
A data directory page printed its readers as a Python list, every board
page's breadcrumb read "Arcade / Arcade", the fba2012 section was headed
with its folder name, BizHawk's tile showed a GitHub avatar and MinUI's a
logo that is not its own, and the nodes the search gate starts wrote
Node's compile cache into /tmp.
"""
from __future__ import annotations
import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
import glob
import json
import re
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
import yaml
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import check_freshness # noqa: E402
import generate_site as gs # noqa: E402
import sitediagrams # noqa: E402
import siterender # noqa: E402
DIST = REPO_ROOT / "website" / "dist"
def _profile(name: str) -> dict:
return yaml.safe_load((REPO_ROOT / "emulators" / f"{name}.yml").read_text(encoding="utf-8"))
class DataTreeReaders(unittest.TestCase):
def test_read_by_names_the_emulators(self):
ref = "y-sys"
gs._DATA_MEMBERS[ref] = {"a.bin": {"size": 1, "sha1": "0" * 40}}
gs._DATA_READERS[ref] = [("dolphin", "Dolphin"), ("ishiiruka", "Ishiiruka")]
try:
page = gs._data_tree_page(ref, {}, [("y-sys", ["a.bin"])], 0)
finally:
del gs._DATA_MEMBERS[ref], gs._DATA_READERS[ref]
self.assertIn("Read by [Dolphin](dolphin.md#data-directories) and "
"[Ishiiruka](ishiiruka.md#data-directories).", page)
self.assertNotIn("('dolphin'", page)
class EngineIntros(unittest.TestCase):
def test_every_intro_names_what_it_emulates(self):
for path in sorted(glob.glob(str(REPO_ROOT / "emulators" / "*.yml"))):
profile = yaml.safe_load(Path(path).read_text(encoding="utf-8"))
if not isinstance(profile, dict) or profile.get("type") in ("alias", "test"):
continue
name = Path(path).stem
with self.subTest(profile=name):
intro = gs._emulator_intro(str(profile.get("emulator") or name), profile)
self.assertNotRegex(intro, r"emulates\s*(?:\.|game\b|and\b|,)")
self.assertNotIn(" ", intro)
class SectionNames(unittest.TestCase):
def test_fba2012_is_headed_by_its_program(self):
self.assertEqual(gs._section_label("Arcade", "fba2012"), "FB Alpha 2012")
self.assertEqual(gs._section_label("Arcade", "Arcade"), gs.OTHER_ARCADE_LABEL)
self.assertEqual(gs._section_label("Sony", "PlayStation"), "PlayStation")
self.assertEqual(siterender.SECTION_LABELS[("Arcade", "fba2012")],
_profile("fbalpha2012")["emulator"])
class PlatformLogos(unittest.TestCase):
def setUp(self):
self.registry = yaml.safe_load(
(REPO_ROOT / "platforms" / "_registry.yml").read_text(encoding="utf-8"))["platforms"]
def test_bizhawk_shows_its_app_icon(self):
self.assertTrue(self.registry["bizhawk"]["logo"].endswith(
"/src/BizHawk.Client.EmuHawk/images/logo.ico"))
def test_minui_shows_its_boot_logo_not_its_author(self):
# The author's GitHub avatar is a face, not the project's mark.
self.assertIn("/Bootlogo.pak/", self.registry["minui"]["logo"])
self.assertTrue(self.registry["minui"]["logo_flip"])
def test_every_logo_copy_has_a_registry_or_tile_logo(self):
from siteguides import GROUP_TILES # noqa: PLC0415
tiles = {re.sub(r"[^a-z0-9]+", "-", tile.name.lower()).strip("-"): tile.logo
for group in GROUP_TILES.values() for tile in group}
sources = json.loads((REPO_ROOT / "docs_assets" / "logos" / "sources.json").read_text())
for entry in sources["images"]:
with self.subTest(platform=entry["platform"]):
logo = (self.registry[entry["platform"]].get("logo")
if entry["platform"] in self.registry else tiles.get(entry["platform"]))
self.assertEqual(logo, entry["source"])
class NodeCompileCache(unittest.TestCase):
def test_the_search_gate_starts_node_without_it(self):
source = (REPO_ROOT / "website" / "tools" / "search_gate.py").read_text(encoding="utf-8")
self.assertIn('NODE_ENV = {**os.environ, "NODE_DISABLE_COMPILE_CACHE": "1"}', source)
starts = [m.start() for m in re.finditer(r'subprocess\.(?:run|Popen)\(\s*\["node"', source)]
self.assertEqual(len(starts), 3)
for start in starts:
call = source[start:source.index("\n\n", start)]
self.assertIn("env=NODE_ENV", call)
def test_lighthouse_and_the_deploy(self):
self.assertIn('"NODE_DISABLE_COMPILE_CACHE": "1"',
(REPO_ROOT / "website" / "tools" / "lighthouse.py").read_text(encoding="utf-8"))
workflow = (REPO_ROOT / ".github" / "workflows" / "deploy-site.yml").read_text(encoding="utf-8")
build = workflow[workflow.index(" build:"):workflow.index(" steps:")]
self.assertIn('NODE_DISABLE_COMPILE_CACHE: "1"', build)
class SourcePathsFreshness(unittest.TestCase):
def test_the_committed_record_is_current(self):
from common import load_emulator_profiles # noqa: PLC0415
profiles = load_emulator_profiles(str(REPO_ROOT / "emulators"), skip_aliases=False)
finding = check_freshness.check_source_paths(profiles)
self.assertEqual(finding.status, check_freshness.OK, finding.detail)
def test_a_recorded_build_file_tree_is_pinned(self):
"""The record holds the platform trees pin_build reads at a commit,
and the check called them orphaned."""
import sitesources # noqa: PLC0415
pin = "a" * 40
profile = {"pin_build": [{"repo": "https://github.com/o/r", "revision": pin,
"path": "p.mk"}]}
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "source-paths.json"
path.write_text(json.dumps({f"github.com/o/r@{pin}": ["p.mk"]}), encoding="utf-8")
with mock.patch.object(sitesources, "SOURCE_PATHS", path):
finding = check_freshness.check_source_paths({"p": profile})
self.assertEqual(finding.status, check_freshness.OK, finding.detail)
def test_a_moved_pin_is_stale(self):
profile = _profile("geolith")
moved = dict(profile, source_commit="f" * 40, upstream_commit="e" * 40)
finding = check_freshness.check_source_paths({"geolith": moved})
self.assertEqual(finding.status, check_freshness.STALE)
self.assertIn("sitesources.py --fetch", finding.detail)
def test_a_pinned_tree_never_read_is_stale(self):
"""fbneo moved to two new pins while fbneo_cps12 kept the old ones:
no recorded tree was orphaned, so the check said OK and 174 citations
stayed unlinked."""
import sitesources # noqa: PLC0415
profile = _profile("geolith")
trees = [sitesources.tree_key(r, p) for r, p in sitesources.profile_trees(profile)]
record = json.loads(sitesources.SOURCE_PATHS.read_text(encoding="utf-8"))
for key in trees:
record.pop(key, None)
# Kept pinned by another profile, as fbneo_cps12 kept the old FBNeo trees.
record.update({key: [] for key in trees[1:]})
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "source-paths.json"
path.write_text(json.dumps(record), encoding="utf-8")
with mock.patch.object(sitesources, "SOURCE_PATHS", path):
finding = check_freshness.check_source_paths({"geolith": profile})
self.assertEqual(finding.status, check_freshness.STALE)
self.assertIn("1 pinned trees not recorded", finding.detail)
def test_a_tree_read_with_no_cited_path_is_recorded(self):
import sitesources # noqa: PLC0415
profile = {"source": "https://github.com/o/r", "source_commit": "e" * 40,
"files": [{"source_ref": "src/a.c:5"}]}
with mock.patch.object(sitesources, "_cached_tree", return_value=frozenset({"x.c"})):
held, unread = sitesources.build_source_paths({"p": profile}, [Path("tmp")])
self.assertEqual(held, {"github.com/o/r@" + "e" * 40: []})
self.assertEqual(unread, [])
class OptionsGlanceFromTheParser(unittest.TestCase):
"""--declared-only and --offline reached install.py and its Options
table, and not the hand-written glance above it."""
def test_every_parser_option_has_a_group(self):
named = {flag for _label, flags in sitediagrams.GLANCE_GROUPS for flag in flags}
self.assertEqual(set(sitediagrams.installer_flags()) - named, set())
self.assertEqual(named - set(sitediagrams.installer_flags()), set())
def test_an_option_no_group_names_still_lands(self):
with mock.patch.object(sitediagrams, "installer_flags",
return_value=["--platform", "--brand-new"]):
lines = sitediagrams.options_glance()
self.assertEqual(lines, [
"- **Where and for what**: [`--platform`](#option-platform)",
"- **Other**: [`--brand-new`](#option-brand-new)",
])
def test_the_source_holds_no_list(self):
text = (REPO_ROOT / "wiki" / "installer.md").read_text(encoding="utf-8")
section = text[text.index(sitediagrams.GLANCE_HEADING):text.index("\n## One line")]
self.assertNotIn("\n- ", section)
with self.assertRaises(ValueError):
sitediagrams.expand_glance(f"# x\n\n{sitediagrams.GLANCE_HEADING}\n\n- a\n\n## y\n")
class FaqInstallsFromWhatYouHold(unittest.TestCase):
"""--from-pack reads the ZIP itself, and a wiped card had no answer."""
def setUp(self):
self.faq = (REPO_ROOT / "wiki" / "faq.md").read_text(encoding="utf-8")
def _section(self, heading: str) -> str:
return self.faq.split(f"## {heading}\n", 1)[1].split("\n## ", 1)[0]
def test_no_unzip_before_from_pack(self):
section = " ".join(self._section("No internet on the device?").split())
self.assertIn("point `--from-pack` at the ZIP:", section)
self.assertNotIn("unzip", section)
install = (REPO_ROOT / "install.py").read_text(encoding="utf-8")
self.assertIn("Install from a pack already downloaded: its ZIP, one of its parts",
(REPO_ROOT / "wiki" / "installer.md").read_text(encoding="utf-8"))
self.assertIn('"--from-pack"', install)
def test_the_cards_the_interface_finds(self):
"""Each system the SD card answer names is one install.py tells by
its card: a release file (_CARD_RULES) or a card rule."""
import install # noqa: PLC0415
section = " ".join(self._section("Fill an SD card from a computer?").split())
self.assertIn("The interface finds a muOS, Onion, spruceOS, MinUI, KNULLI, ROCKNIX, Lakka "
"or MiSTer card.", section)
known = {rule.name for rule in install._card_rules()}
known |= {install.display_name(name) for name, _marker, _prefix in install._CARD_RULES}
for name in ("muOS", "Onion", "spruceOS", "MinUI", "KNULLI", "ROCKNIX", "MiSTer FPGA"):
with self.subTest(card=name):
self.assertIn(name, known)
# A card told by a rule of its share (MiSTer since 73b82375d) is named
# here and in the README, or its user passes --dest the card's root.
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
for rule in install._SHARE_RULES:
word = rule.name.split()[0]
with self.subTest(share=rule.name):
self.assertIn(word, section)
self.assertRegex(readme, rf"\b{word}\b[^.]*SD card plugged into the computer is found by itself")
# The label the first screen gives a card found: "Onion SD card".
self.assertEqual(install._card_word(("retroarch", Path("/x/BIOS"), "Onion")), "SD card")
self.assertEqual(install._card_word(("batocera", Path("/x/bios"), "Batocera")),
"SHARE drive")
def test_a_wiped_card_links_the_copy(self):
section = self._section("Lost or wiped card?")
self.assertIn("`--from`", section)
self.assertIn("(installer.md#copy-from-an-old-card)", section)
installer = (REPO_ROOT / "wiki" / "installer.md").read_text(encoding="utf-8")
self.assertIn("## Copy from an old card { #copy-from-an-old-card }", installer)
self.assertIn('"--from"', (REPO_ROOT / "install.py").read_text(encoding="utf-8"))
@unittest.skipUnless((DIST / "systems" / "arcade-acclaim-zn-1" / "index.html").is_file(),
"site not built")
class BuiltCrumbs(unittest.TestCase):
def test_no_crumb_repeats_the_one_before(self):
repeated = []
for path in sorted(DIST.rglob("index.html")):
nav = re.search(r'<nav[^>]*rb-breadcrumbs[^>]*>(.*?)</nav>',
path.read_text(encoding="utf-8"), re.S)
if not nav:
continue
labels = [re.sub(r"<[^>]+>", "", item).strip()
for item in re.findall(r"<li[^>]*>(.*?)</li>", nav[1], re.S)]
if any(a == b for a, b in zip(labels, labels[1:])):
repeated.append(str(path.relative_to(DIST)))
self.assertEqual(repeated, [])
def test_a_board_page_ends_on_the_arcade_page(self):
text = (DIST / "systems" / "arcade-acclaim-zn-1" / "index.html").read_text(encoding="utf-8")
nav = re.search(r'<nav[^>]*rb-breadcrumbs[^>]*>(.*?)</nav>', text, re.S)[1]
self.assertIn('href="/retrobios/systems/arcade/"', nav)
self.assertNotIn('href="/retrobios/systems/#arcade"', nav)
if __name__ == "__main__":
unittest.main()