"""System pages and file cards (round 4, package 6). The Game Boy, NES and SNES sections said "Start with" a file one core of sixteen requires, under a home page saying they need none. The romset line said no MAME release matched sets MAME loads. The Neo Geo opened on files ares alone reads, and RetroArch had no install line. The Arcade page was sorted by storage folder: NAOMI, PGM and ZN-1 had no section, and their names were plain on every emulator page. Cards named emulators by id, part pages had no parent and no name, and four paragraphs came before the first console. """ from __future__ import annotations import json import re import sys import types import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT / "scripts")) import generate_site as gs # noqa: E402 from sitestarlight import toc_slug # noqa: E402 DIST = REPO_ROOT / "website" / "dist" CONTENT = REPO_ROOT / "website" / "src" / "content" / "docs" def _file(name: str, char: str, path: str = "") -> dict: return {"name": name, "sha1": char * 40, "md5": char * 32, "size": 4096, "path": path or f"bios/Nintendo/Game Boy/{name}"} def _consumers(readers: dict[str, list[tuple[str, dict]]], systems: dict[str, dict[str, set[str]]] | None = None) -> types.SimpleNamespace: """Consumers where each sha1 is read by the given (emulator, entry).""" files_of: dict[str, list] = {} entries: dict[str, dict[str, int]] = {} emulators: dict[str, set[str]] = {} for sha1, read_by in readers.items(): for emulator, entry in read_by: files_of.setdefault(emulator, []).append(entry) entries.setdefault(sha1, {})[emulator] = len(files_of[emulator]) - 1 emulators.setdefault(sha1, set()).add(emulator) return types.SimpleNamespace( platforms={}, emulators=emulators, emulator_entries=entries, emulator_files=files_of, installed={}, platform_systems=systems or {}, platform_destinations={}, _db={"files": {}}, ) def _page(path: Path) -> str: if not path.is_file(): raise unittest.SkipTest(f"{path.relative_to(REPO_ROOT)} not built") return path.read_text(encoding="utf-8") def _opening(html: str, anchor: str) -> str: """The text of the first paragraph under a heading.""" start = html.index("
", html.index(f'id="{anchor}"')) return re.sub(r"<[^>]+>", "", html[start:html.index("
", start)]).strip() class AFileFewRequireIsNoStartFile(unittest.TestCase): """6.1: one requiring emulator decided the line for the console.""" def test_one_of_four_says_no_file_needed_and_names_it(self) -> None: boot = _file("dmg_boot.bin", "1") optional = {"required": False, "system": "nintendo-gb"} consumers = _consumers({"1" * 40: [ ("emux_gb", {"name": "dmg_boot.bin", "required": True}), ("gambatte", optional), ("mgba", optional), ("sameboy", optional)]}) line = gs._start_with([boot], consumers)[0] self.assertTrue(line.startswith("**No file needed** with "), line) self.assertRegex(line, r"Only \S.* requires `dmg_boot.bin`\.$") def test_most_requiring_keeps_the_start_line(self) -> None: bios = _file("scph5501.bin", "2") required = {"name": "scph5501.bin", "required": True} consumers = _consumers({"2" * 40: [ ("a", required), ("b", required), ("c", {"required": False, "hle_fallback": True})]}) self.assertEqual(gs._start_with([bios], consumers)[0], "**Start with**: `scph5501.bin`.") def test_an_entry_read_for_a_system_with_no_section_is_dropped(self) -> None: sample = _file("ym2608_adpcm_rom.bin", "3", "bios/Nintendo/NES/ym2608_adpcm_rom.bin") consumers = _consumers({"3" * 40: [ ("fbneo", {"name": "ym2608_adpcm_rom.bin", "required": True, "system": "no-such-system-anywhere"})]}) self.assertEqual(gs._start_with([sample], consumers, section=("Nintendo", "NES")), []) def test_the_home_usually_not_consoles_open_on_no_file_needed(self) -> None: home = _page(DIST / "index.html") match = re.search(r"Usually not for ([^<.]+)", home) self.assertIsNotNone(match, "the home page no longer says Usually not") names = re.split(r", | and ", match[1]) sections = {"NES": ("nintendo", "nes"), "Super Nintendo": ("nintendo", "snes"), "Mega Drive": ("sega", "mega-drive"), "Game Boy": ("nintendo", "game-boy"), "Game Boy Advance": ("nintendo", "game-boy-advance")} for name in names: self.assertIn(name, sections, f"map {name!r} to its section") page, anchor = sections[name] html = _page(DIST / "systems" / page / "index.html") self.assertTrue(_opening(html, anchor).startswith("No file needed"), name) class TheRomsetLineMatchesByCrc(unittest.TestCase): """6.2: a set with an extra BIOS revision read "No match".""" def test_the_main_neogeo_set_names_every_mame_release(self) -> None: db = json.loads((REPO_ROOT / "database.json").read_text(encoding="utf-8")) main = next((f for sha1, f in db["files"].items() if sha1.startswith("447e2375")), None) if main is None or not (REPO_ROOT / main["path"]).is_file(): self.skipTest("the main neogeo.zip is not in this checkout") line = gs._romset_line(main) self.assertIn("Holds every ROM of MAME 0.190 to 0.289", line) self.assertNotIn("No match", line) class TheNeoGeoOpensOnItsSet(unittest.TestCase): """6.3: the set 26 emulators read leads, RetroArch gets its command.""" def test_the_lead_names_the_exceptions(self) -> None: neogeo = _file("neogeo.zip", "4", "bios/SNK/Neo Geo/neogeo.zip") epo = _file("neo-epo.sp1", "5", "bios/SNK/Neo Geo/neo-epo.sp1") mvs = {"name": "neogeo.zip", "required": True, "system": "snk-neogeo-mvs"} consumers = _consumers({ "4" * 40: [(f"mame{n}", mvs) for n in range(4)], "5" * 40: [("ares", {"name": "neo-epo.sp1", "required": True, "system": "snk-neogeo"})], }) lines = gs._start_with([neogeo, epo], consumers, section=("SNK", "Neo Geo")) self.assertTrue(lines[0].startswith("**Start with** `neogeo.zip`. Keep it zipped. ")) self.assertIn("ares reads `neo-epo.sp1` instead.", lines) cards = gs._console_cards("SNK", [epo, neogeo], consumers, set(), start=gs._start_order([neogeo, epo], consumers, ("SNK", "Neo Geo"))) self.assertIn("neogeo.zip", cards[0][1][2]) def test_a_setup_filing_the_set_elsewhere_gets_its_command(self) -> None: consumers = _consumers({}, systems={"a" * 40: {"retroarch": {"arcade"}}}) pairs = gs._install_pairs([("batocera", "snk-neogeo")], "a" * 40, consumers) self.assertIn(("retroarch", "arcade"), pairs) block = "\n".join(gs._console_install_block("Neo Geo", pairs)) self.assertIn("# RetroArch\n", block) def test_the_built_section(self) -> None: html = _page(DIST / "systems" / "snk" / "index.html") section = html[html.index('id="neo-geo"'):] self.assertTrue(_opening(html, "neo-geo").startswith("Start with neogeo.zip")) self.assertIn("--platform retroarch --system arcade", re.sub(r"<[^>]+>", "", section)) BOARDS = ("Sega NAOMI", "Sammy Atomiswave", "Capcom ZN-1", "IGS PolyGame Master", "Nintendo PlayChoice-10", "SNK Hyper Neo Geo 64") class EachArcadeBoardHasASection(unittest.TestCase): """6.4: the Arcade page was cut by storage folder.""" def test_each_board_heads_its_page(self) -> None: # The page's title names the board, no h2 says it again (round 5, 6.8). for name in BOARDS: slug = toc_slug(name) html = _page(DIST / "systems" / f"arcade-{slug}" / "index.html") self.assertRegex(html, rf"scph5501.bin: [Batocera](b.md), [KNULLI](k.md)",
"bios/scph5501.bin: [RetroDECK](r.md)",
"[EmuDeck](e.md)",
])
if __name__ == "__main__":
unittest.main()