"""What the round 4 packages 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 glob import json import re import sys 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_has_no_logo_and_no_stale_copy(self): self.assertNotIn("logo", self.registry["minui"]) self.assertEqual(list((REPO_ROOT / "docs_assets" / "logos").glob("minui.*")), []) def test_every_logo_copy_has_a_registry_logo(self): sources = json.loads((REPO_ROOT / "docs_assets" / "logos" / "sources.json").read_text()) for entry in sources["images"]: with self.subTest(platform=entry["platform"]): self.assertEqual(self.registry[entry["platform"]].get("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_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) 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 your 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 or ROCKNIX " "card by itself.", 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"): with self.subTest(card=name): self.assertIn(name, known) source = (REPO_ROOT / "install.py").read_text(encoding="utf-8") self.assertIn('f"{card[2]} SD card ({_mount_name(mount)})"', source) 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']*rb-breadcrumbs[^>]*>(.*?)', path.read_text(encoding="utf-8"), re.S) if not nav: continue labels = [re.sub(r"<[^>]+>", "", item).strip() for item in re.findall(r"]*>(.*?)", 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']*rb-breadcrumbs[^>]*>(.*?)', text, re.S)[1] self.assertIn('href="/retrobios/systems/arcade/"', nav) self.assertNotIn('href="/retrobios/systems/#arcade"', nav) if __name__ == "__main__": unittest.main()