mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 12:49:03 -05:00
An app found by content says to get one BIOS of any name and where it goes, with no install command it cannot carry out. Every arcade core names its romset, part pages name their parent and neighbours, a required file says which system it serves, and the core template leaves the index.
172 lines
7.3 KiB
Python
172 lines
7.3 KiB
Python
"""Emulator pages, round 4 (package 7).
|
|
|
|
AetherSX2's page offered a command that answered "No supported platform
|
|
detected" and a file to get by one name, while the app reads any PS2 BIOS
|
|
by its contents. FinalBurn Neo and MAME 2003-Plus named no romset under
|
|
their intro. MAME's card pages and the data directory pages did not say
|
|
where they sit. melonDS marked its DSi files "required" above its DS files.
|
|
The `00_example` template was listed as an emulator.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
|
|
import generate_site as gs # noqa: E402
|
|
|
|
EMPTY_DB = {"files": {}, "indexes": {"by_name": {}}}
|
|
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"))
|
|
|
|
|
|
def _pages(name: str, profile: dict | None = None) -> dict[str, str]:
|
|
return gs.emulator_pages(name, profile or _profile(name), EMPTY_DB, None, set())
|
|
|
|
|
|
def _front(page: str) -> dict:
|
|
assert page.startswith("---\n"), page[:80]
|
|
return yaml.safe_load(page[4:page.index("\n---\n", 4)])
|
|
|
|
|
|
class AnAppThatReadsAnyBios(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.page = _pages("aethersx2")["aethersx2"]
|
|
|
|
def test_the_intro_says_what_to_do(self):
|
|
intro = self.page[self.page.index("# AetherSX2"):self.page.index("About this profile")]
|
|
self.assertIn("AetherSX2 needs one Sony PlayStation 2 BIOS file, any region.", intro)
|
|
self.assertRegex(intro, r"\n1\. .+\n2\. Put it in the `bios` folder inside the app's own data folder\.")
|
|
self.assertIn("Any file name works. The app reads what the file holds.", intro)
|
|
self.assertNotIn("It reads", intro)
|
|
|
|
def test_the_card_names_an_example(self):
|
|
self.assertIn("a Sony PlayStation 2 BIOS (example: <code>ps2-0230a-20080220.bin</code>)",
|
|
self.page)
|
|
|
|
def test_no_command_that_cannot_place_the_file(self):
|
|
self.assertNotIn("--emulator aethersx2", self.page)
|
|
self.assertNotIn("--emulator nethersx2", _pages("nethersx2")["nethersx2"])
|
|
|
|
def test_a_core_keeps_its_command(self):
|
|
# LRPS2 runs in RetroArch, whose BIOS folder the installer finds.
|
|
page = _pages("lrps2")["lrps2"]
|
|
self.assertIn("--emulator lrps2", page)
|
|
self.assertIn("2. Put it in `pcsx2/bios/`, inside your BIOS folder.", page)
|
|
self.assertIn("a Sony PlayStation 2 BIOS, any file name", page)
|
|
|
|
def test_only_found_by_content_with_one_bios(self):
|
|
# DuckStation picks the BIOS of the game's region: its intro stays.
|
|
self.assertIsNone(gs.any_name_bios(_profile("duckstation")))
|
|
self.assertIsNone(gs.any_name_bios(_profile("melonds")))
|
|
self.assertIsNotNone(gs.any_name_bios(_profile("pcsx2")))
|
|
|
|
def test_the_download_links_the_collection_card(self):
|
|
steps = gs._any_name_steps("X", _profile("aethersx2"),
|
|
gs.any_name_bios(_profile("aethersx2")),
|
|
"../systems/sony.md#sha1-abc")
|
|
self.assertIn("1. Download [`ps2-0230a-20080220.bin`](../systems/sony.md#sha1-abc).", steps)
|
|
|
|
|
|
class EveryArcadeCoreNamesItsRomset(unittest.TestCase):
|
|
def test_finalburn_and_mame_2003_plus(self):
|
|
for name, romset in (("fbneo", "FinalBurn Neo v1.0.0.2"),
|
|
("mame2003_plus", "MAME 0.78 (plus backports)")):
|
|
page = _pages(name)[name]
|
|
line = page.index(f"Its BIOS and game sets must come from the {romset} romset.")
|
|
self.assertLess(line, page.index("About this profile"), name)
|
|
|
|
|
|
class PartsKnowWhereTheySit(unittest.TestCase):
|
|
def test_emulator_card_parts(self):
|
|
pages = _pages("mame")
|
|
parts = sorted((stem for stem in pages if stem.startswith("mame-files-")),
|
|
key=lambda stem: int(stem.rsplit("-", 1)[1]))
|
|
self.assertGreater(len(parts), 2)
|
|
for index, stem in enumerate(parts):
|
|
front = _front(pages[stem])
|
|
self.assertEqual(front["parent"], {"label": "MAME", "link": "mame.md"})
|
|
self.assertEqual(front["searchWeight"], 0.5)
|
|
self.assertEqual(front["prev"]["link"],
|
|
f"{parts[index - 1]}.md" if index else "mame.md")
|
|
if index + 1 < len(parts):
|
|
self.assertEqual(front["next"]["link"], f"{parts[index + 1]}.md")
|
|
else:
|
|
self.assertIs(front["next"], False)
|
|
|
|
def test_data_directory_parts(self):
|
|
ref = "x-sys"
|
|
members = {f"f{i:05d}": {"size": 1, "sha1": f"{i:040x}"}
|
|
for i in range(gs.DATA_TREE_ROWS_PER_PAGE * 2 + 1)}
|
|
gs._DATA_MEMBERS[ref] = members
|
|
gs._DATA_READERS[ref] = [("x", "X")]
|
|
try:
|
|
pages = gs.data_tree_pages(ref, members)
|
|
self.assertEqual(len(pages), 3)
|
|
middle = _front(gs._data_tree_page(ref, {}, pages, 1))
|
|
finally:
|
|
del gs._DATA_MEMBERS[ref], gs._DATA_READERS[ref]
|
|
self.assertEqual(middle["parent"], {"label": "X", "link": "x.md"})
|
|
self.assertEqual(middle["prev"]["link"], f"{pages[0][0]}.md")
|
|
self.assertEqual(middle["next"]["link"], f"{pages[2][0]}.md")
|
|
self.assertEqual(middle["search"], "exclude")
|
|
|
|
|
|
class ARequiredFileSaysForWhichSystem(unittest.TestCase):
|
|
def test_melonds_dsi_files(self):
|
|
page = _pages("melonds")["melonds"]
|
|
card = page[page.index('id="file-dsi-nand-bin"'):]
|
|
card = card[:card.index("</h3>")]
|
|
self.assertIn(">required for Nintendo DSi</span>", card)
|
|
self.assertNotIn(">required</span>", page)
|
|
|
|
def test_one_system_says_required(self):
|
|
page = _pages("aethersx2")["aethersx2"]
|
|
self.assertIn(">required</span>", page)
|
|
self.assertNotIn("required for", page)
|
|
|
|
|
|
class TheTemplateIsNoEmulator(unittest.TestCase):
|
|
def test_off_the_index_and_the_search(self):
|
|
profiles = {"00_example": _profile("00_example"), "melonds": _profile("melonds")}
|
|
index = gs.generate_emulators_index(profiles)
|
|
self.assertNotIn("00_example", index)
|
|
self.assertIn("melonds.md", index)
|
|
self.assertEqual(_front(_pages("00_example")["00_example"])["search"], "exclude")
|
|
|
|
|
|
@unittest.skipUnless((DIST / "emulators" / "aethersx2" / "index.html").is_file(), "site not built")
|
|
class TheBuiltPages(unittest.TestCase):
|
|
def _html(self, path: str) -> str:
|
|
return (DIST / path / "index.html").read_text(encoding="utf-8")
|
|
|
|
def test_aethersx2_downloads_from_the_collection(self):
|
|
page = self._html("emulators/aethersx2")
|
|
self.assertRegex(page, r'Download <a href="[^"]*/systems/sony[^"]*#sha1-[0-9a-f]{12}"')
|
|
self.assertNotIn("--emulator aethersx2", page)
|
|
|
|
def test_the_index_leaves_the_template_out(self):
|
|
self.assertNotIn("00_example", self._html("emulators"))
|
|
self.assertIn('data-pagefind-body', self._html("emulators/melonds"))
|
|
self.assertNotIn('data-pagefind-body', self._html("emulators/00_example"))
|
|
|
|
def test_mame_parts_have_a_parent_crumb(self):
|
|
page = self._html("emulators/mame-files-3")
|
|
crumbs = re.search(r'<nav[^>]*rb-breadcrumbs.*?</nav>', page, re.S)
|
|
self.assertIsNotNone(crumbs)
|
|
self.assertIn(">MAME<", crumbs.group(0))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|