mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-23 20:29:06 -05:00
chore: add round 3 wiki copy tests
This commit is contained in:
1 parent
097d453868
commit
66426f2ab5
2 files changed
+303
-1
No files matched your search
@@ -0,0 +1,302 @@
|
||||
"""Wiki copy (round 3, package 5).
|
||||
|
||||
Each class names the finding it holds: a legal page that opened on case law,
|
||||
no arcade answer where players look, a Dreamcast "yes" the Dreamcast
|
||||
emulator contradicts, an offline install no one could find, setups with no
|
||||
page of their own, link labels that named another page, an exit status that
|
||||
could not tell missing files from a failure, and FAQ answers the community
|
||||
asks for (region and language, keys and firmware). Every claim the copy
|
||||
makes about an emulator or the installer is held against the repository.
|
||||
"""
|
||||
|
||||
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))
|
||||
|
||||
from tests import sitepages # noqa: E402
|
||||
|
||||
WIKI = REPO_ROOT / "wiki"
|
||||
FENCED = re.compile(r"^```.*?^```", re.S | re.M)
|
||||
|
||||
|
||||
def _wiki(name: str) -> str:
|
||||
return (WIKI / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
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():]
|
||||
end = re.search(r"(?m)^#{1,2} ", FENCED.sub(lambda m: re.sub(r"[^\n]", " ", m.group(0)), rest))
|
||||
return rest[: end.start()] if end else rest
|
||||
|
||||
|
||||
def _flat(text: str) -> str:
|
||||
return " ".join(text.split())
|
||||
|
||||
|
||||
def _headings(text: str) -> list[str]:
|
||||
return re.findall(r"(?m)^#{2,3} (.+?)\s*$", FENCED.sub("", text))
|
||||
|
||||
|
||||
def _profile(name: str) -> dict:
|
||||
return yaml.safe_load((REPO_ROOT / "emulators" / f"{name}.yml").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _entry(profile: dict, name: str) -> dict:
|
||||
for entry in profile.get("files", []):
|
||||
if entry.get("name") == name:
|
||||
return entry
|
||||
raise AssertionError(f"{name} not in the profile")
|
||||
|
||||
|
||||
class LegalOpensInPlainWords(unittest.TestCase):
|
||||
"""C5: Is it legal? is a Start here page and opened on case law."""
|
||||
|
||||
def setUp(self):
|
||||
self.text = _wiki("legal.md")
|
||||
self.opening = self.text.split("## The reasoning in detail", 1)[0]
|
||||
|
||||
def test_the_answer_comes_before_the_reasoning(self):
|
||||
self.assertIn("## The reasoning in detail", self.text)
|
||||
self.assertIn("It depends on your country. Nothing here is legal advice.", self.opening)
|
||||
for case in ("Connectix", "Accolade", "DMCA", "CFR", "Chamberlain", "Yuzu"):
|
||||
with self.subTest(case=case):
|
||||
self.assertNotIn(case, self.opening)
|
||||
self.assertIn(case, self.text)
|
||||
|
||||
def test_the_plain_lines_hold_against_the_detail(self):
|
||||
opening = _flat(self.opening)
|
||||
self.assertIn("encryption keys (Switch, 3DS, Wii U)", opening)
|
||||
self.assertIn("weakest part of the project's position", self.text)
|
||||
self.assertIn("issues/new", opening)
|
||||
self.assertIn("path or hash", opening)
|
||||
self.assertIn("identifying the file by path or hash", _flat(self.text))
|
||||
|
||||
def test_the_detail_sits_under_the_reasoning_heading(self):
|
||||
detail = self.text.split("## The reasoning in detail", 1)[1]
|
||||
detail = detail.split("## Asking for a file to be removed", 1)[0]
|
||||
self.assertEqual(re.findall(r"(?m)^## ", detail), [])
|
||||
self.assertIn("### Emulation and BIOS redistribution", detail)
|
||||
|
||||
def test_a_contact_section_names_only_a_channel_that_exists(self):
|
||||
section = _section(self.text, "Contact")
|
||||
self.assertIn("https://github.com/Abdess/retrobios/issues", section)
|
||||
self.assertNotIn("@", section)
|
||||
|
||||
|
||||
class ArcadeAnswerWherePlayersLook(unittest.TestCase):
|
||||
"""C3, N3: the romset answer sat in Reference only."""
|
||||
|
||||
def test_troubleshooting_answers_after_step_three(self):
|
||||
text = _wiki("troubleshooting.md")
|
||||
headings = _headings(text)
|
||||
self.assertIn("An arcade game does not start", headings)
|
||||
self.assertGreater(headings.index("An arcade game does not start"),
|
||||
headings.index("3. Check the folder and the names"))
|
||||
section = _flat(_section(text, "An arcade game does not start"))
|
||||
for words in ("neogeo.zip", "same emulator version", "MAME version", "Details:",
|
||||
"../emulators/index.md", "Run the command once."):
|
||||
with self.subTest(words=words):
|
||||
self.assertIn(words, section)
|
||||
|
||||
def test_the_faq_links_the_technical_answer(self):
|
||||
section = _section(_wiki("faq.md"), "My arcade game does not start")
|
||||
anchor = "technical-faq.md#which-mame-version-do-the-arcade-bios-sets-match"
|
||||
self.assertIn(anchor, section)
|
||||
self.assertIn("## Which MAME version do the arcade BIOS sets match?", _wiki("technical-faq.md"))
|
||||
self.assertLessEqual(len(re.findall(r"[.!?](?:\s|$)", section)), 2)
|
||||
|
||||
def test_emulator_pages_name_the_version_the_copy_promises(self):
|
||||
for name in ("mame2003", "mame2000", "fbneo"):
|
||||
with self.subTest(profile=name):
|
||||
profile = _profile(name)
|
||||
self.assertTrue(profile.get("core_version") or profile.get("version"))
|
||||
|
||||
def test_the_check_step_holds_the_searched_words(self):
|
||||
self.assertIn("## 1. Check my BIOS folder { #checking-what-you-installed }",
|
||||
_wiki("troubleshooting.md"))
|
||||
|
||||
|
||||
class BiosFineWhatNext(unittest.TestCase):
|
||||
"""Community need 11: the page assumed the BIOS was the cause."""
|
||||
|
||||
def setUp(self):
|
||||
self.section = _flat(_section(_wiki("troubleshooting.md"),
|
||||
"The BIOS is fine, the game still fails"))
|
||||
|
||||
def test_the_three_causes(self):
|
||||
for words in ("Another emulator?", "A BIOS setting?", ".cue", ".m3u"):
|
||||
with self.subTest(words=words):
|
||||
self.assertIn(words, self.section)
|
||||
|
||||
def test_the_named_setting_is_the_core_option(self):
|
||||
# libretro/pcsx_rearmed frontend/libretro_core_options.h: key
|
||||
# pcsx_rearmed_bios, label "BIOS Selection", values Auto and HLE.
|
||||
self.assertIn("**BIOS Selection** to **Auto**", self.section)
|
||||
notes = _profile("pcsx_rearmed").get("notes", "")
|
||||
self.assertIn('pcsx_rearmed_bios = "HLE"', notes)
|
||||
|
||||
|
||||
class DreamcastIsBetterWithIt(unittest.TestCase):
|
||||
"""C7: "Yes for Dreamcast" while Flycast runs without dc_boot.bin."""
|
||||
|
||||
def test_flycast_falls_back(self):
|
||||
entry = _entry(_profile("flycast"), "dc_boot.bin")
|
||||
self.assertFalse(entry.get("required"))
|
||||
self.assertTrue(entry.get("hle_fallback"))
|
||||
|
||||
def test_no_page_says_yes_for_dreamcast(self):
|
||||
for name in ("faq.md", "what-are-bios-files.md"):
|
||||
text = _wiki(name)
|
||||
with self.subTest(page=name):
|
||||
yes = next(line for line in text.splitlines() if line.startswith("- **Yes"))
|
||||
self.assertNotIn("Dreamcast", yes)
|
||||
self.assertRegex(_flat(text), r"\*\*Better with it:?\*\*:? (for )?Dreamcast")
|
||||
|
||||
|
||||
class OfflineInstallIsFindable(unittest.TestCase):
|
||||
"""N7: no page linked the offline install, and search did not find it."""
|
||||
|
||||
ANCHOR = "without-a-network-offline-install"
|
||||
|
||||
def test_the_installer_heading_says_offline(self):
|
||||
text = _wiki("installer.md")
|
||||
self.assertIn(f"## Without a network (offline install) {{ #{self.ANCHOR} }}", text)
|
||||
# Links written before the rename still land.
|
||||
self.assertIn('<span id="without-a-network"></span>', text)
|
||||
self.assertNotIn("](#without-a-network)", text)
|
||||
|
||||
def test_the_faq_answers_next_to_the_sd_card(self):
|
||||
text = _wiki("faq.md")
|
||||
headings = _headings(text)
|
||||
self.assertEqual(headings.index("No internet on the device?"),
|
||||
headings.index("Fill an SD card from a computer?") + 1)
|
||||
section = _section(text, "No internet on the device?")
|
||||
self.assertIn("--from-pack", section)
|
||||
self.assertIn(f"installer.md#{self.ANCHOR}", section)
|
||||
|
||||
def test_from_pack_is_an_installer_option(self):
|
||||
self.assertIn('"--from-pack"', (REPO_ROOT / "install.py").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
class SetupsWithoutAPage(unittest.TestCase):
|
||||
"""N9, J13: ES-DE, LaunchBox and iOS found nothing."""
|
||||
|
||||
def test_the_answer(self):
|
||||
section = _flat(_section(_wiki("faq.md"), "My setup is not in the list"))
|
||||
for words in ("--dest", "ES-DE", "LaunchBox", "iPhone"):
|
||||
with self.subTest(words=words):
|
||||
self.assertIn(words, section)
|
||||
# No folder is written: the copy names none it cannot cite.
|
||||
self.assertNotRegex(section, r"[~/\\][A-Za-z]")
|
||||
|
||||
def test_the_installer_fills_the_emulators_a_launcher_starts(self):
|
||||
installer = _flat(_wiki("installer.md"))
|
||||
self.assertIn("they reference emulators rather than owning a BIOS directory", installer)
|
||||
self.assertIn("the installer fills their own BIOS folders", installer)
|
||||
|
||||
|
||||
class LinkLabelsMatchPages(unittest.TestCase):
|
||||
"""N5: "Your setup's page" landed on a page titled Get your files."""
|
||||
|
||||
def test_links_to_get_your_files_carry_its_name(self):
|
||||
for name in ("first-install.md", "troubleshooting.md", "faq.md"):
|
||||
with self.subTest(page=name):
|
||||
for label in re.findall(r"\[([^\]]+)\]\(\.\./which-pack\.md\)", _wiki(name)):
|
||||
self.assertEqual(label, "Get your files")
|
||||
|
||||
|
||||
class ExitStatusAndPinnedInstaller(unittest.TestCase):
|
||||
"""D10, J12: --check exits 1 for missing files and for failures, and a
|
||||
packager had no fixed installer to point at."""
|
||||
|
||||
def setUp(self):
|
||||
self.text = _wiki("installer.md")
|
||||
self.source = (REPO_ROOT / "install.py").read_text(encoding="utf-8")
|
||||
|
||||
def test_the_check_event_tells_missing_from_failure(self):
|
||||
section = _flat(_section(self.text, "Exit status"))
|
||||
self.assertIn("read the `check` event", section)
|
||||
self.assertIn("`missing` and `wrong_hash` count what is left", section)
|
||||
fields = re.search(r'"check": \(([^)]*)\)', self.source).group(1)
|
||||
for field in ("missing", "wrong_hash"):
|
||||
with self.subTest(field=field):
|
||||
self.assertIn(f'"{field}"', fields)
|
||||
|
||||
def test_a_packager_pins_installer_and_lists_to_one_tag(self):
|
||||
section = _section(self.text, "One line")
|
||||
self.assertIn("/retrobios/<tag>/install.py", section)
|
||||
self.assertIn("RETROBIOS_REF=<tag>", section)
|
||||
self.assertIn('os.environ.get("RETROBIOS_REF"', self.source)
|
||||
|
||||
|
||||
class SmallCopyFixes(unittest.TestCase):
|
||||
"""C P3: the SD card example and the end line of a run."""
|
||||
|
||||
def test_the_sd_card_example_names_its_setup(self):
|
||||
section = _flat(_section(_wiki("faq.md"), "Fill an SD card from a computer?"))
|
||||
self.assertIn("For RetroArch on a Switch card in drive `E:`", section)
|
||||
guides = (REPO_ROOT / "platforms" / "_guides.yml").read_text(encoding="utf-8")
|
||||
self.assertIn("where: Nintendo Switch", guides)
|
||||
self.assertIn("`retroarch/cores/system/` on the SD card", guides)
|
||||
self.assertIn("--dest E:\\retroarch\\cores\\system", section)
|
||||
|
||||
def test_the_end_line_is_the_printed_one(self):
|
||||
section = _section(_wiki("first-install.md"), "5. Read the last line")
|
||||
self.assertIn("`N files installed, M already up to date.`", section)
|
||||
source = (REPO_ROOT / "install.py").read_text(encoding="utf-8")
|
||||
self.assertIn("installed, \"\n f\"{_plural(up_to_date, 'file')} already up to date.", source)
|
||||
|
||||
|
||||
class CommunityAnswers(unittest.TestCase):
|
||||
"""Community needs 6, 10 and 15: region and language, keys versus
|
||||
firmware versus BIOS, zip or unzip."""
|
||||
|
||||
def setUp(self):
|
||||
self.faq = _wiki("faq.md")
|
||||
|
||||
def test_language_comes_from_the_game(self):
|
||||
section = _flat(_section(self.faq, "Does the BIOS set the game's language?"))
|
||||
self.assertIn("comes from the game", section)
|
||||
self.assertIn("Flycast's **Language** option", section)
|
||||
|
||||
def test_keys_firmware_bios(self):
|
||||
section = _flat(_section(self.faq, "Keys, firmware or BIOS?"))
|
||||
for name, profile in (("scph5501.bin", "duckstation"), ("PS3UPDAT.PUP", "rpcs3"),
|
||||
("prod.keys", "eden")):
|
||||
with self.subTest(name=name):
|
||||
self.assertIn(f"`{name}`", section)
|
||||
_entry(_profile(profile), name)
|
||||
self.assertIn("Firmware (system NCAs) must be installed through Eden's UI",
|
||||
_profile("eden")["notes"])
|
||||
|
||||
def test_zip_or_unzip_stays(self):
|
||||
self.assertIn("neogeo.zip", _section(self.faq, "Zip or unzip?"))
|
||||
|
||||
|
||||
class StartPagesStayInBudget(unittest.TestCase):
|
||||
"""The built pages: the new answers fit the level 0 ceilings."""
|
||||
|
||||
def test_ceilings(self):
|
||||
from tests import test_site_copy
|
||||
|
||||
sitepages.require()
|
||||
for name in ("wiki/faq.md", "wiki/troubleshooting.md", "wiki/first-install.md",
|
||||
"wiki/what-are-bios-files.md"):
|
||||
with self.subTest(page=name):
|
||||
self.assertLessEqual(test_site_copy._load(name).words(),
|
||||
test_site_copy.START_PAGES[name])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -54,7 +54,7 @@ Every module in `tests/`, by the area it guards.
|
||||
| Installer and downloads | loopback server | `test_check_release`, `test_download`, `test_install`, `test_install_cores`, `test_install_experience`, `test_install_offline`, `test_install_selection` |
|
||||
| Handheld port | stubbed PortMaster dialogs, fake installer | `test_port` |
|
||||
| Truth and native export | synthetic + cached originals | `test_export_counts`, `test_export_native_layer`, `test_export_report`, `test_exporters`, `test_truth_data_dir_readers`, `test_truth_diff`, `test_truth_layout` |
|
||||
| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_availability`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_picker`, `test_site_platform_hashes`, `test_site_r2_components`, `test_site_r2_css`, `test_site_r2_emulators`, `test_site_r2_entry`, `test_site_r2_gate`, `test_site_r2_nav`, `test_site_r2_p0`, `test_site_r2_reference`, `test_site_r2_systems`, `test_site_r2_wiki`, `test_site_r3_collection`, `test_site_r3_p0`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
|
||||
| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_availability`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_picker`, `test_site_platform_hashes`, `test_site_r2_components`, `test_site_r2_css`, `test_site_r2_emulators`, `test_site_r2_entry`, `test_site_r2_gate`, `test_site_r2_nav`, `test_site_r2_p0`, `test_site_r2_reference`, `test_site_r2_systems`, `test_site_r2_wiki`, `test_site_r3_collection`, `test_site_r3_p0`, `test_site_r3_wiki`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
|
||||
|
||||
## Test architecture
|
||||
|
||||
|
||||
Reference in new issue
Block a user