Files
libretro/tests/test_site_r4_p0.py
T
Abdessamad Derraz 6c52d24a30 feat: arcade boards and the 28 unlinked ids
siterender.arcade_board(sha1) names the board a set archive is the
BIOS of, from the profiles' bios_zip entries and their contents CRC32s,
never the folder. board_slug(id) gives the section slug. A board links
once the system pages call record_board_section(), so no page links a
section nobody wrote. Thirteen ids whose files sit in one program's
folder get a reviewed parent, mz800 the Sharp MZ section. The link
test counts set archives.
2026-10-23 14:32:34 +02:00

325 lines
14 KiB
Python

"""Arcade boards and the ids that hold files (round 4, package P0).
The Arcade pages were cut by storage folder, so NAOMI, ZN-1, PGM and the
other boards had no section and stayed plain on 987 emulator rows.
siterender.arcade_board() names the board each set archive is the BIOS of,
read from the profiles, and system_section() leads every id of that board
to ``systems/arcade-<board_slug>/#<board_slug>``, the section the system
pages write. The 28 ids that systems.json gave files and no page either
link or carry their reason below.
"""
from __future__ import annotations
import json
import tempfile
import unittest
import zipfile
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
import sys # noqa: E402
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import siterender # noqa: E402
from sitestarlight import toc_slug # noqa: E402
# The boards the audit found with no section (N1, T-F3), by an id each.
NAMED_BOARDS = {
"sega-naomi": "Sega NAOMI",
"sega-naomi2": "Sega NAOMI 2",
"sammy-atomiswave": "Sammy Atomiswave",
"capcom-zn1": "Capcom ZN-1",
"capcom-zn2": "Capcom ZN-2",
"igs-pgm": "IGS PolyGame Master",
"nintendo-playch10": "Nintendo PlayChoice-10",
"nintendo-nss": "Nintendo Super System",
"konami-gv": "Konami GV",
"deco-cassette": "Data East DECO Cassette System",
"super-kaneko-nova": "Kaneko Super Nova System",
"hyper-neogeo64": "SNK Hyper Neo Geo 64",
"sega-megatech": "Sega Mega-Tech",
"sega-megaplay": "Sega Mega Play",
}
# The ids api/v1/systems.json listed with files and no url (D P2-7).
# Each one links, or stays plain for the reason given.
SYSTEMS_JSON_GAPS = (
"atom", "bbc-master-128", "bbc-master-et", "cave-story", "dos",
"emerson-arcadia-2001", "fpinball", "mz2000", "mz2500", "mz800", "naomi2",
"neogeo64", "pegasus", "pv1000", "scummvm", "sega-dreamcast-arcade",
"sinclair-zx-spectrum-next", "solarus", "spectravideo", "sunplus-spmp8000",
"tomb-raider-1", "tyrian", "vc4000", "vgmplay", "vm-labs-nuon", "vvvvvv",
"xrick", "zc210",
)
STAYS_PLAIN = {
"emerson-arcadia-2001": "ar_bios.zip is the Arcadia Systems board's, filed with the Saturn",
"fpinball": "scripten.exe, filed with Windows",
"pv1000": "only MAME's software list pv1000.xml",
"vc4000": "only MAME's software list vc4000.xml",
"sega-dreamcast-arcade": "NAOMI, NAOMI 2 and Atomiswave sets, three boards",
"spectravideo": "the SVI ROMs are filed with the MSX",
"vgmplay": "sound chip sets, no board",
"vvvvvv": "SDL's controller database",
}
def _reset() -> None:
"""Back to the repository's sections and boards, none written."""
siterender._arcade_source[0] = None
siterender._boards_built[0] = False
siterender._sections_installed[0] = False
siterender.reset_console_pages()
def _write_every_board() -> None:
"""What the system pages do once they give each board a section."""
for board in siterender.arcade_boards():
siterender.record_board_section(board)
@unittest.skipUnless((REPO_ROOT / "database.json").exists(), "database.json is not built")
class Boards(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
_reset()
with open(REPO_ROOT / "database.json", encoding="utf-8") as handle:
cls.db = json.load(handle)
_write_every_board()
@classmethod
def tearDownClass(cls) -> None:
_reset()
def _copies(self, name: str) -> list[str]:
return self.db["indexes"]["by_name"][name]
def test_named_boards_exist(self) -> None:
boards = siterender.arcade_boards()
names = set(boards.values())
for sys_id, name in NAMED_BOARDS.items():
with self.subTest(sys_id=sys_id):
self.assertEqual(siterender.system_name(sys_id), name)
self.assertIn(name, names)
def test_every_copy_names_its_board(self) -> None:
"""Every copy of a board's set, wherever it is filed: naomi.zip
under Sega > Dreamcast is still NAOMI's."""
expect = {
"naomi.zip": "sega-naomi",
"awbios.zip": "sammy-atomiswave",
"cpzn1.zip": "capcom-zn1",
"pgm.zip": "igs-pgm",
"playch10.zip": "nintendo-playch10",
"hng64.zip": "hyper-neogeo64",
"taitofx1.zip": "taito-fx1",
}
for name, board in expect.items():
for sha1 in self._copies(name):
with self.subTest(name=name, path=self.db["files"][sha1]["path"]):
self.assertEqual(siterender.arcade_board(sha1), board)
def test_no_board(self) -> None:
"""neogeo.zip and cd32.zip are the BIOS of machines with a section of
their own. mie.zip and jvs13551.zip are shared device sets."""
for name in ("neogeo.zip", "cd32.zip", "mie.zip", "jvs13551.zip"):
for sha1 in self._copies(name):
with self.subTest(name=name, path=self.db["files"][sha1]["path"]):
self.assertIsNone(siterender.arcade_board(sha1))
def test_slug_contract(self) -> None:
boards = siterender.arcade_boards()
slugs = {siterender.board_slug(board) for board in boards}
self.assertEqual(len(slugs), len(boards), "two boards share a slug")
for board, name in boards.items():
with self.subTest(board=board):
slug = siterender.board_slug(board)
self.assertTrue(slug)
self.assertEqual(slug, toc_slug(name))
self.assertEqual(
siterender.console_href(*siterender.system_section(board)),
f"systems/arcade-{slug}.md#{slug}")
def test_aliases_share_a_board(self) -> None:
"""Every id of one board's name leads to one section."""
pairs = (
("sega-atomiswave", "sammy-atomiswave"),
("zn1-capcom", "capcom-zn1"),
("naomi2", "sega-naomi2"),
("neogeo64", "hyper-neogeo64"),
("zn1-taito", "taito-fx1"),
("zn1-tecmo", "tecmo-tps"),
)
for alias, board in pairs:
with self.subTest(alias=alias):
self.assertEqual(siterender.board_slug(alias), siterender.board_slug(board))
self.assertEqual(
siterender.system_section(alias), siterender.system_section(board))
def test_machines_keep_their_section(self) -> None:
self.assertEqual(siterender.system_section("snk-neogeo-mvs"), ("snk", "Neo Geo"))
self.assertEqual(siterender.system_section("sega-hikaru"), ("sega", "Hikaru"))
self.assertEqual(siterender.system_section("arcade"), ("arcade", "Arcade"))
def test_systems_json_gaps(self) -> None:
plain = sorted(i for i in SYSTEMS_JSON_GAPS if siterender.system_section(i) is None)
self.assertEqual(plain, sorted(STAYS_PLAIN))
def test_parents_named_by_the_plan(self) -> None:
expect = {
"dos": ("dos", "DOSBox"),
"scummvm": ("other", "ScummVM"),
"tyrian": ("other", "opentyrian"),
"bbc-master-128": ("other", "beebem"),
}
for sys_id, section in expect.items():
with self.subTest(sys_id=sys_id):
self.assertEqual(siterender.system_section(sys_id), section)
@unittest.skipUnless((REPO_ROOT / "database.json").exists(), "database.json is not built")
class LinksWaitForTheSection(unittest.TestCase):
"""A board links once the system pages wrote its section, never to a
page nobody wrote: the build's link check stops on those."""
def setUp(self) -> None:
_reset()
self.addCleanup(_reset)
def test_unwritten_board_stays_plain(self) -> None:
self.assertIsNone(siterender.system_section("igs-pgm"))
self.assertIsNone(siterender.system_section("naomi2"))
self.assertEqual(siterender.console_href("arcade", "IGS PolyGame Master"),
"systems/arcade.md#igs-polygame-master")
def test_written_board_links_and_takes_its_cards(self) -> None:
siterender.record_board_section("igs-pgm")
self.assertEqual(siterender.system_section("igs-pgm"),
("arcade", "IGS PolyGame Master"))
self.assertIsNone(siterender.system_section("sega-naomi"))
sha1 = next(s for s, e in json.loads(
(REPO_ROOT / "database.json").read_text(encoding="utf-8"))["files"].items()
if e["path"] == "bios/Arcade/Arcade/pgm.zip")
self.assertEqual(siterender.card_page("bios/Arcade/Arcade/pgm.zip", sha1),
"arcade-igs-polygame-master")
siterender.reset_console_pages()
self.assertIsNone(siterender.system_section("igs-pgm"))
def test_an_alias_records_its_board(self) -> None:
siterender.record_board_section("sega-atomiswave")
self.assertEqual(siterender.system_section("sammy-atomiswave"),
("arcade", "Sammy Atomiswave"))
def test_no_board_refused(self) -> None:
with self.assertRaises(KeyError):
siterender.record_board_section("sony-playstation")
class BoardRules(unittest.TestCase):
"""The rules on a fixture: the folder decides nothing, a tie is no
board, a machine's own set is no board, contents: rejects a homonym."""
def setUp(self) -> None:
tmp_root = REPO_ROOT / "tmp"
tmp_root.mkdir(exist_ok=True)
self._tmp = tempfile.TemporaryDirectory(dir=tmp_root)
self.root = Path(self._tmp.name)
siterender.set_console_sections({("Arcade", "MAME"), ("SNK", "Neo Geo")})
def tearDown(self) -> None:
self._tmp.cleanup()
_reset()
def _zip(self, name: str, crc_member: bytes) -> str:
path = self.root / name
with zipfile.ZipFile(path, "w") as archive:
archive.writestr("rom.bin", crc_member)
return str(path)
def _install(self, entries: dict[str, list[dict]], files: dict[str, str]) -> None:
"""``files``: sha1 -> path. Names are the paths' base names."""
db = {
"files": {sha1: {"path": path, "name": Path(path).name} for sha1, path in files.items()},
"indexes": {"by_name": {}, "by_path_suffix": {}},
}
for sha1, path in files.items():
db["indexes"]["by_name"].setdefault(Path(path).name, []).append(sha1)
profiles = {key: {"systems": [], "files": rows} for key, rows in entries.items()}
siterender.set_arcade_boards(profiles, db)
def test_folder_decides_nothing(self) -> None:
self._install(
{"core": [{"name": "pgm.zip", "category": "bios_zip", "system": "igs-pgm"}]},
{"a" * 40: "bios/Arcade/MAME/pgm.zip", "b" * 40: "bios/Other/x/pgm.zip"},
)
self.assertEqual(siterender.arcade_board("a" * 40), "igs-pgm")
self.assertEqual(siterender.arcade_board("b" * 40), "igs-pgm")
def test_tie_is_no_board(self) -> None:
self._install(
{
"one": [{"name": "dev.zip", "category": "bios_zip", "system": "igs-pgm"}],
"two": [{"name": "dev.zip", "category": "bios_zip", "system": "konami-gv"}],
},
{"c" * 40: "bios/Arcade/MAME/dev.zip"},
)
self.assertIsNone(siterender.arcade_board("c" * 40))
def test_majority_wins(self) -> None:
self._install(
{
"one": [{"name": "dev.zip", "category": "bios_zip", "system": "igs-pgm"}],
"two": [{"name": "dev.zip", "category": "bios_zip", "system": "igs-pgm"}],
"three": [{"name": "dev.zip", "category": "bios_zip", "system": "konami-gv"}],
},
{"c" * 40: "bios/Arcade/MAME/dev.zip"},
)
self.assertEqual(siterender.arcade_board("c" * 40), "igs-pgm")
def test_machine_set_is_no_board(self) -> None:
self._install(
{
"one": [{"name": "neogeo.zip", "category": "bios_zip", "system": "snk-neogeo-mvs"}],
"two": [{"name": "neogeo.zip", "category": "bios_zip", "system": "igs-pgm"}],
},
{"d" * 40: "bios/Arcade/MAME/neogeo.zip"},
)
self.assertIsNone(siterender.arcade_board("d" * 40))
def test_contents_reject_a_homonym(self) -> None:
good = self._zip("good.zip", b"good")
other = self.root / "other"
other.mkdir()
bad = str(other / "good.zip")
with zipfile.ZipFile(bad, "w") as archive:
archive.writestr("rom.bin", b"something else")
crc = f"{zipfile.ZipFile(good).getinfo('rom.bin').CRC:08x}"
self._install(
{"core": [{"name": "good.zip", "category": "bios_zip", "system": "igs-pgm",
"contents": [{"name": "rom.bin", "crc32": crc}]}]},
{"e" * 40: good, "f" * 40: bad},
)
# Both copies filed under Arcade, as by_path_suffix records it.
db = siterender._arcade_source[0][1]
db["indexes"]["by_path_suffix"] = {
"Arcade/MAME/good.zip": ["e" * 40], "Arcade/FBNeo/good.zip": ["f" * 40]}
self.assertEqual(siterender.arcade_board("e" * 40), "igs-pgm")
self.assertIsNone(siterender.arcade_board("f" * 40))
def test_no_copy_under_arcade_no_section(self) -> None:
"""A board with no set under Arcade has no section there to lead to."""
self._install(
{"core": [{"name": "pgm.zip", "category": "bios_zip", "system": "igs-pgm"}]},
{"a" * 40: "bios/IGS/PGM/pgm.zip"},
)
self.assertIsNone(siterender.arcade_board("a" * 40))
with self.assertRaises(KeyError):
siterender.record_board_section("igs-pgm")
if __name__ == "__main__":
unittest.main()