mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 12:49:03 -05:00
A file most of whose readers take beside another one is no start file (NAOMI opened on hod2bios.zip), and names that answer one slot for different emulators read as alternatives with their readers, each by the name of its card.
309 lines
15 KiB
Python
309 lines
15 KiB
Python
"""System pages and file cards (round 2, package 6).
|
|
|
|
The Sony page opened on LibPS.exe, its card titles read "scph5501.bin
|
|
permalink" in the site search, the two setup lines of a card were defined
|
|
only in a code comment, and the systems index named no console.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
import types
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
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
|
|
from cross_reference import FileConsumers # noqa: E402
|
|
|
|
DIST = REPO_ROOT / "website" / "dist"
|
|
|
|
|
|
def _file(name: str, char: str, path: str = "") -> dict:
|
|
return {"name": name, "sha1": char * 40, "md5": char * 32, "size": 524288,
|
|
"path": path or f"bios/Sony/PlayStation/{name}"}
|
|
|
|
|
|
def _consumers(readers: dict[str, list[dict]], platforms: dict[str, set[str]] | None = None
|
|
) -> types.SimpleNamespace:
|
|
"""Consumers where each sha1 is read by the given profile entries, one
|
|
emulator per 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 number, entry in enumerate(read_by):
|
|
emulator = f"emu{number}-{sha1[:4]}"
|
|
files_of[emulator] = [entry]
|
|
entries.setdefault(sha1, {})[emulator] = 0
|
|
emulators.setdefault(sha1, set()).add(emulator)
|
|
return types.SimpleNamespace(
|
|
platforms=platforms or {}, emulators=emulators, emulator_entries=entries,
|
|
emulator_files=files_of, installed={}, platform_systems={},
|
|
platform_destinations={},
|
|
)
|
|
|
|
|
|
class TheConsoleBiosComesFirst(unittest.TestCase):
|
|
"""Cards were sorted by byte order: capitals first, so LibPS.exe and
|
|
PSX_DTLH2000.bin opened the PlayStation section and scph5501.bin was
|
|
28th."""
|
|
|
|
def setUp(self):
|
|
self.files = [
|
|
_file("LibPS.exe", "1"), _file("PSX_DTLH2000.bin", "2"),
|
|
_file("dtlh1100.bin", "3"), _file("scph5501.bin", "4"),
|
|
_file("scph5500.bin", "5"), _file("scph1001.bin", "6"),
|
|
_file("openbios.bin", "7"),
|
|
]
|
|
self.consumers = _consumers({
|
|
"4" * 40: [{"name": "scph5501.bin", "required": True, "priority": 2,
|
|
"region": ["north-america"]}],
|
|
"5" * 40: [{"name": "scph5500.bin", "required": True, "priority": 5,
|
|
"region": ["japan"]}],
|
|
"6" * 40: [{"name": "scph1001.bin", "required": False, "priority": 1}],
|
|
"2" * 40: [{"name": "PSX_DTLH2000.bin", "required": False}],
|
|
}, platforms={"6" * 40: {"a", "b"}, "3" * 40: {"a", "b", "c"}})
|
|
|
|
def names(self) -> list[str]:
|
|
cards = gs._console_cards("Sony", self.files, self.consumers, set())
|
|
return [re.search(r"<code>(.*?)</code>", card[2])[1] for _v, card, _s in cards]
|
|
|
|
def test_required_then_priority_then_setups_then_name(self):
|
|
self.assertEqual(self.names(), [
|
|
"scph5501.bin", "scph5500.bin", # required, by priority
|
|
"scph1001.bin", # optional with a priority
|
|
"dtlh1100.bin", # listed by three setups
|
|
"LibPS.exe", "openbios.bin", "PSX_DTLH2000.bin", # by name, any case
|
|
])
|
|
|
|
def test_a_console_cut_in_parts_goes_by_name(self):
|
|
cards = gs._console_cards("Sony", self.files, self.consumers, set(), by_name=True)
|
|
names = [re.search(r"<code>(.*?)</code>", card[2])[1] for _v, card, _s in cards]
|
|
self.assertEqual(names, sorted(names, key=str.casefold))
|
|
|
|
def required_only(self) -> types.SimpleNamespace:
|
|
"""The BIOS readers alone: an emulator reading only an optional
|
|
file runs without the BIOS, and two of four would say so."""
|
|
return _consumers({
|
|
"4" * 40: [{"name": "scph5501.bin", "required": True, "priority": 2,
|
|
"region": ["north-america"]}],
|
|
"5" * 40: [{"name": "scph5500.bin", "required": True, "priority": 5,
|
|
"region": ["japan"]}],
|
|
})
|
|
|
|
def test_the_section_opens_on_the_bios_by_region(self):
|
|
lines = gs._start_with(self.files, self.required_only())
|
|
self.assertEqual(lines[0], "**Start with** the BIOS for the region of your games:")
|
|
self.assertIn("- **North America**: `scph5501.bin`", lines)
|
|
self.assertIn("- **Japan**: `scph5500.bin`", lines)
|
|
self.assertLess(lines.index("- **North America**: `scph5501.bin`"),
|
|
lines.index("- **Japan**: `scph5500.bin`"))
|
|
|
|
def test_regions_needing_the_same_file_share_a_line(self):
|
|
consumers = _consumers({"4" * 40: [
|
|
{"required": True, "region": ["north-america", "latin-america", "brazil"]}]})
|
|
lines = gs._start_with([_file("scph5501.bin", "4")], consumers)
|
|
# Brazil says nothing Latin America does not.
|
|
self.assertIn("- **North America, Latin America**: `scph5501.bin`", lines)
|
|
|
|
def test_a_long_list_names_the_first_and_counts_the_rest(self):
|
|
files = [_file(f"rom{i}.bin", "abcdefgh"[i]) for i in range(8)]
|
|
# One emulator requires all eight: files each required by another
|
|
# emulator are other names for one slot (round 5, 6.2).
|
|
consumers = _consumers({f["sha1"]: [{"required": True}] for f in files})
|
|
consumers.emulator_files = {"emu": [{"required": True}] * len(files)}
|
|
consumers.emulator_entries = {f["sha1"]: {"emu": i} for i, f in enumerate(files)}
|
|
self.assertEqual(gs._start_with(files, consumers)[0],
|
|
"**Start with**: `rom0.bin`, `rom1.bin`, `rom2.bin` and 5 more files. "
|
|
"They come first below.")
|
|
|
|
def test_no_required_file_no_line(self):
|
|
self.assertEqual(gs._start_with([_file("a.bin", "a")], _consumers({})), [])
|
|
|
|
def test_the_page_carries_the_line_under_the_console(self):
|
|
page = gs.generate_system_page("Sony", {"PlayStation": self.files}, self.required_only())
|
|
heading = page.index("## PlayStation")
|
|
self.assertLess(heading, page.index("**Start with**"))
|
|
self.assertLess(page.index("**Start with**"), page.index('class="rb-sys-file"'))
|
|
|
|
|
|
class ThePermalinkIsAnIcon(unittest.TestCase):
|
|
"""The word "permalink" was link text inside every card title: the
|
|
search showed "scph5501.bin permalink" and screen readers read it."""
|
|
|
|
def test_no_word_and_out_of_the_search(self):
|
|
lines = gs._system_file_card(_file("scph5501.bin", "4"), FileConsumers({"files": {}}))
|
|
title = lines[2]
|
|
link = re.search(r'<a class="rb-permalink"[^>]*>(.*?)</a>', title)
|
|
self.assertNotIn("permalink", re.sub(r"<[^>]+>", "", title))
|
|
self.assertIn('aria-label="Permanent link to scph5501.bin"', link[0])
|
|
self.assertIn("data-pagefind-ignore", link[0])
|
|
self.assertIn('<use href="#rb-link-icon"/>', link[1])
|
|
|
|
def test_the_page_draws_the_icon_once(self):
|
|
page = gs.generate_system_page(
|
|
"Sony", {"PlayStation": [_file("a.bin", "a"), _file("b.bin", "b")]},
|
|
FileConsumers({"files": {}}))
|
|
self.assertEqual(page.count('<symbol id="rb-link-icon"'), 1)
|
|
self.assertEqual(page.count('href="#rb-link-icon"'), 2)
|
|
|
|
|
|
class TheCardLinesSayWhatTheyMean(unittest.TestCase):
|
|
""""Platforms:" and "Also installed by:" listed different setups, as
|
|
lower-case ids, and only a code comment told them apart."""
|
|
|
|
def setUp(self):
|
|
sha1 = "4" * 40
|
|
self.consumers = types.SimpleNamespace(
|
|
platforms={sha1: {"batocera"}}, emulators={},
|
|
platform_systems={sha1: {"batocera": {"sony-playstation"}}},
|
|
platform_destinations={sha1: {"batocera": {"scph5501.bin"}}},
|
|
installed={sha1: {"retrobat": {"bios/scph5501.bin"}}},
|
|
)
|
|
self.lines = gs._system_file_card(_file("scph5501.bin", "4"), self.consumers)
|
|
|
|
def test_labels_and_display_names(self):
|
|
text = "\n".join(self.lines)
|
|
# Grouped by where the setups put the file (round 4, 6.9).
|
|
self.assertIn("<dt>On the setup's list</dt><dd><code>scph5501.bin</code>: <a href=\"../"
|
|
"platforms/batocera.md#sony-playstation\">Batocera</a></dd>", text)
|
|
self.assertIn("<dt>Packed for its emulators by</dt><dd><code>bios/scph5501.bin</code>: "
|
|
"<a href=\"../platforms/retrobat.md\">RetroBat</a></dd>", text)
|
|
self.assertNotIn("Platforms:", text)
|
|
self.assertNotIn("Also installed by", text)
|
|
|
|
def test_the_facts_are_out_of_the_search(self):
|
|
# The setup list made every card's excerpt a run of names and
|
|
# paths (round 4, V6): the card's title is what the search reads.
|
|
lists = [line for line in self.lines if line.startswith("<dl")]
|
|
self.assertEqual(lists, ['<dl class="rb-facts" data-pagefind-ignore>'])
|
|
|
|
def test_the_page_defines_both_lines(self):
|
|
page = gs.generate_system_page("Sony", {"PlayStation": [_file("scph5501.bin", "4")]},
|
|
self.consumers)
|
|
key = page.index("**On the setup's list** names")
|
|
self.assertLess(key, page.index('class="rb-sys-file"'))
|
|
self.assertIn("**Packed for its emulators by** names", page)
|
|
|
|
def test_a_file_nothing_reads_says_so(self):
|
|
title = gs._system_file_card(_file("GameIndex.yaml", "9"), FileConsumers({"files": {}}))[2]
|
|
self.assertIn(">No reader</span>", title)
|
|
self.assertNotIn("No reader", self.lines[2])
|
|
|
|
|
|
class FileFactsAreAGrid(unittest.TestCase):
|
|
"""Each card was two bullet lists of code chips."""
|
|
|
|
def test_one_value_cell_per_label_and_plain_hashes(self):
|
|
entry = {**_file("scph5501.bin", "4"), "crc32": "8d8cb7e4",
|
|
"provenance": {"redump": {"name": "ps-30a.bin"},
|
|
"tosec": {"name": "SCPH-5501 (1996).bin"}}}
|
|
lines = gs._system_file_card(entry, FileConsumers({"files": {}}))
|
|
facts = [line for line in lines if line.startswith("<dt>")]
|
|
self.assertTrue(facts)
|
|
for line in facts:
|
|
self.assertEqual(line.count("<dt>"), 1, line)
|
|
self.assertEqual(line.count("<dd>"), 1, line)
|
|
text = "\n".join(lines)
|
|
self.assertIn('<dt>SHA1</dt><dd><span class="rb-hash">' + "4" * 40 + "</span></dd>", text)
|
|
self.assertNotIn("<code class=\"rb-hash\">", text)
|
|
catalogued = next(line for line in facts if line.startswith("<dt>Catalogued"))
|
|
self.assertIn("<br>", catalogued)
|
|
self.assertNotRegex(text, r"(?m)^- ")
|
|
|
|
def test_collection_path_keeps_its_chip(self):
|
|
text = "\n".join(gs._system_file_card(_file("a.bin", "a"), FileConsumers({"files": {}})))
|
|
self.assertIn("<dt>Collection path</dt><dd><code>bios/Sony/PlayStation/a.bin</code>", text)
|
|
|
|
|
|
class ConsoleHeadingsStartWithTheName(unittest.TestCase):
|
|
"""The icon and a space came before the name: "Section titled
|
|
“ PlayStation”" on five of seven Sony headings. Round 3 removed the
|
|
icon altogether."""
|
|
|
|
def test_no_icon_even_when_one_exists(self):
|
|
icon = siterender._icon_name("Sony", "PlayStation")
|
|
with mock.patch.dict(siterender._icon_available, {icon: True}):
|
|
self.assertEqual(gs._console_heading("Sony", "PlayStation"),
|
|
"## PlayStation { #playstation }")
|
|
|
|
def test_no_icon_no_image(self):
|
|
icon = siterender._icon_name("Nobody", "Nothing")
|
|
with mock.patch.dict(siterender._icon_available, {icon: False}):
|
|
self.assertEqual(gs._console_heading("Nobody", "Nothing"), "## Nothing { #nothing }")
|
|
|
|
|
|
class TheSystemsIndexListsConsoles(unittest.TestCase):
|
|
"""The index listed 74 manufacturers and no console: PlayStation had to
|
|
be known to be under Sony."""
|
|
|
|
def test_one_row_per_console_by_its_name(self):
|
|
groups = {
|
|
"Sony": {"PlayStation": [_file("a.bin", "a")], "PlayStation 2": [_file("b.bin", "b")]},
|
|
"Other": {"ScummVM": [_file("c.bin", "c")]},
|
|
"Game Engines": {"TheXTech": [_file("d.png", "d")]},
|
|
}
|
|
page = gs.generate_systems_index(groups)
|
|
self.assertIn("| [Sony PlayStation](sony.md#playstation) | 1 | [Sony](sony.md) |", page)
|
|
self.assertIn("| [Sony PlayStation 2](sony.md#playstation-2) | 1 | [Sony](sony.md) |",
|
|
page)
|
|
# A program's folder under Other is named alone, in its own table.
|
|
consoles = page.split("## Emulator folders")[0]
|
|
self.assertNotIn("ScummVM", consoles)
|
|
self.assertIn("| [ScummVM](../emulators/scummvm.md) | [1 file](other.md#scummvm) |",
|
|
page.split("## Emulator folders")[1])
|
|
self.assertNotIn("TheXTech", consoles)
|
|
self.assertIn("| [TheXTech](game-engines.md#thextech)", page)
|
|
|
|
def test_rows_go_by_name_not_by_maker(self):
|
|
groups = {"Sega": {"Saturn": [_file("a.bin", "a")]},
|
|
"Atari": {"Lynx": [_file("b.bin", "b")]}}
|
|
page = gs.generate_systems_index(groups)
|
|
self.assertLess(page.index("Atari Lynx"), page.index("Sega Saturn"))
|
|
|
|
|
|
class FindAFileSaysWhatAHashIs(unittest.TestCase):
|
|
|
|
def test_the_sentence_above_the_box(self):
|
|
page = gs.generate_files_page()
|
|
intro = page.split('<form class="rb-file-lookup"')[0]
|
|
self.assertIn("A hash is a fingerprint of a file's contents", intro)
|
|
self.assertIn("(../wiki/glossary.md)", intro)
|
|
self.assertNotIn("**permalink**", page)
|
|
|
|
|
|
@unittest.skipUnless((DIST / "systems" / "sony" / "index.html").is_file(), "site not built")
|
|
class TheBuiltSonyPage(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.text = (DIST / "systems" / "sony" / "index.html").read_text(encoding="utf-8")
|
|
|
|
def test_no_card_title_says_permalink(self):
|
|
titles = re.findall(r'<h3[^>]*class="rb-file-title".*?</h3>', self.text, re.S)
|
|
self.assertTrue(titles)
|
|
for title in titles:
|
|
self.assertNotIn("permalink", re.sub(r"<[^>]+>", "", title))
|
|
|
|
def test_playstation_opens_on_a_bios_it_needs(self):
|
|
section = self.text.split('id="playstation"', 1)[1].split("<h2", 1)[0]
|
|
self.assertIn("Start with", section.split('class="rb-sys-file"', 1)[0])
|
|
# PlayStation has a page of its own, and its files open there.
|
|
own = (DIST / "systems" / "sony-playstation" / "index.html").read_text(encoding="utf-8")
|
|
self.assertIn('href="/retrobios/systems/sony-playstation/"', section)
|
|
first = re.search(r'class="rb-file-title"[^>]*><code[^>]*>(.*?)</code>', own)[1]
|
|
self.assertNotIn(first, {"LibPS.exe", "PSX_DTLH2000.bin"})
|
|
self.assertRegex(first, r"^scph\d+")
|
|
|
|
def test_headings_have_no_leading_space(self):
|
|
self.assertNotRegex(self.text, r'<h2 id="playstation[^"]*"><img[^>]*>\s')
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|