mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-04 09:03:47 -06:00
chore: merge round 5 search fixes
This commit is contained in:
commit
fecc6d9d32
15 files changed
+642
-67
No files matched your search
@@ -4360,31 +4360,44 @@ class EmulatorPagesStateTheirDataForMachines(unittest.TestCase):
|
||||
|
||||
data = emulator_structured_data({
|
||||
"emulator": "DuckStation", "source": "https://github.com/stenzek/duckstation",
|
||||
"source_commit": "4122fed9", "profiled_date": "2026-03-23",
|
||||
"source_commit": "4122fed9", "core_version": "v0.1-12070",
|
||||
"profiled_date": "2026-03-23",
|
||||
"files": [{"name": "scph5501.bin", "md5": "490F666E", "sha1": ["a" * 40]},
|
||||
{"name": "free.bin"}],
|
||||
})
|
||||
self.assertEqual(data["about"]["codeRepository"], "https://github.com/stenzek/duckstation")
|
||||
self.assertEqual(data["about"]["version"], "4122fed9")
|
||||
# The release the profile describes, never the pin (round 5, 3.5).
|
||||
self.assertEqual(data["about"]["version"], "v0.1-12070")
|
||||
self.assertNotIn("description", data["about"])
|
||||
self.assertEqual(data["dateModified"], "2026-03-23")
|
||||
self.assertEqual(data["hasPart"][0]["identifier"], ["sha1:" + "a" * 40, "md5:490f666e"])
|
||||
self.assertNotIn("identifier", data["hasPart"][1])
|
||||
|
||||
def test_every_repository_with_its_own_pin(self):
|
||||
"""xemu's libretro build lives in another repository at another
|
||||
commit, and a port's upstream_commit was never published."""
|
||||
def test_every_repository_with_its_own_release(self):
|
||||
"""xemu's libretro build lives in another repository. Each source
|
||||
repository states the release of its build, a version holding a
|
||||
commit id states none, and a port's upstream is another code base."""
|
||||
from scripts.generate_site import emulator_structured_data
|
||||
|
||||
data = emulator_structured_data({
|
||||
"emulator": "xemu",
|
||||
"source": {"standalone": "https://github.com/xemu-project/xemu",
|
||||
"libretro": "https://github.com/LibretroAdmin/xemu-libretro"},
|
||||
"emulator": "MesenCE",
|
||||
"source": {"standalone": "https://github.com/nesdev-org/MesenCE",
|
||||
"libretro": "https://github.com/libretro/MesenCE"},
|
||||
"source_commit": {"standalone": "f9b14039", "libretro": "d9804db7"},
|
||||
"upstream": "https://github.com/mborgerson/xemu", "upstream_commit": "aa11",
|
||||
"core_version": {"standalone": "2.2.1", "libretro": "2.0.0"},
|
||||
"upstream": "https://github.com/SourMesen/Mesen2", "upstream_commit": "aa11",
|
||||
})
|
||||
self.assertEqual(
|
||||
[(a["codeRepository"].rsplit("/", 1)[1], a.get("version")) for a in data["about"]],
|
||||
[("xemu", "f9b14039"), ("xemu-libretro", "d9804db7"), ("xemu", "aa11")])
|
||||
[("MesenCE", "2.2.1"), ("MesenCE", "2.0.0"), ("Mesen2", None)])
|
||||
xemu = emulator_structured_data({
|
||||
"emulator": "xemu",
|
||||
"source": {"standalone": "https://github.com/xemu-project/xemu",
|
||||
"libretro": "https://github.com/LibretroAdmin/xemu-libretro"},
|
||||
"core_version": {"standalone": "0.8.139-2-g25e1ee2fd5",
|
||||
"libretro": "libretro-experimental"},
|
||||
})
|
||||
self.assertEqual([a.get("version") for a in xemu["about"]], [None, None])
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -117,10 +117,12 @@ class GroupsAreGroups(unittest.TestCase):
|
||||
|
||||
def test_explore_groups_collapse_to_their_overview(self):
|
||||
explore = {key: value for key, value in self.groups.items() if key.startswith("Explore/")}
|
||||
self.assertEqual(explore["Explore/Systems"], {"href": "/retrobios/systems/", "prune": True})
|
||||
self.assertEqual(explore["Explore/Systems"],
|
||||
{"href": "/retrobios/systems/", "prune": True, "crumb": True})
|
||||
self.assertEqual(explore["Explore/Systems/Consoles and computers"]["href"],
|
||||
"/retrobios/systems/#consoles-and-computers")
|
||||
self.assertEqual(explore["Explore/Emulators/D"], {"href": "/retrobios/emulators/#d", "prune": True})
|
||||
self.assertEqual(explore["Explore/Emulators/D"],
|
||||
{"href": "/retrobios/emulators/#d", "prune": True, "crumb": False})
|
||||
|
||||
def test_no_group_points_at_one_of_its_own_pages(self):
|
||||
"""The overview a group opens on is its own index page or a section of
|
||||
|
||||
@@ -293,7 +293,12 @@ class TheBuiltSonyPage(unittest.TestCase):
|
||||
|
||||
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])
|
||||
# The opening line follows the profiles: "Start with" when most
|
||||
# emulators require the BIOS, "No file needed with ... Only ...
|
||||
# require" when most run without it. Either way it names a BIOS and
|
||||
# links it, before the first card.
|
||||
opening = section.split('class="rb-sys-file"', 1)[0]
|
||||
self.assertRegex(opening, r'<a href="[^"]+"><code[^>]*>scph\d+\.bin</code></a>')
|
||||
# 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)
|
||||
|
||||
@@ -291,8 +291,8 @@ class SearchFoldsPagesUnderAHash(unittest.TestCase):
|
||||
|
||||
def test_search_fields_are_weighted_and_never_shown(self):
|
||||
config = _read(CONFIG)
|
||||
self.assertIn("metaWeights: { title: 10, files: 0.5, aliases: 5, devices: 5, consoles: 10, terms: 30 },", config)
|
||||
self.assertIn("const UNSHOWN_FIELDS = new Set(['files', 'aliases', 'devices', 'consoles', 'terms']);", _read(SITE_TS))
|
||||
self.assertIn("metaWeights: { title: 10, files: 0.5, aliases: 10, devices: 5, consoles: 100, exact: 20, terms: 30 },", config)
|
||||
self.assertIn("const UNSHOWN_FIELDS = new Set(['files', 'aliases', 'devices', 'consoles', 'exact', 'terms']);", _read(SITE_TS))
|
||||
|
||||
|
||||
class OsTabsNeverScrollThePage(unittest.TestCase):
|
||||
|
||||
@@ -65,7 +65,8 @@ class MetaFields(unittest.TestCase):
|
||||
body = ('<h3 class="rb-file-title"><code>scph5501.bin</code></h3>'
|
||||
'<h3 class="rb-file-title"><code>a,b.bin</code></h3>')
|
||||
meta = sitestarlight.file_names_meta(body)
|
||||
self.assertIn('data-pagefind-meta="files:a b.bin scph5501.bin"', meta)
|
||||
# Each name as its parts, each part once (round 5, 3.2).
|
||||
self.assertIn('data-pagefind-meta="files:a b bin scph5501"', meta)
|
||||
self.assertRegex(meta, r"<span [^>]*hidden></span>")
|
||||
|
||||
def test_a_field_escapes_its_value(self) -> None:
|
||||
|
||||
@@ -214,8 +214,8 @@ class SearchFields(unittest.TestCase):
|
||||
|
||||
def test_declared_and_never_shown(self):
|
||||
config = _read(CONFIG)
|
||||
self.assertIn("metaWeights: { title: 10, files: 0.5, aliases: 5, devices: 5, consoles: 10, terms: 30 },", config)
|
||||
self.assertIn("const UNSHOWN_FIELDS = new Set(['files', 'aliases', 'devices', 'consoles', 'terms']);", _read(SITE_TS))
|
||||
self.assertIn("metaWeights: { title: 10, files: 0.5, aliases: 10, devices: 5, consoles: 100, exact: 20, terms: 30 },", config)
|
||||
self.assertIn("const UNSHOWN_FIELDS = new Set(['files', 'aliases', 'devices', 'consoles', 'exact', 'terms']);", _read(SITE_TS))
|
||||
|
||||
|
||||
class SearchLoadsOnFirstUse(unittest.TestCase):
|
||||
|
||||
@@ -120,7 +120,8 @@ class FieldsFromFrontMatter(unittest.TestCase):
|
||||
"terms": "bios not found, bios missing",
|
||||
})["text"]
|
||||
self.assertIn('data-pagefind-meta="consoles:Neo Geo Neo Geo Pocket Color"', text)
|
||||
self.assertIn('data-pagefind-meta="terms:bios not found bios missing"', text)
|
||||
self.assertIn('data-pagefind-meta="terms:bios not found bios missing Neo Geo BIOS '
|
||||
'Neo Geo Pocket Color BIOS"', text)
|
||||
# A field value is an attribute: none of it reaches an excerpt.
|
||||
self.assertNotRegex(text, r">[^<]*bios missing[^<]*<")
|
||||
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
"""Round 5, package 3: search, sidebar and head.
|
||||
|
||||
The get/ download pages sat in no tab, and the theme then numbered the tabs
|
||||
0 to 4. "snes bios" found NES.emu and never the SNES page, which answers
|
||||
"No file needed" and so never says BIOS where the search reads. An emulator
|
||||
crumb ran through its letter group ("Emulators / D"). The home page's
|
||||
structured data named the page and called it part of itself, and an
|
||||
emulator's source repository published a commit id as its version beside a
|
||||
placeholder description. These tests read the generator and, where it is
|
||||
built, the site.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
from html.parser import HTMLParser
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
|
||||
import generate_site # noqa: E402
|
||||
import sitestarlight # noqa: E402
|
||||
from common import list_registered_platforms, load_emulator_profiles # noqa: E402
|
||||
from tests.test_site_r2_nav import ANCHORS, _nav, _present # noqa: E402
|
||||
|
||||
|
||||
def _gate_queries() -> dict[str, tuple[tuple[str, ...], int]]:
|
||||
"""The gate's QUERIES, read from website/tools/search_gate.py: a tool
|
||||
script, not a module the suite installs."""
|
||||
tree = ast.parse((REPO_ROOT / "website" / "tools" / "search_gate.py").read_text(encoding="utf-8"))
|
||||
node = next(node for node in tree.body if isinstance(node, ast.AnnAssign)
|
||||
and isinstance(node.target, ast.Name) and node.target.id == "QUERIES")
|
||||
return ast.literal_eval(node.value)
|
||||
|
||||
|
||||
QUERIES = _gate_queries()
|
||||
DIST = REPO_ROOT / "website" / "dist"
|
||||
ROUTE = REPO_ROOT / "website" / "src" / "routeData.ts"
|
||||
PLATFORMS = str(REPO_ROOT / "platforms")
|
||||
|
||||
|
||||
def _sidebar_with_get_pages() -> tuple[dict, list[str]]:
|
||||
nav = _nav()
|
||||
names = list_registered_platforms(PLATFORMS, include_archived=True)
|
||||
get_pages = ["get/index.md", *(f"get/{name}.md" for name in names)]
|
||||
return sitestarlight.sidebar(nav, _present(nav) | set(get_pages), ANCHORS), get_pages
|
||||
|
||||
|
||||
class GetPagesSitInYourSetup(unittest.TestCase):
|
||||
"""3.1: a folder the navigation lists nothing of takes the tab of the
|
||||
pages of the same name, and nothing is excluded from every tab."""
|
||||
|
||||
def test_every_get_page_joins_your_setup(self):
|
||||
side, get_pages = _sidebar_with_get_pages()
|
||||
self.assertEqual(side["options"]["exclude"], [])
|
||||
setup = set(side["options"]["topics"].get("your-setup", []))
|
||||
for rel in get_pages:
|
||||
with self.subTest(page=rel):
|
||||
self.assertIn("/" + sitestarlight._sidebar_link(rel).strip("/"), setup)
|
||||
|
||||
def test_no_page_is_left_out_of_every_tab(self):
|
||||
nav = _nav()
|
||||
present = _present(nav) | {"orphans/alone.md"}
|
||||
side = sitestarlight.sidebar(nav, present, ANCHORS)
|
||||
self.assertEqual(side["options"]["exclude"], [])
|
||||
placed = {path for paths in side["options"]["topics"].values() for path in paths}
|
||||
self.assertIn("/orphans/alone", placed)
|
||||
|
||||
|
||||
class ConsoleWordsReachTheConsole(unittest.TestCase):
|
||||
"""3.2: "<name> BIOS" for each console the page names, from its own
|
||||
`consoles` field, never a word typed in the generator."""
|
||||
|
||||
def test_each_console_with_bios(self):
|
||||
self.assertEqual(sitestarlight.console_words(["SNES", "Super Famicom"]),
|
||||
["SNES BIOS", "Super Famicom BIOS"])
|
||||
|
||||
def test_the_terms_field_carries_them(self):
|
||||
_target, text = sitestarlight.finalize_page(
|
||||
"systems/nintendo-snes.md", "# SNES\n\nNo file needed.\n", level="0",
|
||||
weight=None, browser_title="SNES", description="d",
|
||||
structured={"@type": "TechArticle"}, terms={},
|
||||
authored={"consoles": ["SNES"]})
|
||||
self.assertIn('data-pagefind-meta="consoles:SNES"', text)
|
||||
self.assertIn('data-pagefind-meta="terms:SNES BIOS"', text)
|
||||
|
||||
def test_no_search_word_is_typed_in_the_generator(self):
|
||||
tree = ast.parse((REPO_ROOT / "scripts" / "sitestarlight.py").read_text(encoding="utf-8"))
|
||||
docstrings = {id(node.value) for node in ast.walk(tree) if isinstance(node, ast.Expr)}
|
||||
strings = {node.value.casefold() for node in ast.walk(tree)
|
||||
if isinstance(node, ast.Constant) and isinstance(node.value, str)
|
||||
and id(node) not in docstrings}
|
||||
typed = {text for text in strings for query in QUERIES
|
||||
if re.search(rf"(?<!\w){re.escape(query)}(?!\w)", text)}
|
||||
self.assertEqual(typed, set())
|
||||
|
||||
|
||||
class ANameAnotherPageCovers(unittest.TestCase):
|
||||
"""3.2: the PlayStation 2 page holds every word of "playstation bios".
|
||||
A console whose words another page's consoles also hold is written once
|
||||
more, in `exact`, on its own page, and file names reach the search as
|
||||
their parts: "ds" found twelve dsi_* names on the DSi page."""
|
||||
|
||||
PAGES = {
|
||||
"systems/sony-playstation": ["PlayStation"],
|
||||
"systems/sony-playstation-2": ["PlayStation 2"],
|
||||
"systems/sony": ["PlayStation 3", "PlayStation Portable"],
|
||||
"systems/nintendo-nintendo-ds": ["Nintendo DS"],
|
||||
"systems/nintendo-nintendo-dsi": ["Nintendo DSi"],
|
||||
"systems/nintendo": ["Famicom Disk System", "Nintendo 64", "Super Famicom Box"],
|
||||
"systems/arcade-nintendo-super-system": ["Nintendo Super System"],
|
||||
"systems/sega-saturn": ["Saturn"],
|
||||
}
|
||||
|
||||
def words(self) -> dict[str, set[str]]:
|
||||
return {page: sitestarlight.page_console_words({"consoles": names}, "")
|
||||
for page, names in self.PAGES.items()}
|
||||
|
||||
def test_names_another_page_covers(self):
|
||||
covered = {page: sitestarlight.covered_names(names, page, self.words())
|
||||
for page, names in self.PAGES.items()}
|
||||
self.assertEqual(covered, {
|
||||
"systems/sony-playstation": ["PlayStation"],
|
||||
"systems/sony-playstation-2": [],
|
||||
# Every word of PlayStation 3 is on the PlayStation 2 page, but
|
||||
# not "3" itself: no page covers it.
|
||||
"systems/sony": [],
|
||||
"systems/nintendo-nintendo-ds": ["Nintendo DS"],
|
||||
"systems/nintendo-nintendo-dsi": [],
|
||||
"systems/nintendo": [],
|
||||
# Nintendo, Super and System, each in another name.
|
||||
"systems/arcade-nintendo-super-system": ["Nintendo Super System"],
|
||||
"systems/sega-saturn": [],
|
||||
})
|
||||
|
||||
def test_words_of_a_decorated_page(self):
|
||||
text = '<span data-pagefind-meta="consoles:Nintendo DS" hidden></span>'
|
||||
self.assertEqual(sitestarlight.page_console_words({}, text), {"nintendo", "ds"})
|
||||
self.assertEqual(sitestarlight.page_console_words({}, "No field."), set())
|
||||
|
||||
def test_the_exact_field_is_written_on_the_covered_page_only(self):
|
||||
words = self.words()
|
||||
|
||||
def text(page: str) -> str:
|
||||
return sitestarlight.finalize_page(
|
||||
f"{page}.md", "# T\n\nBody.\n", level="0", weight=None,
|
||||
browser_title="T", description="d", structured={"@type": "TechArticle"},
|
||||
terms={}, authored={"consoles": self.PAGES[page]}, words_by_page=words)[1]
|
||||
|
||||
self.assertIn('data-pagefind-meta="exact:PlayStation"', text("systems/sony-playstation"))
|
||||
for page in ("systems/sony-playstation-2", "systems/sega-saturn"):
|
||||
with self.subTest(page=page):
|
||||
self.assertNotIn('data-pagefind-meta="exact:', text(page))
|
||||
|
||||
def test_the_field_is_weighted_and_never_shown(self):
|
||||
config = (REPO_ROOT / "website" / "astro.config.mjs").read_text(encoding="utf-8")
|
||||
weights = re.search(r"metaWeights: \{([^}]*)\}", config)[1]
|
||||
fields = dict(re.findall(r"(\w+): ([\d.]+)", weights))
|
||||
self.assertIn("exact", fields)
|
||||
# Below the console field, so the longer name keeps its own query.
|
||||
self.assertLess(float(fields["exact"]), float(fields["consoles"]))
|
||||
site = (REPO_ROOT / "website" / "src" / "scripts" / "site.ts").read_text(encoding="utf-8")
|
||||
self.assertRegex(site, r"UNSHOWN_FIELDS = new Set\(\[[^\]]*'exact'")
|
||||
|
||||
def test_file_names_as_parts(self):
|
||||
body = "".join(f'<h3 class="rb-file-title"><code>{name}</code></h3>'
|
||||
for name in ("dsi_bios7.bin", "DSi_Nand_USA.bin", "NDS_Bios7.bin"))
|
||||
meta = sitestarlight.file_names_meta(body)
|
||||
self.assertIn('data-pagefind-meta="files:DSi Nand USA bin NDS Bios7"', meta)
|
||||
# A letter of any script is a letter.
|
||||
self.assertIn("ファミコン", sitestarlight.file_names_meta(
|
||||
'<h3 class="rb-file-title"><code>ファミコン_bios.rom</code></h3>'))
|
||||
|
||||
|
||||
class GateQueries(unittest.TestCase):
|
||||
"""3.2, 3.3 and 3.6: the queries the gate runs, the pages that answer
|
||||
them and the rank they must reach."""
|
||||
|
||||
EXPECTED = {
|
||||
"snes bios": (("systems/nintendo-snes/",), 1),
|
||||
"nes bios": (("systems/nintendo-nes/",), 1),
|
||||
"gamecube bios": (("systems/nintendo-gamecube/",), 1),
|
||||
"game boy bios": (("systems/nintendo-game-boy/",), 1),
|
||||
"3ds bios": (("systems/nintendo-3ds/",), 1),
|
||||
"playstation bios": (("systems/sony-playstation/",), 1),
|
||||
"playstation 2 bios": (("systems/sony-playstation-2/",), 1),
|
||||
"playstation 3 bios": (("systems/sony/",), 1),
|
||||
"game boy advance bios": (("systems/nintendo/",), 1),
|
||||
"game boy color bios": (("systems/nintendo/",), 1),
|
||||
"nintendo ds bios": (("systems/nintendo-nintendo-ds/",), 1),
|
||||
"nintendo dsi bios": (("systems/nintendo-nintendo-dsi/",), 1),
|
||||
"wii bios": (("systems/nintendo/",), 1),
|
||||
"wii u bios": (("systems/nintendo-wii-u/",), 1),
|
||||
"saturn bios": (("systems/sega-saturn/",), 1),
|
||||
"check my bios folder": (("wiki/troubleshooting/",), 3),
|
||||
"is my bios correct": (("files/",), 3),
|
||||
"schema": (("data/",), 3),
|
||||
"json schema": (("data/",), 3),
|
||||
"license": (("data/", "wiki/legal/"), 3),
|
||||
"check bios folder script": (("data/",), 3),
|
||||
"ios": (("which-pack/", "wiki/faq/"), 3),
|
||||
}
|
||||
|
||||
def test_the_gate_runs_each_query(self):
|
||||
for query, expected in self.EXPECTED.items():
|
||||
with self.subTest(query=query):
|
||||
self.assertEqual(QUERIES.get(query), expected)
|
||||
|
||||
def test_each_console_query_names_a_generated_console_page(self):
|
||||
docs = REPO_ROOT / "website" / "src" / "content" / "docs"
|
||||
if not docs.is_dir():
|
||||
self.skipTest("site not generated")
|
||||
for query, (pages, _rank) in self.EXPECTED.items():
|
||||
if not query.endswith(" bios"):
|
||||
continue
|
||||
stem = pages[0].strip("/")
|
||||
with self.subTest(query=query):
|
||||
self.assertTrue(any((docs / f"{stem}{ext}").is_file() for ext in (".md", ".mdx")),
|
||||
stem)
|
||||
|
||||
|
||||
class LetterGroupsAreNoCrumb(unittest.TestCase):
|
||||
"""3.4: a group of names by their first letter is a way through a
|
||||
list, not a place."""
|
||||
|
||||
def test_by_initial(self):
|
||||
names = [{"label": "Dolphin", "link": "/d"}, {"label": "DuckStation", "link": "/e"}]
|
||||
self.assertTrue(sitestarlight.by_initial("D", names))
|
||||
self.assertFalse(sitestarlight.by_initial("E", names))
|
||||
digits = [{"label": "2600.emu", "link": "/a"}, {"label": "81", "link": "/b"}]
|
||||
self.assertTrue(sitestarlight.by_initial("0 to 9", digits))
|
||||
self.assertFalse(sitestarlight.by_initial("Sony", names))
|
||||
self.assertFalse(sitestarlight.by_initial("D", [{"label": "D", "items": names}]))
|
||||
|
||||
def test_emulator_letter_groups_are_marked(self):
|
||||
side = sitestarlight.sidebar(_nav(), _present(_nav()), ANCHORS)
|
||||
letters = {key: value for key, value in side["groups"].items()
|
||||
if key.startswith("Explore/Emulators/")}
|
||||
self.assertTrue(letters)
|
||||
for key, group in letters.items():
|
||||
with self.subTest(group=key):
|
||||
self.assertIs(group["crumb"], False)
|
||||
self.assertIs(side["groups"]["Explore/Emulators"]["crumb"], True)
|
||||
self.assertIs(side["groups"]["Explore/Systems"]["crumb"], True)
|
||||
|
||||
def test_the_route_middleware_skips_them(self):
|
||||
self.assertIn("group?.crumb === false", ROUTE.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
class StructuredData(unittest.TestCase):
|
||||
"""3.5: the home page is the site, and a source repository states the
|
||||
release, never a commit id, and no placeholder description."""
|
||||
|
||||
def test_versions_name_releases_only(self):
|
||||
release = generate_site.release_versions
|
||||
self.assertEqual(release({"core_version": "v0.1-12070"}), {"": "v0.1-12070"})
|
||||
self.assertEqual(release({"core_version": "1.5.85"}), {"": "1.5.85"})
|
||||
self.assertEqual(release({"core_version": "8dc77b0"}), {})
|
||||
self.assertEqual(release({"core_version": "0.8.139-2-g25e1ee2fd5"}), {})
|
||||
self.assertEqual(release({"core_version": "libretro branch"}), {})
|
||||
self.assertEqual(release({"core_version": "edge"}), {})
|
||||
self.assertEqual(release({}), {})
|
||||
self.assertEqual(release({"core_version": {"standalone": "v1.1", "libretro": "4122fed"}}),
|
||||
{"standalone": "v1.1"})
|
||||
# A pin of digits only, in the version: supermodel3.
|
||||
self.assertEqual(release({"core_version": "0.3a-1481545-mm DirtBagXon (Sinden)",
|
||||
"upstream_commit": "14815455dc28fca2c8662fb1ed99d447ecf8f364"}),
|
||||
{})
|
||||
# A dated release whose digits start no pin stays.
|
||||
self.assertEqual(release({"core_version": "20200712", "source_commit": "a1626f4e"}),
|
||||
{"": "20200712"})
|
||||
|
||||
def test_the_pin_is_never_a_version(self):
|
||||
data = generate_site.emulator_structured_data({
|
||||
"emulator": "Redream", "source": "https://github.com/x/redream",
|
||||
"source_commit": "0123abcd",
|
||||
})
|
||||
self.assertEqual(data["about"], {"@type": "SoftwareSourceCode", "name": "Redream",
|
||||
"codeRepository": "https://github.com/x/redream"})
|
||||
|
||||
|
||||
class _Head(HTMLParser):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.ld: list[dict] = []
|
||||
self.sidebar: list[str] = []
|
||||
self.tabs_current = 0
|
||||
self._ld: list[str] | None = None
|
||||
self._label: list[str] | None = None
|
||||
self._tabs = False
|
||||
|
||||
def handle_starttag(self, tag: str, attrs: list) -> None:
|
||||
a = dict(attrs)
|
||||
if tag == "script" and a.get("type") == "application/ld+json":
|
||||
self._ld = []
|
||||
if tag == "summary":
|
||||
self._label = []
|
||||
if tag == "nav" and a.get("aria-label") == "Sections":
|
||||
self._tabs = True
|
||||
elif self._tabs and tag == "a" and a.get("aria-current"):
|
||||
self.tabs_current += 1
|
||||
|
||||
def handle_endtag(self, tag: str) -> None:
|
||||
if tag == "nav":
|
||||
self._tabs = False
|
||||
if tag == "script" and self._ld is not None:
|
||||
self.ld.append(json.loads("".join(self._ld)))
|
||||
self._ld = None
|
||||
if tag == "summary" and self._label is not None:
|
||||
self.sidebar.append(" ".join("".join(self._label).split()))
|
||||
self._label = None
|
||||
|
||||
def handle_data(self, data: str) -> None:
|
||||
for buffer in (self._ld, self._label):
|
||||
if buffer is not None:
|
||||
buffer.append(data)
|
||||
|
||||
|
||||
def _head(path: Path) -> _Head:
|
||||
page = _Head()
|
||||
page.feed(path.read_text(encoding="utf-8"))
|
||||
return page
|
||||
|
||||
|
||||
@unittest.skipUnless((DIST / "index.html").is_file(), "site not built")
|
||||
class BuiltSite(unittest.TestCase):
|
||||
def test_no_sidebar_group_is_named_by_a_number(self):
|
||||
for path in sorted(DIST.rglob("*.html")):
|
||||
labels = _head(path).sidebar
|
||||
with self.subTest(page=str(path.relative_to(DIST))):
|
||||
self.assertFalse([label for label in labels if re.fullmatch(r"\d+", label)])
|
||||
|
||||
def test_every_get_page_has_one_current_tab(self):
|
||||
pages = sorted((DIST / "get").rglob("index.html"))
|
||||
self.assertTrue(pages)
|
||||
for path in [*pages, DIST / "404.html", DIST / "index.html"]:
|
||||
with self.subTest(page=str(path.relative_to(DIST))):
|
||||
self.assertEqual(_head(path).tabs_current, 1)
|
||||
|
||||
def test_the_home_page_is_the_site(self):
|
||||
ld = _head(DIST / "index.html").ld
|
||||
self.assertEqual(len(ld), 1)
|
||||
self.assertEqual(ld[0]["@type"], "WebSite")
|
||||
self.assertEqual(ld[0]["name"], "RetroBIOS")
|
||||
self.assertNotIn("isPartOf", ld[0])
|
||||
|
||||
def test_no_source_repository_states_a_commit_or_a_description(self):
|
||||
pins = {str(value).casefold()[:7]
|
||||
for profile in load_emulator_profiles(str(REPO_ROOT / "emulators")).values()
|
||||
for field in ("source_commit", "upstream_commit")
|
||||
for value in (profile[field].values() if isinstance(profile.get(field), dict)
|
||||
else [profile.get(field)])
|
||||
if value}
|
||||
self.assertTrue(pins)
|
||||
seen = 0
|
||||
for path in sorted((DIST / "emulators").rglob("index.html")):
|
||||
for data in _head(path).ld:
|
||||
about = data.get("about") or []
|
||||
for entry in about if isinstance(about, list) else [about]:
|
||||
seen += 1
|
||||
with self.subTest(page=str(path.relative_to(DIST)), repo=entry.get("codeRepository")):
|
||||
self.assertNotIn("description", entry)
|
||||
runs = re.findall(r"[0-9a-f]{7,40}", str(entry.get("version", "")).casefold())
|
||||
self.assertFalse([run for run in runs if run[:7] in pins])
|
||||
self.assertFalse(generate_site._COMMIT_ID.search(str(entry.get("version", ""))))
|
||||
self.assertTrue(seen)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user