mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 21:43:23 -05:00
A profile whose builds live in separate repositories keys source, upstream and source_commit by build mode. The site flattened the URLs but read a single scalar revision, so a libretro fork could be pinned to the standalone commit, and binding the object form into SQLite failed outright once ymir adopted it. URL and revision are now read as pairs. The site also published a sitemap nothing pointed at, so robots.txt is generated alongside it.
262 lines
9.7 KiB
Python
262 lines
9.7 KiB
Python
"""Regression tests for the public website data and citation contracts."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import sqlite3
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT / "scripts"))
|
|
|
|
from generate_site import ( # noqa: E402
|
|
_admonition_body,
|
|
_browser_title,
|
|
_forge_sources,
|
|
_source_ref_markdown,
|
|
decorate_markdown_pages,
|
|
generate_data_exports,
|
|
)
|
|
|
|
|
|
class SiteReferenceContracts(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
scratch = ROOT / "tmp" / "tests"
|
|
scratch.mkdir(parents=True, exist_ok=True)
|
|
self.temp = tempfile.TemporaryDirectory(dir=scratch)
|
|
self.docs = Path(self.temp.name) / "docs"
|
|
self.docs.mkdir()
|
|
|
|
def tearDown(self) -> None:
|
|
self.temp.cleanup()
|
|
|
|
@staticmethod
|
|
def _fixtures() -> tuple[dict, dict, dict, dict]:
|
|
payload = b"firmware"
|
|
sha1 = hashlib.sha1(payload).hexdigest()
|
|
md5 = hashlib.md5(payload).hexdigest()
|
|
sha256 = hashlib.sha256(payload).hexdigest()
|
|
db = {
|
|
"schema_version": 1,
|
|
"generated_at": "2026-08-09T00:00:00Z",
|
|
"total_files": 1,
|
|
"total_size": len(payload),
|
|
"files": {
|
|
sha1: {
|
|
"path": "bios/Test/fw.bin",
|
|
"name": "fw.bin",
|
|
"size": len(payload),
|
|
"sha1": sha1,
|
|
"md5": md5,
|
|
"sha256": sha256,
|
|
"crc32": "0f00ba11",
|
|
"adler32": "0f00ba11",
|
|
}
|
|
},
|
|
"indexes": {
|
|
"by_md5": {md5: sha1},
|
|
"by_name": {"fw.bin": [sha1]},
|
|
"by_crc32": {"0f00ba11": sha1},
|
|
"by_sha256": {sha256: sha1},
|
|
"by_path_suffix": {"fw.bin": [sha1]},
|
|
},
|
|
}
|
|
coverages = {
|
|
"test": {
|
|
"platform": "Test Platform",
|
|
"config": {
|
|
"platform": "Test Platform",
|
|
"cores": ["testemu"],
|
|
"systems": {
|
|
"test-system": {
|
|
"files": [
|
|
{
|
|
"name": "fw.bin",
|
|
"destination": "fw.bin",
|
|
"required": True,
|
|
"sha1": sha1,
|
|
"size": len(payload),
|
|
}
|
|
]
|
|
}
|
|
},
|
|
},
|
|
"total": 1,
|
|
"present": 1,
|
|
"verified": 1,
|
|
"untested": 0,
|
|
"missing": 0,
|
|
"core_present": 1,
|
|
"core_missing": 0,
|
|
"core_unsourceable": 0,
|
|
"details": [],
|
|
}
|
|
}
|
|
profiles = {
|
|
"testemu": {
|
|
"emulator": "Test Emulator",
|
|
"type": "libretro",
|
|
"source": "https://github.com/example/emulator",
|
|
"source_commit": "a" * 40,
|
|
"cores": ["testemu"],
|
|
"systems": ["test-system"],
|
|
"files": [
|
|
{
|
|
"name": "fw.bin",
|
|
"system": "test-system",
|
|
"required": True,
|
|
"source_ref": "src/firmware.cpp:10-12",
|
|
}
|
|
],
|
|
}
|
|
}
|
|
stats = {
|
|
"schema_version": 1,
|
|
"generated_at": "2026-08-09T00:00:00Z",
|
|
"files": 1,
|
|
"size_bytes": len(payload),
|
|
"platforms": 1,
|
|
"emulators": 1,
|
|
"systems": 1,
|
|
"catalog_matched": 0,
|
|
"source": "https://github.com/Abdess/retrobios",
|
|
"downloads": "https://github.com/Abdess/retrobios/releases/tag/v2026.08.06",
|
|
"composition": {
|
|
"systems": {"files": 1, "size_bytes": len(payload)},
|
|
"arcade": {"files": 0, "size_bytes": 0},
|
|
"game_data": {"files": 0, "size_bytes": 0},
|
|
},
|
|
}
|
|
return db, coverages, profiles, stats
|
|
|
|
def test_source_references_are_revision_pinned_links(self):
|
|
_, _, profiles, _ = self._fixtures()
|
|
rendered = _source_ref_markdown(
|
|
profiles["testemu"], "src/firmware.cpp:10-12"
|
|
)
|
|
self.assertIn("/blob/" + "a" * 40 + "/src/firmware.cpp#L10-L12", rendered)
|
|
|
|
def test_ambiguous_two_repository_path_is_never_linked(self):
|
|
"""A path that could live in either declared repository stays plain.
|
|
|
|
The libretro port and the original emulator are two different trees.
|
|
Linking an unattributable path to whichever came first publishes a
|
|
citation that resolves to nothing.
|
|
"""
|
|
profile = {
|
|
"source": "https://github.com/libretro/example-libretro",
|
|
"source_commit": "a" * 40,
|
|
"upstream": "https://gitlab.com/vendor/example",
|
|
"upstream_commit": "b" * 40,
|
|
}
|
|
rendered = _source_ref_markdown(profile, "jg.c:213-214")
|
|
self.assertEqual(rendered, "`jg.c:213-214`")
|
|
self.assertNotIn("http", rendered)
|
|
|
|
# A repository-name prefix removes the ambiguity, so it links again.
|
|
prefixed = _source_ref_markdown(profile, "example/jg.c:213-214")
|
|
self.assertIn("gitlab.com/vendor/example/-/blob/" + "b" * 40 + "/jg.c", prefixed)
|
|
|
|
def test_preprocessor_tokens_cannot_become_page_headings(self):
|
|
rendered = _admonition_body("First paragraph.\n\n#if FEATURE\nEnabled")
|
|
self.assertIn("\\#if FEATURE", rendered)
|
|
self.assertNotIn("\n #if FEATURE", rendered)
|
|
|
|
def test_browser_titles_distinguish_emulators_from_system_pages(self):
|
|
self.assertEqual(
|
|
_browser_title(Path("emulators/qemu.md"), "QEMU"),
|
|
"QEMU emulator firmware",
|
|
)
|
|
self.assertEqual(
|
|
_browser_title(Path("systems/qemu.md"), "QEMU"),
|
|
"QEMU systems",
|
|
)
|
|
|
|
def test_exports_have_catalog_hashes_and_queryable_sqlite(self):
|
|
db, coverages, profiles, stats = self._fixtures()
|
|
exports = generate_data_exports(
|
|
self.docs, db, coverages, profiles, stats
|
|
)
|
|
catalog = json.loads((self.docs / "api/v1/catalog.json").read_text())
|
|
self.assertEqual(catalog["count"], len(exports))
|
|
for item in catalog["items"]:
|
|
path = self.docs / item["url"]
|
|
self.assertTrue(path.is_file())
|
|
self.assertEqual(hashlib.sha256(path.read_bytes()).hexdigest(), item["sha256"])
|
|
|
|
with sqlite3.connect(self.docs / "downloads/retrobios.sqlite") as connection:
|
|
self.assertEqual(connection.execute("SELECT count(*) FROM files").fetchone()[0], 1)
|
|
self.assertEqual(connection.execute("SELECT count(*) FROM platforms").fetchone()[0], 1)
|
|
self.assertEqual(connection.execute("SELECT count(*) FROM emulators").fetchone()[0], 1)
|
|
|
|
def test_page_metadata_is_specific_and_idempotent(self):
|
|
page = self.docs / "gaps.md"
|
|
page.write_text("# Gap Analysis - RetroBIOS\n\nCurrent unresolved entries.\n")
|
|
decorate_markdown_pages(self.docs)
|
|
decorate_markdown_pages(self.docs)
|
|
content = page.read_text()
|
|
self.assertEqual(content.count('type="application/ld+json"'), 1)
|
|
self.assertIn("Current RetroBIOS verification gaps", content)
|
|
self.assertIn('"@type":"TechArticle"', content)
|
|
|
|
|
|
class PerModeSourcePins(unittest.TestCase):
|
|
"""A profile whose builds live in separate repositories.
|
|
|
|
`source`, `upstream` and `source_commit` may each be keyed by build mode.
|
|
Reading the URL from one mode and the revision from another produces a
|
|
permalink into a tree that never held that line, and binding the object
|
|
form straight into SQLite fails outright.
|
|
"""
|
|
|
|
PROFILE = {
|
|
"emulator": "twobuilds",
|
|
"source": {
|
|
"standalone": "https://github.com/vendor/emu",
|
|
"libretro": "https://github.com/porter/emu-libretro",
|
|
},
|
|
"upstream": "https://github.com/vendor/emu",
|
|
"source_commit": {"standalone": "a" * 40, "libretro": "b" * 40},
|
|
}
|
|
|
|
def test_each_repository_keeps_its_own_revision(self):
|
|
pairs = {
|
|
(repo.slug, pin) for repo, pin in _forge_sources(self.PROFILE)
|
|
}
|
|
self.assertIn(("vendor/emu", "a" * 40), pairs)
|
|
self.assertIn(("porter/emu-libretro", "b" * 40), pairs)
|
|
# The libretro fork must never be pinned to the standalone revision.
|
|
self.assertNotIn(("porter/emu-libretro", "a" * 40), pairs)
|
|
self.assertNotIn(("vendor/emu", "b" * 40), pairs)
|
|
|
|
def test_a_build_mode_label_is_resolved_first(self):
|
|
repo, pin = _forge_sources(self.PROFILE, "libretro")[0]
|
|
self.assertEqual((repo.slug, pin), ("porter/emu-libretro", "b" * 40))
|
|
|
|
def test_the_plain_string_form_is_unchanged(self):
|
|
profile = {
|
|
"source": "https://github.com/vendor/emu",
|
|
"source_commit": "c" * 40,
|
|
}
|
|
self.assertEqual(
|
|
[(repo.slug, pin) for repo, pin in _forge_sources(profile)],
|
|
[("vendor/emu", "c" * 40)],
|
|
)
|
|
|
|
def test_sqlite_export_accepts_a_per_mode_commit(self):
|
|
from generate_site import _json_text
|
|
|
|
self.assertEqual(
|
|
_json_text(self.PROFILE["source_commit"]),
|
|
'{"libretro": "' + "b" * 40 + '", "standalone": "' + "a" * 40 + '"}',
|
|
)
|
|
self.assertEqual(_json_text("c" * 40), "c" * 40)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|