.json had no schema_version, and its "id" beside
the profile's fields failed the schema published next to it."""
def test_shape(self):
try:
import jsonschema # noqa: PLC0415
except ImportError:
self.skipTest("jsonschema is not installed")
import yaml # noqa: PLC0415
root = Path(__file__).resolve().parents[1]
source = (root / "scripts" / "generate_site.py").read_text(encoding="utf-8")
self.assertNotIn('json.dumps({"id": name, **profile}', source)
envelope = json.loads((root / "schemas" / "site-api-envelope.schema.json").read_text())
schema = json.loads((root / "schemas" / "emulator.schema.json").read_text())
profile = yaml.safe_load((root / "emulators" / "handy.yml").read_text())
document = {"schema_version": 1, "generated_at": "2026-10-19T00:00:00Z",
"kind": "emulators", "count": 1,
"items": [{"id": "handy", "profile": profile}]}
jsonschema.validate(document, envelope)
jsonschema.validate(document["items"][0]["profile"], schema)
class AHashFindsEveryPageThatNamesIt(unittest.TestCase):
"""Hashes left the site search, 4 MB of the index every visitor
downloaded. files/?hash= reads one shard instead, and it must answer at
least what the search answered: the card, the platforms, the profiles."""
def test_records_cover_every_layer_and_shard_by_prefix(self):
import json as _json
import tempfile
import generate_site # noqa: PLC0415
sha1 = "ab" * 20
md5 = "490f666e1afb15b7362b406ed1cea246"
db = {"files": {sha1: {"name": "scph5501.bin", "md5": md5, "crc32": "8D8CB7E4",
"path": "bios/Sony/PlayStation/scph5501.bin"}}}
coverages = {"batocera": {"config": {"systems": {"sony-playstation": {"files": [
{"name": "scph5501.bin", "md5": f"{md5}, 0123456789abcdef0123456789abcdef"},
{"name": "typo.bin", "md5": "not-a-hash"},
]}}}}}
profiles = {
"duckstation": {"files": [{"name": "scph5501.bin", "md5": md5},
{"name": "set.zip", "contents": [
{"name": "m.rom", "crc32": "11223344"}]}]},
"an-alias": {"type": "alias", "files": [{"name": "x", "md5": md5}]},
}
locations = generate_site.file_location_items(db) + [
{"sha1": "cd" * 20, "url": "emulators/dolphin/#sha1-" + "cd" * 6}]
members = {"dolphin-sys": {"Sys/GC/font.bin": {"sha1": "cd" * 20}}}
records = generate_site.file_lookup_records(db, coverages, profiles, locations, members)
found = {(r["layer"], r.get("owner", ""), r["algorithm"], r["value"]): r for r in records}
self.assertEqual(found[("collection", "", "md5", md5)]["url"],
"systems/sony/#sha1-" + "ab" * 6)
self.assertIn(("collection", "", "crc32", "8d8cb7e4"), found)
self.assertEqual(found[("data-directory", "", "sha1", "cd" * 20)]["name"], "font.bin")
self.assertEqual(found[("platform", "batocera", "md5", md5)]["url"],
"platforms/batocera/#sony-playstation")
self.assertIn(("platform", "batocera", "md5", "0123456789abcdef0123456789abcdef"), found)
self.assertEqual(found[("emulator", "duckstation", "md5", md5)]["url"],
"emulators/duckstation/#file-scph5501-bin-490f666e")
self.assertEqual(found[("emulator", "duckstation", "crc32", "11223344")]["name"], "m.rom")
self.assertNotIn(("emulator", "an-alias", "md5", md5), found)
with tempfile.TemporaryDirectory(dir=Path(__file__).resolve().parents[1] / "tmp") as tmp:
shards = generate_site.write_lookup_shards(Path(tmp), records, "2026-01-01T00:00:00Z")
folder = Path(tmp) / "lookup"
names = sorted(path.name for path in folder.glob("*.json"))
self.assertEqual(len(names), shards)
for path in folder.glob("*.json"):
document = _json.loads(path.read_text())
self.assertEqual(document["kind"], "file-lookup")
self.assertEqual(document["count"], len(document["items"]))
for item in document["items"]:
self.assertTrue(item["value"].startswith(path.stem))
self.assertNotIn("no.json", names)
class AFileHasAnAddressByContent(unittest.TestCase):
"""mt32_control.rom moved from the Commodore page to the Roland page and
every link to its card broke: the anchor was content-based, the page
was not."""
def test_index_page_and_resolver(self):
import generate_site # noqa: PLC0415
db = {"files": {
"ab" * 20: {"path": "bios/Roland/mt32-roms/mt32_control.rom"},
"cd" * 20: {"path": "data/elsewhere.bin"},
}}
self.assertEqual(
generate_site.file_location_items(db),
[{"sha1": "ab" * 20, "url": "systems/roland/#sha1-" + "ab" * 6}],
)
self.assertIn('id="rb-file-lookup-status"', generate_site.generate_files_page())
script = (Path(__file__).resolve().parents[1] / "website" / "src" / "scripts"
/ "site.ts").read_text()
self.assertIn("api/v1/lookup/", script)
self.assertIn("params.has('sha1')", script)
from cross_reference import FileConsumers # noqa: PLC0415
card = "\n".join(generate_site._system_file_card(
{"name": "x.rom", "sha1": "ab" * 20, "size": 1}, FileConsumers({"files": {}}),
))
self.assertIn("/retrobios/files/?sha1=" + "ab" * 20, card)
class ACardNamesThePacksThatInstallIt(unittest.TestCase):
"""A file card named only the platforms whose own list declares the
file: a RetroBat user found no RetroBat on scph5501.bin, which its pack
installs for six cores."""
def test_a_manifest_destination_reaches_the_card(self):
import json as _json
from cross_reference import FileConsumers
from generate_site import _installed_links
with tempfile.TemporaryDirectory(dir=ROOT / "tmp") as tmp:
Path(tmp, "retrobat.json").write_text(_json.dumps({
"platform": "retrobat",
"files": [{"dest": "scph5501.bin", "sha1": "b" * 40,
"alternatives": [{"sha1": "a" * 40}]}],
}))
consumers = FileConsumers({"files": {}})
consumers.add_manifests(tmp)
links = _installed_links("a" * 40, consumers, set())
self.assertEqual(links, ["[retrobat](../platforms/retrobat.md) `scph5501.bin`"])
self.assertEqual(_installed_links("a" * 40, consumers, {"retrobat"}), [])
class ProseCitationsLinkToThePin(unittest.TestCase):
"""4,328 file:line citations in notes and platform details were plain
text, while profile_sync keeps each one on the profile's pin."""
PROFILE = {"source": "https://github.com/libretro/FreeChaF",
"source_commit": "76c7a84f1f7e80f3e6f2bba96fe100cb24e99124",
"files": [{"name": "x", "source_ref": "libretro.c:10"}]}
def test_a_rooted_or_structured_path_is_linked(self):
from sitesources import link_prose_citations
text = link_prose_citations(
self.PROFILE, "BIOS loading is in src/libretro.c:186-208 and libretro.c:12.")
self.assertIn("[`src/libretro.c:186-208`](https://github.com/libretro/FreeChaF/blob/"
"76c7a84f1f7e80f3e6f2bba96fe100cb24e99124/src/libretro.c#L186-L208)", text)
self.assertIn("[`libretro.c:12`](", text)
def test_what_cannot_be_placed_stays_text(self):
from sitesources import link_prose_citations
self.assertEqual(link_prose_citations(self.PROFILE, "see memory.c:5"), "see memory.c:5")
self.assertEqual(link_prose_citations(self.PROFILE, "in `src/a.c:5`"), "in `src/a.c:5`")
two = {**self.PROFILE, "upstream": "https://github.com/x/y", "upstream_commit": "a" * 40}
self.assertEqual(link_prose_citations(two, "src/a.c:5"), "src/a.c:5")
class ACitationSaysWhoseCodeItIs(unittest.TestCase):
"""Readers could not tell whether the libretro core or the emulator asked
for a file: both trees were cited alike, and left unlinked."""
PROFILE = {"type": "libretro", "source": "https://github.com/libretro/a5200",
"upstream": "https://github.com/alekmaul/a5200",
"source_commit": "a" * 40, "upstream_commit": "b" * 40}
def test_each_part_links_to_its_repository_with_its_label(self):
from sitesources import _source_ref_markdown, requested_by
ref = "source libretro.c:5, upstream emu/memory.c:46, 50-52"
text = _source_ref_markdown(self.PROFILE, ref)
self.assertIn("libretro/a5200/blob/" + "a" * 40 + "/libretro.c#L5) (libretro glue)", text)
self.assertIn("alekmaul/a5200/blob/" + "b" * 40 + "/emu/memory.c#L50-L52) (upstream emulator)",
text)
self.assertEqual(requested_by(self.PROFILE, ref), "libretro glue and upstream emulator")
self.assertEqual(requested_by(self.PROFILE, "source src/mem.c:107"),
"emulator code, libretro port")
self.assertEqual(requested_by(self.PROFILE, "upstream emu/memory.c:46"),
"upstream emulator")
def test_a_role_without_a_pin_links_nowhere_else(self):
from sitesources import _source_ref_markdown
profile = {**self.PROFILE, "upstream_commit": ""}
self.assertEqual(_source_ref_markdown(profile, "upstream emu/memory.c:46"),
"`emu/memory.c:46` (upstream emulator)")
class EveryAcceptedHashIsItsOwnWord(unittest.TestCase):
"""Recalbox md5 lists rendered as one comma-joined code span: the site
search made one token of it and found none but the first."""
def test_a_list_is_split(self):
from generate_site import _hash_spans
md5s = ["96cf" + "0" * 28, "d5af" + "0" * 28]
text = _hash_spans(",".join(md5s))
for md5 in md5s:
self.assertIn(f'{md5}', text)
class AMismatchNamesTheDumpTheCollectionHolds(unittest.TestCase):
"""The gaps table printed the hash an emulator accepts and stopped, while
the collection held that exact dump for every row."""
def test_the_held_copy_is_named(self):
from generate_site import _held_accepted
db = {"files": {"s1": {"path": "bios/Microsoft/MSX/CLK/msx-japanese.rom"}},
"indexes": {"by_sha256": {"ab" * 32: "s1"}, "by_crc32": {"84f68bf9": "s1"}}}
text = ("RetroBat says OK but ares says sha256 mismatch: got " + "cd" * 32
+ ", accepted [" + "ab" * 32 + "]")
self.assertEqual(_held_accepted(text, db), "bios/Microsoft/MSX/CLK/msx-japanese.rom")
self.assertEqual(_held_accepted("xroar says crc32 mismatch: got 1, accepted [84f68bf9]", db),
"bios/Microsoft/MSX/CLK/msx-japanese.rom")
self.assertEqual(_held_accepted("size mismatch: got 1, accepted [256]", db), "")
class ADataDirectoryFileCanBeLookedUp(unittest.TestCase):
"""2,534 hashes the packs ship from data directories were on no page:
the emulator page gave only a URL whose content moves."""
def test_a_member_resolves_to_its_emulator_page(self):
import json as _json
from generate_site import data_directory_members, file_location_items
with tempfile.TemporaryDirectory(dir=ROOT / "tmp") as tmp:
Path(tmp, "retroarch.json").write_text(_json.dumps({"data_directories": [
{"ref": "dolphin-sys", "files": {"GC/font.bin": {"sha1": "c" * 40, "size": 3}}},
]}))
members = data_directory_members(Path(tmp))
items = file_location_items(
{"files": {}}, {"dolphin": {"data_directories": [{"ref": "dolphin-sys"}]}}, members)
self.assertEqual(items, [{"sha1": "c" * 40, "url": "emulators/dolphin/#sha1-cccccccccccc"}])
class ACardSaysWhyAFileIsLeftOut(unittest.TestCase):
"""The badge said "bundled" and nothing about which build, so a reader
could not tell why a RetroArch pack carried PPSSPP's assets."""
def test_the_build_and_the_platforms_are_named(self):
from generate_site import _file_badges
badges = " ".join(_file_badges({"name": "a", "bundled": True, "mode": "standalone"}, True))
self.assertIn("installed with the standalone emulator", badges)
class AnEmulatorPageOffersItsInstall(unittest.TestCase):
"""An emulator page linked each file one by one and offered no way to
install them together."""
def test_the_command_names_the_emulator(self):
from generate_site import _install_block
text = "\n".join(_install_block("pcem", {"type": "libretro"}))
self.assertIn("install.sh | sh -s -- --emulator pcem", text)
self.assertIn("install.ps1))) --emulator pcem", text)
self.assertEqual(_install_block("x", {"type": "alias"}), [])
def test_the_installer_takes_the_option(self):
source = (ROOT / "install.py").read_text(encoding="utf-8")
self.assertIn('"--emulator"', source)
class NestedProfileBlocksRenderAsLists(unittest.TestCase):
"""The clownmdemu page showed "- **external_required:** False" in a code
block: a key written as a paragraph left its children indented eight
spaces inside the admonition, which Markdown reads as code."""
def _html(self, value: object) -> str:
try:
import markdown
except ImportError:
self.skipTest("markdown is not installed")
from siterender import _render_yaml_value
# The page opens the block with ???+ (pymdownx.details). An admonition
# nests its body by the same four spaces, and needs nothing CI lacks.
lines = ['!!! abstract "Analysis"', ""]
_render_yaml_value(lines, value, 4)
return markdown.markdown("\n".join(lines), extensions=["admonition"])
def test_a_nested_mapping_is_a_nested_list(self):
html = self._html({"megacd_bios": {"external_required": False, "source_ref": "a.c:1"},
"tmss": {"supported": True}})
self.assertNotIn("", html)
self.assertNotIn("**", html)
self.assertIn("external required: no", html)
self.assertIn("a.c:1", html)
def test_platform_details_are_a_list(self):
import generate_site
lines = generate_site._render_platform_details(
{"platform_details": {"megacd": {"root": "x"}, "note": "y"}}
)
try:
import markdown
except ImportError:
self.skipTest("markdown is not installed")
# The block's body, read as the engine reads Markdown inside HTML.
body = lines[lines.index("Platform details") + 1:
lines.index("")]
html = markdown.markdown("\n".join(body))
self.assertIn("root: x", html)
self.assertNotIn("**", html)
def test_a_list_under_a_key_is_a_sublist(self):
html = self._html({"roms": [{"name": "a.bin", "size": 2}, "b.bin"]})
self.assertNotIn("", html)
self.assertIn("name: a.bin, size: 2", html)
class TheOneLinerIsWhereUsersLand(unittest.TestCase):
"""The download page had no Android line and no Windows form with
options, and the platform and system pages offered only ZIP parts."""
def test_download_page(self):
import generate_site # noqa: PLC0415
page = generate_site.generate_which_pack()
self.assertIn("Android (Termux", page)
self.assertIn("install.ps1))) --check", page)
self.assertIn("--platform retroarch --dest", page)
def test_platform_and_system_pages(self):
import generate_site # noqa: PLC0415
from cross_reference import FileConsumers # noqa: PLC0415
cov = {"platform": "Batocera", "mode": "md5", "present": 0, "total": 0,
"verified": 0, "missing": 0, "details": [], "config": {"systems": {}}}
page = generate_site.generate_platform_page("batocera", cov)
self.assertIn("install.sh | sh -s -- --platform batocera", page)
self.assertLess(page.index("--platform batocera"), page.index("rb-button"))
# Batocera runs the command on the device itself. A platform detected
# on Windows gets the PowerShell form.
cov = dict(cov, platform="RetroArch", mode="existence")
page = generate_site.generate_platform_page("retroarch", cov)
self.assertIn("install.ps1))) --platform retroarch", page)
self.assertIn("install.sh | sh -s -- --platform retroarch", page)
entry = {"name": "a.bin", "sha1": "a" * 40, "md5": "b" * 32, "size": 3,
"path": "bios/Recalbox/MSX/a.bin"}
page = generate_site.generate_system_page(
"Microsoft", {"MSX": [entry]}, FileConsumers({"files": {}}),
{"MSX": [("recalbox", "msx1,msx2,msxturbor"), ("batocera", "msx1")]})
self.assertIn("--platform recalbox --system msx1,msx2,msxturbor\n", page)
self.assertIn("--platform batocera --system msx1", page)
def test_a_system_is_offered_only_where_the_manifest_records_it(self):
import generate_site # noqa: PLC0415
import siterender # noqa: PLC0415
saved = dict(siterender._system_page_map)
self.addCleanup(siterender.set_system_page_map, saved)
siterender.set_system_page_map({"sony-playstation": ("sony", "PlayStation"),
"nowhere": ("sony", "PlayStation")})
cov = {"retroarch": {"config": {"systems": {"sony-playstation": {}, "nowhere": {}}}}}
choices = generate_site.system_install_choices(cov)
self.assertEqual(choices["sony"]["PlayStation"], [("retroarch", "sony-playstation")])
class TheGapPageOpensOnCounts(unittest.TestCase):
"""The page opened on a slogan and on tiles reading "Verified (100%)"
for 5,437 of 5,438 files and "Missing 0", above a table of red cells."""
def test_every_count_is_said(self):
import generate_site # noqa: PLC0415
text = generate_site._platform_lists_sentence(5438, 5437, 1, 0)
self.assertIn("Of the 5,438 files", text)
self.assertIn("5,437 are held", text)
self.assertIn("1 is held but not verified", text)
self.assertIn("0 are missing", text)
self.assertNotIn("%", text)
source = (Path(__file__).resolve().parents[1] / "scripts" / "generate_site.py").read_text()
self.assertNotIn("Unified view of BIOS verification", source)
class WhichPackDescribesNoPlatformByOpinion(unittest.TestCase):
"""The page called RetroPie the largest community with the most guides,
Batocera an easy setup and Recalbox good for a first build: claims
nothing in the repository measures, ranking platforms against each
other."""
def test_no_ranking_words(self):
import re
import generate_site # noqa: PLC0415
page = generate_site.generate_which_pack()
rows = [line for line in page.splitlines() if line.startswith("| ")]
found = [
row for row in rows
if re.search(r"\b(largest|most|popular|easy|best|classic|lightweight|"
r"one-click|plug-and-play|first build|accuracy-focused)\b",
row, re.IGNORECASE)
]
self.assertEqual(found, [])
class EveryPageLinksItsOwnData(unittest.TestCase):
"""An emulator page linked its YAML and JSON. A platform page linked
nothing, and a tool had to download platforms.json (2.8 MB) and filter
it. A system page linked nothing either."""
def test_platform_and_system_documents(self):
import generate_site # noqa: PLC0415
from cross_reference import FileConsumers # noqa: PLC0415
cov = {"platform": "Batocera", "mode": "md5", "present": 0, "total": 0,
"verified": 0, "missing": 0, "details": [], "config": {"systems": {}}}
page = generate_site.generate_platform_page("batocera", cov)
self.assertIn("[JSON](../api/v1/platforms/batocera.json)", page)
self.assertIn("/blob/main/platforms/batocera.yml)", page)
entry = {"name": "a.bin", "sha1": "a" * 40, "md5": "b" * 32, "size": 3,
"path": "bios/Sony/PlayStation/a.bin"}
page = generate_site.generate_system_page(
"Sony", {"PlayStation": [entry]}, FileConsumers({"files": {}}))
self.assertIn("[JSON](../api/v1/systems/sony.json)", page)
item = generate_site.system_export_item("sony", "Sony", {"PlayStation": [entry]})
self.assertEqual(item["consoles"]["PlayStation"][0]["sha1"], "a" * 40)
with tempfile.TemporaryDirectory(dir=Path(__file__).resolve().parents[1] / "tmp") as tmp:
path = Path(tmp) / "sony.json"
generate_site.write_single_item(path, "systems", item, "2026-10-21T00:00:00Z")
document = json.loads(path.read_text())
self.assertEqual(document["count"], 1)
try:
import jsonschema # noqa: PLC0415
except ImportError:
return
root = Path(__file__).resolve().parents[1]
envelope = json.loads((root / "schemas" / "site-api-envelope.schema.json").read_text())
jsonschema.validate(document, envelope)
if __name__ == "__main__":
unittest.main()