mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 15:43:45 -06:00
get/ pages join the tab of the setup pages of the same name, so no page is excluded from every tab and the tabs keep their names. Groups filing emulators by first letter are no crumb. A console page carries "<name> BIOS" in its terms field. The search gate runs the console, checker, schema, license and iOS queries.
261 lines
13 KiB
Python
261 lines
13 KiB
Python
"""Search (round 4, package 3).
|
|
|
|
The search gate listed the queries that missed and no workflow ran it: its
|
|
browser runner could not even start from the repository root. Console and
|
|
error words had no field to reach their page ("wonderswan color", "bios not
|
|
found"), parts of a split page carried the console page's weight and came
|
|
first for "which mame version", and 707 pages gave search engines one of
|
|
five template sentences of level 3 words as their description. These tests
|
|
read the generator, the gate, the workflow and, where it is built, the site.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import re
|
|
import sys
|
|
import tempfile
|
|
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
|
|
import sitestarlight # noqa: E402
|
|
import validate_site # noqa: E402
|
|
|
|
TOOLS = REPO_ROOT / "website" / "tools"
|
|
WORKFLOW = REPO_ROOT / ".github" / "workflows" / "deploy-site.yml"
|
|
DOCS = REPO_ROOT / "website" / "src" / "content" / "docs"
|
|
DIST = REPO_ROOT / "website" / "dist"
|
|
|
|
|
|
def _front(text: str) -> dict:
|
|
return yaml.safe_load(text.split("---\n")[1])
|
|
|
|
|
|
def _gate_queries() -> dict:
|
|
tree = ast.parse((TOOLS / "search_gate.py").read_text(encoding="utf-8"))
|
|
for node in tree.body:
|
|
if isinstance(node, ast.AnnAssign) and getattr(node.target, "id", "") == "QUERIES":
|
|
return ast.literal_eval(node.value)
|
|
raise AssertionError("no QUERIES in search_gate.py")
|
|
|
|
|
|
def _finalize(rel: str, body: str, authored: dict) -> dict:
|
|
_target, text = sitestarlight.finalize_page(
|
|
rel, body, level="", weight=None, browser_title="T", description="d",
|
|
structured={}, terms={}, authored=authored)
|
|
return {"front": _front(text), "text": text}
|
|
|
|
|
|
class TheGateRunsAndBlocks(unittest.TestCase):
|
|
def test_the_deploy_runs_the_gate_before_the_upload(self) -> None:
|
|
steps = yaml.safe_load(WORKFLOW.read_text(encoding="utf-8"))["jobs"]["build"]["steps"]
|
|
runs = [str(step.get("run", "")) for step in steps]
|
|
uses = [str(step.get("uses", "")) for step in steps]
|
|
gate = next(i for i, run in enumerate(runs)
|
|
if "website/tools/search_gate.py --site-dir website/dist" in run)
|
|
build = next(i for i, run in enumerate(runs) if "npm run build --prefix website" in run)
|
|
upload = next(i for i, use in enumerate(uses) if "upload-pages-artifact" in use)
|
|
self.assertLess(build, gate)
|
|
self.assertLess(gate, upload)
|
|
# The driver is pinned to a version and kept out of the lockfile.
|
|
self.assertRegex(runs[gate], r"npm install --no-save --prefix website playwright-core@\d+\.\d+\.\d+")
|
|
|
|
def test_no_third_party_action_and_every_action_pinned(self) -> None:
|
|
text = WORKFLOW.read_text(encoding="utf-8")
|
|
for repo, ref in re.findall(r"uses:\s*([\w.-]+/[\w.-]+)@(\S+)", text):
|
|
with self.subTest(action=repo):
|
|
self.assertTrue(repo.startswith("actions/"))
|
|
self.assertRegex(ref, r"^[0-9a-f]{40}$")
|
|
|
|
def test_the_runner_resolves_from_website_and_says_what_is_missing(self) -> None:
|
|
source = (TOOLS / "measure.mjs").read_text(encoding="utf-8")
|
|
self.assertIn("await import('playwright-core')", source)
|
|
self.assertIn("npm install --no-save --prefix website playwright-core", source)
|
|
self.assertNotRegex(source, r"^import \{ chromium \}", "a static import fails before any message")
|
|
|
|
def test_the_gate_serves_a_build_dir_and_fails_on_a_miss(self) -> None:
|
|
source = (TOOLS / "search_gate.py").read_text(encoding="utf-8")
|
|
self.assertIn("--site-dir", source)
|
|
self.assertIn("serve.mjs", source)
|
|
self.assertIn("return 0 if answered == len(QUERIES)", source)
|
|
|
|
def test_the_plans_queries_are_gated(self) -> None:
|
|
queries = _gate_queries()
|
|
expected = {
|
|
"playstation bios": ("systems/sony-playstation/", 1),
|
|
"please insert playstation cd-rom": ("wiki/troubleshooting/", 3),
|
|
"bios not found": ("wiki/troubleshooting/", 3),
|
|
"check my dump": ("files/", 1),
|
|
"md5 check": ("files/", 1),
|
|
"manifest": ("data/", 3),
|
|
"events": ("wiki/installer/", 3),
|
|
"exit code": ("wiki/installer/", 3),
|
|
"rate limit": ("data/", 3),
|
|
"ios": ("wiki/faq/", 3),
|
|
"dc bios missing": ("emulators/flycast/", 3),
|
|
"neogeo.zip": ("systems/snk/", 1),
|
|
"scph5501": ("systems/sony-playstation/", 3),
|
|
}
|
|
for query, (page, rank) in expected.items():
|
|
with self.subTest(query=query):
|
|
pages, gated = queries[query]
|
|
self.assertTrue(any(page.startswith(prefix) for prefix in pages))
|
|
self.assertLessEqual(gated, rank)
|
|
|
|
def test_the_home_has_a_weight_budget(self) -> None:
|
|
source = (TOOLS / "search_gate.py").read_text(encoding="utf-8")
|
|
self.assertIn("HOME_BUDGET_KB = 80", source)
|
|
|
|
|
|
class FieldsFromFrontMatter(unittest.TestCase):
|
|
def test_consoles_and_terms_become_search_fields(self) -> None:
|
|
text = _finalize("systems/snk.md", "# SNK\n\nBody.\n", {
|
|
"consoles": ["Neo Geo", "Neo Geo Pocket Color"],
|
|
"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 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[^<]*<")
|
|
|
|
def test_no_search_word_is_typed_in_the_module(self) -> None:
|
|
source = (REPO_ROOT / "scripts" / "sitestarlight.py").read_text(encoding="utf-8")
|
|
for words in ("bios not found", "mame version", "WonderSwan"):
|
|
self.assertNotIn(words, source)
|
|
|
|
def test_a_page_without_fields_gets_none(self) -> None:
|
|
text = _finalize("wiki/legal.md", "# Legal\n\nBody.\n", {})["text"]
|
|
self.assertNotIn("consoles:", text)
|
|
self.assertNotIn("terms:", text)
|
|
|
|
|
|
class FindOrCheckAFile(unittest.TestCase):
|
|
""""check my dump" and "md5 check" found emulator pages first."""
|
|
|
|
def test_the_page_weighs_two(self) -> None:
|
|
rel = Path("files/index.md")
|
|
self.assertEqual(gs.search_weight(rel, gs.page_level(rel)), 2)
|
|
|
|
def test_its_terms_become_a_field(self) -> None:
|
|
text = _finalize("files/index.md", "# Find or check a file\n\nBody.\n",
|
|
{"terms": "check dump, md5 check"})["text"]
|
|
self.assertIn('data-pagefind-meta="terms:check dump md5 check"', text)
|
|
|
|
def test_the_sidebar_names_it_as_its_title(self) -> None:
|
|
source = (REPO_ROOT / "scripts" / "generate_site.py").read_text(encoding="utf-8")
|
|
self.assertIn('{"Find or check a file": "files/index.md"}', source)
|
|
self.assertNotIn('{"Find a file": "files/index.md"}', source)
|
|
|
|
|
|
class PartPages(unittest.TestCase):
|
|
def test_part_weight(self) -> None:
|
|
self.assertEqual(gs.SEARCH_WEIGHT["part"], 0.5)
|
|
self.assertEqual(gs.search_weight(Path("systems/arcade-mame-5.md"), "part"), 0.5)
|
|
|
|
def test_a_part_keeps_its_own_weight_and_neighbours(self) -> None:
|
|
"""The MAME card parts write prev, next and searchWeight: the build
|
|
kept only parent and wrote prev: false, next: false."""
|
|
page = (
|
|
"---\n"
|
|
"parent:\n label: MAME\n link: mame.md\n"
|
|
"prev:\n label: MAME files, part 2 of 8\n link: mame-files-2.md\n"
|
|
"next:\n label: MAME files, part 4 of 8\n link: mame-files-4.md\n"
|
|
f"searchWeight: {gs.search_weight(Path('emulators/mame-files-3.md'), 'part')}\n"
|
|
"---\n\n# MAME files, part 3 of 8\n\nPart of [MAME](mame.md). "
|
|
"This part holds 196 entries, `a.zip` to `b.zip`.\n"
|
|
)
|
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
|
path = Path(tmp) / "emulators" / "mame-files-3.md"
|
|
path.parent.mkdir()
|
|
path.write_text(page, encoding="utf-8")
|
|
gs.PART_PAGES.add("emulators/mame-files-3.md")
|
|
self.addCleanup(gs.PART_PAGES.discard, "emulators/mame-files-3.md")
|
|
gs.decorate_markdown_pages(Path(tmp))
|
|
front = _front(path.read_text(encoding="utf-8"))
|
|
self.assertEqual(front["searchWeight"], 0.5)
|
|
self.assertEqual(front["prev"], {"label": "MAME files, part 2 of 8",
|
|
"link": "/retrobios/emulators/mame-files-2/"})
|
|
self.assertEqual(front["next"]["link"], "/retrobios/emulators/mame-files-4/")
|
|
self.assertEqual(front["parent"]["link"], "/retrobios/emulators/mame/")
|
|
self.assertIn("This part holds 196 entries, a.zip to b.zip", front["description"])
|
|
|
|
def test_built_mame_parts_state_their_neighbours(self) -> None:
|
|
part = DOCS / "emulators" / "mame-files-3.md"
|
|
if not part.is_file():
|
|
self.skipTest("site pages not generated")
|
|
front = _front(part.read_text(encoding="utf-8"))
|
|
if "searchWeight" not in front and front.get("prev") is False:
|
|
self.skipTest("the emulator pages package is not merged")
|
|
self.assertEqual(front["searchWeight"], 0.5)
|
|
self.assertTrue(str(front["prev"]["link"]).startswith("/retrobios/emulators/"))
|
|
|
|
|
|
class Descriptions(unittest.TestCase):
|
|
def test_titles_say_bios_files(self) -> None:
|
|
saved = dict(gs.PAGE_DATA)
|
|
self.addCleanup(lambda: (gs.PAGE_DATA.clear(), gs.PAGE_DATA.update(saved)))
|
|
gs.PAGE_DATA["emulators/duckstation.md"] = {"hasPart": [{"name": "scph5501.bin"}]}
|
|
self.assertEqual(gs._browser_title(Path("emulators/duckstation.md"), "DuckStation"),
|
|
"DuckStation BIOS files")
|
|
self.assertEqual(gs._browser_title(Path("systems/sega.md"), "Sega"), "Sega BIOS files")
|
|
self.assertEqual(gs._browser_title(Path("systems/sega-dreamcast.md"), "Sega: Dreamcast"),
|
|
"Dreamcast BIOS files (Sega)")
|
|
|
|
def test_an_emulator_page_is_described_by_its_intro(self) -> None:
|
|
saved = dict(gs.PAGE_DATA)
|
|
self.addCleanup(lambda: (gs.PAGE_DATA.clear(), gs.PAGE_DATA.update(saved)))
|
|
gs.PAGE_DATA["emulators/duckstation.md"] = {"hasPart": [{"name": "scph5501.bin"}]}
|
|
body = ("# DuckStation\n\nDuckStation emulates Sony PlayStation. It picks the BIOS for "
|
|
"your game's region.\n\n<details>\n\nThe installer finds your platform.\n")
|
|
text = gs._page_description(body, Path("emulators/duckstation.md"), "DuckStation")
|
|
self.assertTrue(text.startswith("DuckStation emulates Sony PlayStation."))
|
|
self.assertNotIn("source-pinned", text)
|
|
|
|
def test_a_console_page_is_described_by_its_start_line(self) -> None:
|
|
body = "# Sega: Dreamcast\n\nPart of Sega.\n\n**Start with**: `dc_boot.bin`, `dc_flash.bin`.\n"
|
|
text = gs._page_description(body, Path("systems/sega-dreamcast.md"), "Sega: Dreamcast")
|
|
self.assertEqual(text, "Dreamcast BIOS files (Sega). Start with dc_boot.bin, dc_flash.bin.")
|
|
|
|
def test_the_faq_is_described_by_its_questions(self) -> None:
|
|
body = ("# Common questions\n\n## Do I need BIOS files?\n\nYes.\n\n## Which one?\n\n"
|
|
"Run the command with --dry-run.\n\n## Zip or not?\n\nZip.\n")
|
|
text = gs._page_description(body, Path("wiki/faq.md"), "Common questions")
|
|
self.assertTrue(text.startswith("Common questions: Do I need BIOS files?"))
|
|
|
|
def test_a_written_description_wins(self) -> None:
|
|
source = (REPO_ROOT / "scripts" / "generate_site.py").read_text(encoding="utf-8")
|
|
self.assertIn('authored.get("description") or _page_description(', source)
|
|
|
|
def test_the_check_counts_templates(self) -> None:
|
|
pages = {}
|
|
for n in range(12):
|
|
page = validate_site.Page(Path(f"/s/p{n}/index.html"))
|
|
page.h1 = f"Core {n}"
|
|
page.title = f"Core {n} BIOS files - RetroBIOS"
|
|
page.descriptions = [f"Core {n}: emulator firmware profile with paths."]
|
|
pages[page.path] = page
|
|
issues = validate_site._check_description_templates(pages, Path("/s"))
|
|
self.assertEqual(len(issues), 1)
|
|
self.assertIn("12 pages share", issues[0])
|
|
for page in list(pages.values())[:2]:
|
|
page.descriptions = [f"{page.h1} emulates a console of its own {page.h1[-1]}x."]
|
|
self.assertEqual(validate_site._check_description_templates(pages, Path("/s")), [])
|
|
|
|
def test_built_descriptions_are_not_templates(self) -> None:
|
|
if not (DIST / "index.html").is_file():
|
|
self.skipTest("site not built")
|
|
pages = {path.resolve(): validate_site._parse_page(path) for path in DIST.rglob("*.html")}
|
|
content = {path: page for path, page in pages.items() if not page.redirect}
|
|
self.assertEqual(validate_site._check_description_templates(content, DIST.resolve()), [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|