From 22b6567c6508bc4a498328a29374c410d8b513d6 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sat, 24 Oct 2026 00:36:40 +0200 Subject: [PATCH] chore: hold the round 5 wiki fixes in tests --- tests/test_site_r5_wiki.py | 225 +++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 tests/test_site_r5_wiki.py diff --git a/tests/test_site_r5_wiki.py b/tests/test_site_r5_wiki.py new file mode 100644 index 00000000..542f9bdc --- /dev/null +++ b/tests/test_site_r5_wiki.py @@ -0,0 +1,225 @@ +"""Wiki (round 5, package 5). + +Each class names the finding it holds: a "no setup" command that sent +macOS and Steam Deck files where RetroArch never reads, a level 0 rule that +every emulator needs one exact name, FAQ answers that ended at a ZIP most +setups lack, a packager section pointing at release assets that do not +exist, and two copy fixes. Every claim is held against install.py, the +setup guides or the generator that writes the page a link opens. +""" + +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)) +sys.path.insert(0, str(REPO_ROOT / "scripts")) + +import sitediagrams # noqa: E402 +from siteguides import INSTALL_BASE # noqa: E402 +from tests import sitepages # noqa: E402 + +WIKI = REPO_ROOT / "wiki" +FENCED = re.compile(r"^```.*?^```", re.S | re.M) +INSTALL_PY = (REPO_ROOT / "install.py").read_text(encoding="utf-8") + + +def _wiki(name: str) -> str: + return (WIKI / name).read_text(encoding="utf-8") + + +def _front(text: str) -> dict: + return yaml.safe_load(text.split("---", 2)[1]) + + +def _section(text: str, heading: str) -> str: + start = re.search(rf"(?m)^#+ {re.escape(heading)}(?:\s*\{{[^}}]*\}})?\n", text) + if start is None: + raise AssertionError(f"no section {heading!r}") + rest = text[start.end():] + blank = FENCED.sub(lambda m: re.sub(r"[^\n]", " ", m.group(0)), rest) + end = re.search(r"(?m)^#{1,2} ", blank) + return rest[: end.start()] if end else rest + + +def _flat(text: str) -> str: + return " ".join(text.split()) + + +def _guide_folders(name: str) -> dict[str, str]: + guides = yaml.safe_load((REPO_ROOT / "platforms" / "_guides.yml").read_text(encoding="utf-8")) + return {row["where"]: row["path"] for row in guides["guides"][name]["folders"]} + + +class NoSetupFoundGivesEachSystemItsFolder(unittest.TestCase): + """C3: macOS, Linux and Steam Deck shared `~/.config/retroarch/system`.""" + + def setUp(self): + self.section = _section(_wiki("troubleshooting.md"), "If the command fails") + + def test_the_label_is_the_installer_message(self): + self.assertIn("**`No supported platform detected`:**", self.section) + self.assertIn('print(" No supported platform detected.")', INSTALL_PY) + + def test_the_lead_names_the_menu(self): + self.assertIn("In RetroArch, open **Settings > Directory > System/BIOS**", + _flat(self.section)) + + def test_each_tab_carries_the_folder_retroarch_reads_there(self): + expected = { + # install.py's Windows default and the hint it prints. + "Windows": "C:\\RetroArch-Win64\\system", + "macOS": "~/Documents/RetroArch/system", + "Linux": "~/.config/retroarch/system", + "Steam Deck": "~/.var/app/org.libretro.RetroArch/config/retroarch/system", + } + self.assertIn('_host_path("C:/RetroArch-Win64/system")', INSTALL_PY) + self.assertIn('"windows": "C:\\\\RetroArch-Win64\\\\system"', INSTALL_PY) + self.assertIn('return Path.home() / "Documents" / "RetroArch"', INSTALL_PY) + folders = _guide_folders("retroarch") + self.assertEqual(folders["macOS"], f"`{expected['macOS']}/`") + self.assertEqual(folders["Linux"], f"`{expected['Linux']}/`") + self.assertEqual(folders["Steam Deck and Linux Flatpak"], f"`{expected['Steam Deck']}/`") + + tabs = sitediagrams.expand_commands(self.section) + parts = re.split(r'', tabs) + bodies = dict(zip(parts[1::2], parts[2::2])) + self.assertEqual(set(bodies), set(expected)) + for label, folder in expected.items(): + with self.subTest(tab=label): + self.assertIn(f"--platform retroarch --dest {folder}\n", bodies[label]) + others = {f for key, f in expected.items() if key != label} + for other in others: + self.assertNotIn(f"--dest {other}\n", bodies[label]) + + def test_the_built_page_shows_four_folders(self): + sitepages.require() + built = sitepages.page("wiki/troubleshooting.md").read_text(encoding="utf-8") + self.assertNotIn("os-tabs", built) + for folder in ("C:\\RetroArch-Win64\\system", "~/Documents/RetroArch/system", + "~/.config/retroarch/system", + "~/.var/app/org.libretro.RetroArch/config/retroarch/system"): + with self.subTest(folder=folder): + self.assertIn(f"--dest {folder}", built) + + +class OsTabsTakeOptionsPerSystem(unittest.TestCase): + """The marker gives any tab its own options, and refuses words it cannot read.""" + + PAGE = ("\n" + f"```sh\ncurl -fsSL {INSTALL_BASE}/install.sh | sh -s -- --dest /l\n```\n") + + def test_each_named_tab_uses_its_own(self): + parts = re.split(r'', sitediagrams.expand_commands(self.PAGE)) + bodies = dict(zip(parts[1::2], parts[2::2])) + self.assertIn("--dest E:\\x\n", bodies["Windows"]) + self.assertIn("--dest /m\n", bodies["macOS"]) + self.assertIn("--dest /l\n", bodies["Linux"]) + self.assertIn("--dest /d\n", bodies["Steam Deck"]) + + def test_text_before_a_system_name_fails_the_build(self): + page = self.PAGE.replace("os-tabs windows:", "os-tabs typo windows:") + with self.assertRaises(ValueError): + sitediagrams.expand_commands(page) + + +class MostEmulatorsNotEvery(unittest.TestCase): + """C8: level 0 said every emulator needs one exact name.""" + + def test_the_rule_names_its_exception(self): + section = _flat(_section(_wiki("what-are-bios-files.md"), "The name must match")) + self.assertIn("Most emulators look for one exact name, like `scph5501.bin`.", section) + self.assertIn("A few, like DuckStation, read any name and check what the file holds.", + section) + profile = yaml.safe_load((REPO_ROOT / "emulators" / "duckstation.yml").read_text( + encoding="utf-8")) + self.assertEqual(profile["found_by"], "content") + + +class FaqAnswersLeadSomewhere(unittest.TestCase): + """C9: "Use the ZIP" for iOS, and a dead end for 12 setups without one.""" + + def setUp(self): + self.text = _wiki("faq.md") + self.generator = (REPO_ROOT / "scripts" / "generate_site.py").read_text(encoding="utf-8") + + def test_ios_goes_to_the_iphone_section(self): + section = _flat(_section(self.text, "My setup is not in the list")) + self.assertIn("iPhone, iPad (iOS): no terminal. [Pick your consoles and copy the files " + "to the app](../which-pack.md#iphone-and-ipad).", section) + self.assertNotIn("Use the ZIP", section) + self.assertIn('"## iPhone and iPad { #iphone-and-ipad }"', self.generator) + + def test_antivirus_answer_covers_setups_without_a_zip(self): + section = _flat(_section(self.text, "Windows or my antivirus blocks the command")) + self.assertIn("No ZIP for your setup? [Pick your consoles]" + "(../which-pack.md#only-some-consoles) and download their files.", + section) + self.assertNotIn("if any", section) + self.assertIn('"## Only some consoles?"', self.generator) + + +class PackagersPinACommit(unittest.TestCase): + """J4: the section fetched install.py from a release that carries none.""" + + def setUp(self): + self.section = _section(_wiki("installer.md"), "For packagers") + + def test_the_pin_read_is_the_one_install_sh_writes(self): + sed = re.search(r"sed -n '(s/\^DEFAULT_INSTALL_SHA256=.*?/p)'", self.section) + self.assertIsNotNone(sed) + bootstrap = (REPO_ROOT / "install.sh").read_text(encoding="utf-8") + lines = re.findall(r'(?m)^DEFAULT_INSTALL_SHA256="([0-9a-f]{64})"$', bootstrap) + self.assertEqual(len(lines), 1) + + def test_the_manifest_comes_from_the_same_commit(self): + self.assertIn('-O "$base/install/retroarch.json"', self.section) + self.assertTrue((REPO_ROOT / "install" / "retroarch.json").is_file()) + self.assertIn("RETROBIOS_REF=$commit python3 install.py --manifest retroarch.json", + self.section) + self.assertIn('"--manifest",', INSTALL_PY) + self.assertIn('"https://raw.githubusercontent.com/Abdess/retrobios/"', INSTALL_PY) + + def test_search_words(self): + terms = _front(_wiki("installer.md"))["terms"] + for word in ("packager", "pin", "reproducible"): + with self.subTest(word=word): + self.assertIn(word, terms) + + +class SearchWords(unittest.TestCase): + """Contract with the search gate: each page carries its own words.""" + + def test_troubleshooting(self): + terms = _front(_wiki("troubleshooting.md"))["terms"] + for word in ("check my bios folder", "check bios folder"): + with self.subTest(word=word): + self.assertIn(word, terms) + + def test_legal(self): + self.assertIn("license", _front(_wiki("legal.md"))["terms"]) + + +class SmallCopyFixes(unittest.TestCase): + """C-other: the RetroPie share had no name, and Mega CD two spellings.""" + + def test_retropie_share_is_named_with_its_source(self): + section = _section(_wiki("first-install.md"), "Error: Python 3.8 or newer is required.") + self.assertIn("From\nWindows, it is `\\\\RETROPIE\\bios`:", section) + self.assertIn("samba.sh:49-52", section) + self.assertIn("Transferring-Roms.md:29", section) + + def test_mega_cd_is_spelled_once(self): + for path in sorted(WIKI.glob("*.md")): + with self.subTest(page=path.name): + self.assertNotIn("Mega-CD", path.read_text(encoding="utf-8")) + + +if __name__ == "__main__": + unittest.main()