Files
libretro/tests/test_site_r5_systems.py
T

298 lines
14 KiB
Python

"""System pages and boards (round 5, package 6).
NAOMI opened on hod2bios.zip, a set 13 of the 19 emulators listing it read
for one game. Lines read other names for one file as files to get: "Start
with: fdsbios.zip, disksys.rom". Twelve neogeo.zip were on five pages, the
SNK ones closed under Variants. The Atom, two MZ computers, a sound chip and
a toy had board pages, and the NAOMI and Atomiswave sets under the
Dreamcast folder stayed off their boards' pages. ScummVM's files were on
three pages, one section named ".variants". Board pages said their name
three times before the first file.
"""
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
import siterender # noqa: E402
CONTENT = REPO_ROOT / "website" / "src" / "content" / "docs"
SYSTEMS = CONTENT / "systems"
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/Sega/NAOMI/{name}"}
def _consumers(reads: dict[str, list[tuple[str, dict]]]) -> types.SimpleNamespace:
"""Consumers where each sha1 is read by (emulator, entry) pairs, an
emulator's entries kept in one list as a profile keeps them."""
files_of: dict[str, list] = {}
entries: dict[str, dict[str, int]] = {}
emulators: dict[str, set[str]] = {}
for sha1, read_by in reads.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={},
platform_destinations={}, _db={"files": {}},
)
def _page(stem: str) -> str:
path = SYSTEMS / f"{stem}.md"
if not path.is_file():
raise unittest.SkipTest(f"systems/{stem}.md not generated")
return path.read_text(encoding="utf-8")
def _start_line(text: str) -> str:
match = re.search(r"^\*\*(Start with|No file needed)\*\*.*$", text, re.M)
return match[0] if match else ""
def _database() -> dict:
path = REPO_ROOT / "database.json"
if not path.is_file():
raise unittest.SkipTest("database.json is not built")
return json.loads(path.read_text(encoding="utf-8"))
class AStartFileIsNoOnesExtra(unittest.TestCase):
"""6.1: a file most of whose readers take beside another one is no
start file, whoever requires it."""
def test_the_board_opens_on_its_bios_alone(self) -> None:
naomi, hod2 = _file("naomi.zip", "1"), _file("hod2bios.zip", "2")
both = [(f"flycast{n}", {"name": "hod2bios.zip", "required": True}) for n in range(2)]
extra = [(f"mame{n}", {"name": "hod2bios.zip", "required": False}) for n in range(3)]
consumers = _consumers({
"1" * 40: [(e, {"name": "naomi.zip", "required": True}) for e, _ in both + extra],
"2" * 40: both + extra,
})
self.assertEqual(gs._start_with([naomi, hod2], consumers),
["**Start with**: `naomi.zip`.", ""])
def test_the_naomi_page_opens_on_naomi_zip(self) -> None:
self.assertEqual(_start_line(_page("arcade-sega-naomi")), "**Start with**: `naomi.zip`.")
def test_a_file_several_require_beside_the_lead_stays(self) -> None:
bios7, bios9 = _file("bios7.bin", "3"), _file("bios9.bin", "4")
consumers = _consumers({
"3" * 40: [(e, {"name": "bios7.bin", "required": True}) for e in ("a", "b")],
"4" * 40: [(e, {"name": "bios9.bin", "required": True}) for e in ("a", "b")],
})
self.assertEqual(gs._start_with([bios7, bios9], consumers)[0],
"**Start with**: `bios7.bin`, `bios9.bin`.")
def test_some_files_each_is_not_every_file_for_one(self) -> None:
"""The SNES said bsnes and four more require 22 files."""
files = [_file(f"chip{n}.rom", str(n)) for n in range(1, 4)]
reads = {f["sha1"]: [(f"free{k}", {"name": f["name"], "required": False,
"hle_fallback": True}) for k in range(5)]
for f in files}
reads["1" * 40].append(("bsnes", {"name": "chip1.rom", "required": True}))
reads["2" * 40].append(("jgenesis", {"name": "chip2.rom", "required": True}))
reads["3" * 40].append(("jgenesis", {"name": "chip3.rom", "required": True}))
line = gs._start_with(files, _consumers(reads))[0]
self.assertIn("require some of these files: ", line)
class OtherNamesForOneSlotReadAsAlternatives(unittest.TestCase):
"""6.2: "Start with: fdsbios.zip, disksys.rom" read as two files."""
def test_the_other_name_is_read_instead(self) -> None:
disk, fds = _file("disksys.rom", "5"), _file("fdsbios.zip", "6")
consumers = _consumers({
"5" * 40: [(e, {"name": "disksys.rom", "required": True})
for e in ("mesen", "nestopia", "fceumm")],
"6" * 40: [("fbneo", {"name": "fdsbios.zip", "required": True})],
})
self.assertEqual(gs._start_with([disk, fds], consumers)[0],
"**Start with**: `disksys.rom`. FinalBurn Neo reads `fdsbios.zip` instead.")
def test_a_card_read_under_another_name_says_both(self) -> None:
disk, card = _file("disksys.rom", "5"), _file("long name (1986).bin", "7")
consumers = _consumers({
"5" * 40: [(e, {"name": "disksys.rom", "required": True}) for e in ("a", "b")],
"7" * 40: [("c", {"name": "short.rom", "required": True})],
})
self.assertIn("reads `long name (1986).bin` (as `short.rom`) instead.",
gs._start_with([disk, card], consumers)[0])
def test_a_regional_line_names_one_file_per_reader(self) -> None:
mega_cd = _page("sega").split("{ #mega-cd }", 1)[1].split("\n## ", 1)[0]
regions = [line for line in mega_cd.splitlines() if line.startswith("- **")]
self.assertTrue(regions)
for line in regions:
lead = line.split(". ", 1)[0]
self.assertEqual(len(re.findall(r"`[^`]+`", lead)), 1, line)
def test_the_fds_line(self) -> None:
fds = _page("nintendo").split("{ #famicom-disk-system }", 1)[1]
self.assertRegex(_start_line(fds), r"^\*\*Start with\*\*: `disksys.rom`\. FinalBurn Neo "
r"reads \[?`fdsbios.zip`.* instead\.$")
class TheNeoGeoSetsSitWithTheNeoGeo(unittest.TestCase):
"""6.3: every neogeo.zip has its card on SNK's pages, side by side and
open, and the ares sentence names the card."""
def test_every_neogeo_zip_is_on_snk(self) -> None:
db = _database()
sha1s = [sha1 for sha1, f in db["files"].items() if f["name"] == "neogeo.zip"]
held = "".join(path.read_text(encoding="utf-8") for path in SYSTEMS.glob("snk*.md"))
if not held:
self.skipTest("SNK pages not generated")
self.assertEqual([s for s in sha1s if f'id="sha1-{s[:12]}"' not in held], [])
others = "".join(path.read_text(encoding="utf-8")
for path in SYSTEMS.glob("arcade*.md"))
self.assertEqual([s for s in sha1s if f'id="sha1-{s[:12]}"' in others], [])
def test_the_sets_are_open_and_together(self) -> None:
section = _page("snk").split("{ #neo-geo }", 1)[1].split("\n## ", 1)[0]
titles = re.findall(r'class="rb-file-title"[^>]*><code>([^<]+)</code>', section)
sets = [n for n, name in enumerate(titles) if name == "neogeo.zip"]
self.assertGreater(len(sets), 1)
self.assertEqual(sets, list(range(len(sets))))
variants = section.split("Variants (", 1)[1] if "Variants (" in section else ""
self.assertNotIn("<code>neogeo.zip</code>", variants)
def test_ares_is_told_the_card_it_reads(self) -> None:
self.assertIn("ares reads `neo-epo.bin` and `sp-45.sp1` instead.", _page("snk"))
class ABoardIsAnArcadeMachine(unittest.TestCase):
"""6.4: computers, a sound chip and a toy had board pages."""
def test_no_board_has_a_parent_outside_arcade(self) -> None:
wrong = sorted(board for board in siterender.arcade_boards()
if board in siterender.SYSTEM_PARENTS)
self.assertEqual(wrong, [])
def test_no_board_page_for_a_machine_filed_elsewhere(self) -> None:
if not SYSTEMS.is_dir():
self.skipTest("pages not generated")
for stem in ("arcade-acorn-atom", "arcade-sharp-mz-2000", "arcade-sharp-mz-2500",
"arcade-yamaha-ym2608", "arcade-miuchiz"):
self.assertFalse((SYSTEMS / f"{stem}.md").exists(), stem)
def test_the_computers_sets_are_on_their_makers_page(self) -> None:
acorn = _page("acorn")
self.assertIn("{ #atom }", acorn)
self.assertIn("<code>atom_gdos.zip</code>", acorn)
cards = re.findall(r'class="rb-file-title"[^>]*><code>([^<]+)</code>', _page("sharp"))
self.assertEqual(cards.count("mz2000.zip"), 2)
class TheDreamcastBoardsAreFiledByBoard(unittest.TestCase):
"""6.5: the NAOMI and Atomiswave sets under the Dreamcast folder."""
def test_the_board_sets_left_the_dreamcast(self) -> None:
sega = _page("sega")
dreamcast = sega.split("{ #dreamcast }", 1)[1].split("\n## ", 1)[0]
for name in ("naomi.zip", "awbios.zip", "naomi2.zip"):
self.assertNotIn(f"<code>{name}</code>", dreamcast)
self.assertIn("(/retrobios/systems/arcade-sega-naomi/)", dreamcast)
self.assertIn("(/retrobios/systems/arcade-sammy-atomiswave/)", dreamcast)
def test_every_naomi_set_is_on_its_board(self) -> None:
db = _database()
page = _page("arcade-sega-naomi")
missing = [sha1 for sha1, f in db["files"].items()
if f["name"] == "naomi.zip" and f'id="sha1-{sha1[:12]}"' not in page]
self.assertEqual(missing, [])
def test_the_dreamcast_arcade_id_leads_to_naomi(self) -> None:
siterender.reset_console_pages()
for board in siterender.arcade_boards():
siterender.record_board_section(board)
try:
self.assertEqual(siterender.system_section("sega-dreamcast-arcade"),
("arcade", "Sega NAOMI"))
finally:
siterender.reset_console_pages()
class EnginesGoByEngine(unittest.TestCase):
"""6.6: no section named after a folder of variants or a file."""
def test_no_section_or_row_is_a_folder_or_a_file(self) -> None:
db = _database()
names = set(db["indexes"]["by_name"])
index = _page("index")
rows = re.findall(r"^\| \[([^\]]+)\]\(", index, re.M)
headings = [h.strip() for path in SYSTEMS.glob("*.md")
for h in re.findall(r"^## ([^{\n]+)", path.read_text(encoding="utf-8"), re.M)]
wrong = sorted({n for n in rows + headings if n.startswith(".") or n in names})
self.assertEqual(wrong, [])
def test_scummvm_has_one_page(self) -> None:
self.assertFalse((SYSTEMS / "other-scummvm.md").exists())
self.assertIn("## ScummVM {", _page("scummvm"))
self.assertNotIn("](/retrobios/systems/other/#scummvm)", _page("index"))
def test_a_folder_named_by_a_profile_key_reads_the_engine(self) -> None:
index = _page("index")
self.assertIn("| [EasyRPG Player](/retrobios/systems/rpg-maker/#easyrpg) |", index)
self.assertIn("| [SDLPAL](", index)
def test_moved_pages_redirect(self) -> None:
if not SYSTEMS.is_dir():
self.skipTest("pages not generated")
for old, new in gs.SYSTEM_PAGES_MOVED.items():
self.assertFalse((CONTENT / old).exists(), old)
self.assertTrue((CONTENT / new).exists(), new)
redirects = CONTENT.parents[1] / "generated" / "redirects.json"
if redirects.is_file():
table = json.loads(redirects.read_text(encoding="utf-8"))
self.assertEqual(table.get("/systems/other-scummvm/"), "/systems/scummvm/")
class OneLabelOnceOnABoardPage(unittest.TestCase):
"""6.7, 6.8, 6.9, 6.10: the words a board or part page repeats."""
def test_a_label_naming_files_installs_these_files(self) -> None:
block = "\n".join(gs._console_install_block("Other arcade files", [("mame", "arcade")]))
self.assertIn("<summary>Install these files</summary>", block)
block = "\n".join(gs._console_install_block("Sega NAOMI", [("mame", "arcade")]))
self.assertIn("<summary>Install the files of Sega NAOMI</summary>", block)
def test_a_board_page_has_no_heading_of_its_name(self) -> None:
page = _page("arcade-sega-naomi")
self.assertNotRegex(page, r"^## Sega NAOMI", )
self.assertNotIn("](/retrobios/systems/arcade-sega-naomi/#sega-naomi)",
_page("index"))
def test_no_part_says_where_it_sits(self) -> None:
if not SYSTEMS.is_dir():
self.skipTest("pages not generated")
said = sorted(p.name for p in SYSTEMS.glob("*.md")
if "\nPart of [" in p.read_text(encoding="utf-8"))
self.assertEqual(said, [])
def test_a_part_goes_by_the_emulators_name(self) -> None:
titles = [re.search(r"^title: (.*)$", p.read_text(encoding="utf-8"), re.M)[1].strip("'")
for p in SYSTEMS.glob("other-mame*.md")]
if not titles:
self.skipTest("pages not generated")
self.assertEqual([t for t in titles if not t.startswith(("MAME files", "Other: MAME"))],
[])
if __name__ == "__main__":
unittest.main()