mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 12:33:53 -06:00
tests/__init__.py keeps a run offline, its scratch under tmp/ and its installer cache its own, but only discover -s tests -t . imported it. discover tests ran with RETROBIOS_OFFLINE unset, scratch on the /tmp tmpfs and the user's real installer cache, the setting in which e2e fixtures had written into the shared .cache/large. Every test module now imports the package first, and a test fails on a module that does not. test_verify_listing.py took its line with the previous commit.
294 lines
13 KiB
Python
294 lines
13 KiB
Python
"""Rom folder parsers of the card OSes and the menu-less frontends, offline.
|
|
|
|
The parsers run on fixture text through a fake Fetcher, so nothing here
|
|
reaches the network. The committed setup files are checked against the
|
|
schema and their own declared sources.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
|
|
|
|
import json
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT / "scripts"))
|
|
|
|
from romfolders import split_refs # noqa: E402
|
|
from safeparse import yaml_load # noqa: E402
|
|
from scraper.romfolders import SourceError, load_sources, scrape_setup # noqa: E402
|
|
from scraper.romfolders.__main__ import dump_setup # noqa: E402
|
|
from scraper.romfolders.card import ( # noqa: E402
|
|
libretro_core,
|
|
parse_allium,
|
|
parse_bizhawk,
|
|
parse_crossmix,
|
|
parse_gmenu2x,
|
|
parse_koriki,
|
|
parse_minui,
|
|
parse_mister,
|
|
parse_muos,
|
|
parse_onion,
|
|
parse_plumos,
|
|
parse_romm,
|
|
parse_spruce,
|
|
parse_toml_subset,
|
|
ref,
|
|
)
|
|
|
|
FIXTURES = json.loads(
|
|
(ROOT / "tests" / "fixtures" / "romfolders_card" / "files.json").read_text(encoding="utf-8")
|
|
)
|
|
SETUPS = (
|
|
"onion", "allium", "spruceos", "miyoocfw", "koriki", "crossmix-os", "minui",
|
|
"myminui", "nextui", "plumos-pixel2", "plumos-mf", "muos", "misterfpga", "romm",
|
|
"bizhawk", "lakka", "retroarch",
|
|
)
|
|
|
|
|
|
class FakeFetcher:
|
|
"""Serves fixture files by source key, the Fetcher interface parsers use."""
|
|
|
|
def __init__(self, files: dict[str, dict[str, str]]) -> None:
|
|
self.files = files
|
|
|
|
def text(self, key: str, path: str) -> str:
|
|
try:
|
|
return self.files[key][path]
|
|
except KeyError as exc:
|
|
raise SourceError(f"{key}:{path} does not exist") from exc
|
|
|
|
def optional_text(self, key: str, path: str) -> str | None:
|
|
return self.files.get(key, {}).get(path)
|
|
|
|
def tree(self, key: str, prefix: str = "") -> list[str]:
|
|
return sorted(p for p in self.files.get(key, {}) if p.startswith(prefix))
|
|
|
|
|
|
def spec(source: str, **params: object) -> dict:
|
|
return {"params": {"source": source, **params}}
|
|
|
|
|
|
def by_folder(rows: list[dict]) -> dict[str, dict]:
|
|
return {row["folder"]: row for row in rows}
|
|
|
|
|
|
class LaunchScriptTests(unittest.TestCase):
|
|
def test_core_named_through_a_shell_variable(self) -> None:
|
|
script = "default_core=gpsp\nretroarch -L cores/${default_core}_libretro.so\n"
|
|
self.assertEqual(libretro_core(script), "gpsp")
|
|
|
|
def test_core_named_by_emu_exe(self) -> None:
|
|
self.assertEqual(libretro_core("EMU_EXE=fceumm\nminarch.elf x\n"), "fceumm")
|
|
|
|
def test_standalone_script_names_no_core(self) -> None:
|
|
self.assertIsNone(libretro_core("./drastic \"$1\"\n"))
|
|
|
|
def test_space_in_a_cited_path_is_escaped(self) -> None:
|
|
part = ref("os", "Emu/Commodore - Amiga/config.json", 3)
|
|
self.assertEqual(split_refs(part), [("os", "Emu/Commodore%20-%20Amiga/config.json", "3")])
|
|
|
|
|
|
class ParserTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.fetcher = FakeFetcher(FIXTURES)
|
|
|
|
def test_onion_emu_package_is_the_default_over_retroarch_section(self) -> None:
|
|
rows = by_folder(parse_onion({"params": {"source": "onion"}}, self.fetcher))
|
|
self.assertEqual(sorted(rows), ["GBA", "PCNINETYEIGHT"])
|
|
self.assertEqual(rows["GBA"]["core"], "gpsp")
|
|
self.assertEqual(rows["GBA"]["extensions"], [".gba", ".zip", ".7z"])
|
|
self.assertIn("Nintendo - GBA (VBA-M)", rows["GBA"]["note"])
|
|
self.assertNotIn("core", rows["PCNINETYEIGHT"])
|
|
|
|
def test_spruce_reads_default_and_alternative_folders(self) -> None:
|
|
rows = by_folder(parse_spruce(spec("spruce", skip=["-CUSTOM-SYSTEM-"]), self.fetcher))
|
|
self.assertEqual(list(rows), ["PS"])
|
|
self.assertEqual(rows["PS"]["aliases"], ["psx", "PS1"])
|
|
self.assertEqual(rows["PS"]["core"], "pcsx_rearmed")
|
|
self.assertEqual(rows["PS"]["extensions"], [".bin", ".cue", ".chd"])
|
|
self.assertIn("neogeo.zip", rows["PS"]["note"])
|
|
|
|
def test_crossmix_skips_separators_and_underscore_folders(self) -> None:
|
|
rows = by_folder(parse_crossmix(spec("crossmix"), self.fetcher))
|
|
self.assertEqual(list(rows), ["PS"])
|
|
self.assertEqual(rows["PS"]["core"], "pcsx_rearmed")
|
|
|
|
def test_koriki_first_launcher_wins(self) -> None:
|
|
params = {"source": "koriki", "sections": "base/.simplemenu/section_groups/apps and games.ini",
|
|
"launchers": "base/.simplemenu/launchers", "roms_prefix": "/mnt/SDCARD/Roms/",
|
|
"skip": ["CONSOLES"]}
|
|
rows = by_folder(parse_koriki({"params": params}, self.fetcher))
|
|
self.assertEqual(rows["PS"]["core"], "pcsx_rearmed")
|
|
self.assertEqual(rows["PS"]["extensions"], [".cue", ".chd", ".pbp"])
|
|
self.assertEqual(rows["PS"]["source_ref"],
|
|
"koriki base/.simplemenu/section_groups/apps%20and%20games.ini:4")
|
|
|
|
def test_allium_folder_is_the_first_pattern(self) -> None:
|
|
rows = by_folder(parse_allium(spec("allium", consoles="consoles.toml", cores="cores.toml"),
|
|
self.fetcher))
|
|
self.assertEqual(sorted(rows), ["ARCADE", "GBA"])
|
|
self.assertEqual(rows["ARCADE"]["core"], "mame2003_plus")
|
|
self.assertEqual(rows["ARCADE"]["aliases"], ["mame"])
|
|
self.assertEqual(rows["ARCADE"]["source_ref"], "allium consoles.toml:1")
|
|
|
|
def test_toml_subset_matches_tomllib(self) -> None:
|
|
try:
|
|
import tomllib
|
|
except ImportError:
|
|
self.skipTest("tomllib needs Python 3.11")
|
|
def clean(node: object) -> object:
|
|
if isinstance(node, dict):
|
|
return {k: clean(v) for k, v in node.items() if k != "__line__"}
|
|
if isinstance(node, list):
|
|
return [clean(v) for v in node]
|
|
return node
|
|
|
|
for name in ("consoles.toml", "cores.toml"):
|
|
text = FIXTURES["allium"][name]
|
|
self.assertEqual(clean(parse_toml_subset(text)), tomllib.loads(text))
|
|
|
|
def test_toml_outside_the_subset_is_refused(self) -> None:
|
|
with self.assertRaises(SourceError):
|
|
parse_toml_subset("when = 1979-05-27T07:32:00Z\n")
|
|
|
|
def test_minui_takes_the_core_most_devices_run(self) -> None:
|
|
rows = by_folder(parse_minui(spec("minui"), self.fetcher))
|
|
ps = rows["Sony PlayStation (PS)"]
|
|
self.assertEqual(ps["name"], "PS")
|
|
self.assertEqual(ps["core"], "pcsx_rearmed")
|
|
self.assertEqual(ps["board_defaults"]["tg5040"]["core"], "swanstation")
|
|
self.assertNotIn("rg35xx", ps["board_defaults"])
|
|
self.assertEqual(rows["A2600"]["core"], "stella2014")
|
|
self.assertNotIn("extensions", ps)
|
|
|
|
def test_plumos_shared_folder_goes_to_the_first_system(self) -> None:
|
|
rows = by_folder(parse_plumos(spec("plumos", path="systems.json"), self.fetcher))
|
|
self.assertEqual(rows["FC"]["name"], "nes")
|
|
self.assertEqual(rows["FC"]["core"], "quicknes")
|
|
self.assertEqual(rows["fds"]["emulator"], "standalone")
|
|
self.assertIn("FC", rows["fds"]["aliases"])
|
|
self.assertIn("disabled by default", rows["fds"]["note"])
|
|
|
|
def test_muos_folder_is_the_friendly_name_of_the_catalogue(self) -> None:
|
|
rows = by_folder(parse_muos(spec("muos", path="core.json"), self.fetcher))
|
|
self.assertEqual(rows["sonyplaystation"]["core"], "pcsx_rearmed")
|
|
self.assertEqual(rows["sonyplaystation"]["emulator"], "retroarch")
|
|
self.assertEqual(rows["nintendods"]["core"], "drastic")
|
|
self.assertEqual(rows["nintendods"]["emulator"], "external")
|
|
|
|
def test_gmenu2x_link_and_shipped_folders(self) -> None:
|
|
params = {"source": "gmenu2x", "shipped_roms": "board/miyoo/roms",
|
|
"link_roms_prefix": "/mnt/roms",
|
|
"links": [{"source": "gpsp", "path": "miyoo/opkg_assets/gpsp.lnk", "core": "gpsp"}]}
|
|
rows = by_folder(parse_gmenu2x({"params": params}, self.fetcher))
|
|
self.assertEqual(sorted(rows), ["GB", "GBA"])
|
|
self.assertEqual(rows["GBA"]["extensions"], [".gba", ".zip"])
|
|
self.assertEqual(rows["GBA"]["source_ref"], "gpsp miyoo/opkg_assets/gpsp.lnk:3")
|
|
self.assertNotIn("core", rows["GB"])
|
|
|
|
def test_mister_rows_are_the_shipped_core_folders(self) -> None:
|
|
rows = by_folder(parse_mister(spec("dist", arcade=["mame"], arcade_note="mra"), self.fetcher))
|
|
self.assertEqual(sorted(rows), ["SNES", "mame"])
|
|
self.assertTrue(rows["mame"]["arcade"])
|
|
self.assertIsNone(rows["SNES"]["arcade"])
|
|
|
|
def test_romm_reads_multiline_arrays_and_bios_only_slugs(self) -> None:
|
|
rows = by_folder(parse_romm(spec("romm", cores_map="index.ts",
|
|
bios_fixture="known_bios_files.json"), self.fetcher))
|
|
self.assertEqual(sorted(rows), ["3do", "3ds", "64dd", "arcade"])
|
|
self.assertEqual(rows["arcade"]["core"], "mame2003_plus")
|
|
self.assertIn("nightly", rows["3ds"]["note"])
|
|
self.assertNotIn("core", rows["64dd"])
|
|
|
|
def test_bizhawk_combined_systems_and_extension_lists(self) -> None:
|
|
rows = by_folder(parse_bizhawk(spec("bizhawk", paths="PathEntryCollection.cs",
|
|
rom_loader="RomLoader.cs", config="Config.cs"),
|
|
self.fetcher))
|
|
self.assertEqual(rows["Gameboy"]["name"], "GB")
|
|
self.assertEqual(rows["Gameboy"]["aliases"], ["GBC", "SGB"])
|
|
self.assertEqual(rows["Gameboy"]["extensions"], [".gb", ".gbc", ".sgb"])
|
|
self.assertEqual(rows["NES"]["extensions"], [".nes", ".fds", ".unf"])
|
|
self.assertIn("QuickNes", rows["NES"]["note"])
|
|
self.assertEqual(rows["32X"]["extensions"], [])
|
|
self.assertNotIn("core", rows["NES"])
|
|
|
|
|
|
class ScrapeTests(unittest.TestCase):
|
|
def test_two_scrapes_write_the_same_bytes(self) -> None:
|
|
setup = {
|
|
"name": "Fixture", "platform": "retroarch", "format": "plumos",
|
|
"params": {"source": "plumos", "path": "systems.json"},
|
|
"sources": {"plumos": {"repo": "https://github.com/a/b", "commit": "0" * 40}},
|
|
"roms_root": {"path": "/r", "from_bios": "../r", "source_ref": "plumos systems.json:1"},
|
|
"case_sensitive": None,
|
|
}
|
|
first = dump_setup(scrape_setup("fixture", setup, FakeFetcher(FIXTURES)))
|
|
second = dump_setup(scrape_setup("fixture", setup, FakeFetcher(FIXTURES)))
|
|
self.assertEqual(first, second)
|
|
self.assertEqual(yaml_load(first)["folders"][0]["folder"], "FC")
|
|
|
|
def test_an_empty_read_fails_unless_allowed(self) -> None:
|
|
setup = {"format": "mister", "params": {"source": "none", "arcade_note": "x"}}
|
|
with self.assertRaises(SourceError):
|
|
scrape_setup("empty", setup, FakeFetcher({}))
|
|
|
|
|
|
class CommittedSetupTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.sources = load_sources(ROOT / "platforms" / "romfolders" / "_sources")
|
|
|
|
def documents(self) -> dict[str, dict]:
|
|
out = {}
|
|
for setup in SETUPS:
|
|
path = ROOT / "platforms" / "romfolders" / f"{setup}.yml"
|
|
with path.open(encoding="utf-8") as handle:
|
|
out[setup] = yaml_load(handle)
|
|
return out
|
|
|
|
def test_every_owned_setup_has_sources_and_output(self) -> None:
|
|
for setup in SETUPS:
|
|
self.assertIn(setup, self.sources, setup)
|
|
for setup, doc in self.documents().items():
|
|
self.assertEqual(doc["setup"], setup)
|
|
self.assertEqual(doc["platform"], self.sources[setup]["platform"])
|
|
|
|
def test_every_ref_names_a_declared_source(self) -> None:
|
|
for setup, doc in self.documents().items():
|
|
declared = set(doc["sources"])
|
|
refs = [doc["roms_root"]["source_ref"]]
|
|
refs += [doc["case_ref"]] if doc.get("case_ref") else []
|
|
for block in ("skip", "hidden", "media"):
|
|
refs += [r["source_ref"] for r in doc.get(block, [])]
|
|
refs += [row["source_ref"] for row in doc["folders"]]
|
|
refs += [b["source_ref"] for row in doc["folders"]
|
|
for b in (row.get("board_defaults") or {}).values()]
|
|
for source_ref in refs:
|
|
for key, _, _ in split_refs(source_ref):
|
|
self.assertIn(key, declared, f"{setup}: {source_ref}")
|
|
|
|
def test_committed_setups_validate_against_the_schema(self) -> None:
|
|
try:
|
|
from jsonschema import Draft202012Validator
|
|
except ImportError:
|
|
self.skipTest("jsonschema not installed")
|
|
schema = json.loads((ROOT / "schemas" / "romfolders.schema.json").read_text(encoding="utf-8"))
|
|
validator = Draft202012Validator(schema)
|
|
for setup, doc in self.documents().items():
|
|
errors = [e.message for e in validator.iter_errors(doc)]
|
|
self.assertEqual(errors, [], setup)
|
|
|
|
def test_rows_with_folders_are_not_empty(self) -> None:
|
|
for setup, doc in self.documents().items():
|
|
if self.sources[setup].get("allow_empty"):
|
|
self.assertEqual(doc["folders"], [], setup)
|
|
else:
|
|
self.assertGreater(len(doc["folders"]), 0, setup)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|