mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-26 13:47:20 -05:00
feat: packs for the most downloaded setups
This commit is contained in:
commit
eeef4fc6fb
34 files changed
+1187
-253
No files matched your search
+74
-41
@@ -20,6 +20,8 @@ sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
|
||||
import release_record # noqa: E402
|
||||
from generate_readme import ( # noqa: E402
|
||||
SITE_URL,
|
||||
asset_links,
|
||||
contributing_text,
|
||||
download_links,
|
||||
download_table,
|
||||
@@ -96,15 +98,17 @@ class TheDownloadTablesFollowTheRegistry(unittest.TestCase):
|
||||
self.assertIn(label, row.split(" | ")[1])
|
||||
|
||||
|
||||
class DownloadLinksNameTheFile(unittest.TestCase):
|
||||
class AssetLinksNameTheFile(unittest.TestCase):
|
||||
"""The get/ page of a platform links the files of its pack."""
|
||||
|
||||
def test_a_whole_pack_links_its_asset(self):
|
||||
self.assertEqual(
|
||||
download_links("romm", SPLIT_RECORD, "../..", "Download"),
|
||||
asset_links("romm", SPLIT_RECORD, "../..", "Download"),
|
||||
"[Download](../../releases/download/v2000.01.01/RomM_1_BIOS_Pack.zip)",
|
||||
)
|
||||
|
||||
def test_a_split_pack_links_every_part(self):
|
||||
cell = download_links("batocera", SPLIT_RECORD, "../..", "Download")
|
||||
cell = asset_links("batocera", SPLIT_RECORD, "../..", "Download")
|
||||
for part in ("part1of2", "part2of2"):
|
||||
self.assertIn(
|
||||
f"releases/download/v2000.01.01/Batocera_1_BIOS_Pack.{part}.zip", cell
|
||||
@@ -115,81 +119,110 @@ class DownloadLinksNameTheFile(unittest.TestCase):
|
||||
"""One link taken for the pack left half of it out of the folder."""
|
||||
self.assertIn(
|
||||
"Download, 2 files, all needed: ",
|
||||
download_links("batocera", SPLIT_RECORD, "../..", "Download"),
|
||||
asset_links("batocera", SPLIT_RECORD, "../..", "Download"),
|
||||
)
|
||||
buttons = download_links("batocera", SPLIT_RECORD, REPO, "Pack", " { .x }")
|
||||
buttons = asset_links("batocera", SPLIT_RECORD, REPO, "Pack", " { .x }")
|
||||
self.assertTrue(buttons.startswith("2 files to download, all needed: "), buttons)
|
||||
|
||||
def test_buttons_carry_their_attributes_on_each_part(self):
|
||||
cell = download_links("batocera", SPLIT_RECORD, REPO, "Pack", " { .x }")
|
||||
cell = asset_links("batocera", SPLIT_RECORD, REPO, "Pack", " { .x }")
|
||||
self.assertEqual(cell.count("{ .x }"), 2)
|
||||
|
||||
def test_a_release_without_the_pack_names_the_installer(self):
|
||||
self.assertEqual(
|
||||
download_links("bizhawk", SPLIT_RECORD, "../..", "Download"),
|
||||
"Not in v2000.01.01: "
|
||||
"[use the installer](https://abdess.github.io/retrobios/wiki/installer/)",
|
||||
)
|
||||
|
||||
def test_without_a_recorded_release_the_link_is_the_release_page(self):
|
||||
self.assertEqual(
|
||||
download_links("bizhawk", {}, "../..", "Download"),
|
||||
asset_links("romm", {}, "../..", "Download"),
|
||||
"[Download](../../releases/latest)",
|
||||
)
|
||||
|
||||
def test_the_readme_table_links_each_recorded_pack(self):
|
||||
|
||||
class DownloadLinksKeepTheirAddress(unittest.TestCase):
|
||||
"""Every release deletes the previous tag and its asset URLs: six of the
|
||||
eight links a community wiki had to our packs answered 404. A download
|
||||
cell links the platform's get/ page, which outlives the release."""
|
||||
|
||||
def test_a_released_pack_links_its_get_page(self):
|
||||
self.assertEqual(
|
||||
download_links("romm", SPLIT_RECORD, "Download", site=SITE_URL),
|
||||
f"[Download]({SITE_URL}get/romm/)",
|
||||
)
|
||||
self.assertEqual(
|
||||
download_links("batocera", SPLIT_RECORD, "Download", site=SITE_URL),
|
||||
f"[Download]({SITE_URL}get/batocera/)",
|
||||
)
|
||||
|
||||
def test_a_setup_without_a_release_pack_reads_use_the_command(self):
|
||||
self.assertEqual(
|
||||
download_links("bizhawk", SPLIT_RECORD, "Download", site=SITE_URL),
|
||||
f"[Use the command]({SITE_URL}get/bizhawk/)",
|
||||
)
|
||||
|
||||
def test_a_marked_setup_the_release_lacks_has_no_zip_yet(self):
|
||||
self.assertEqual(
|
||||
download_links("retroarch", SPLIT_RECORD, "Download", site=SITE_URL),
|
||||
f"No ZIP yet: [use the command]({SITE_URL}get/retroarch/)",
|
||||
)
|
||||
|
||||
def test_without_a_recorded_release_the_get_page_answers(self):
|
||||
self.assertEqual(
|
||||
download_links("romm", {}, "Download", site=SITE_URL),
|
||||
f"[Download]({SITE_URL}get/romm/)",
|
||||
)
|
||||
|
||||
def test_the_readme_table_links_get_pages(self):
|
||||
coverages = {
|
||||
"batocera": {"platform": "Batocera"},
|
||||
"bizhawk": {"platform": "BizHawk"},
|
||||
"romm": {"platform": "RomM"},
|
||||
}
|
||||
rows = download_table(coverages, set(), {}, SPLIT_RECORD)[2:]
|
||||
self.assertEqual(len(rows), 2)
|
||||
self.assertEqual(len(rows), 3)
|
||||
for row in rows:
|
||||
self.assertNotIn("releases/latest", row)
|
||||
self.assertIn("releases/download/v2000.01.01/", row)
|
||||
self.assertNotIn("/releases/", row)
|
||||
self.assertIn(f"{SITE_URL}get/", row)
|
||||
self.assertIn("[Use the command]", next(r for r in rows if "BizHawk" in r))
|
||||
|
||||
def test_the_generated_readme_links_every_released_asset(self):
|
||||
def test_the_generated_readme_links_every_get_page(self):
|
||||
if not RECORD.get("packs"):
|
||||
self.skipTest("release.json is not written")
|
||||
from common import list_registered_platforms, list_release_pack_platforms
|
||||
|
||||
readme = _read("README.md")
|
||||
for pack in RECORD["packs"].values():
|
||||
for asset in pack["assets"]:
|
||||
with self.subTest(asset=asset):
|
||||
self.assertIn(
|
||||
f"../../releases/download/{RECORD['tag']}/{asset})", readme
|
||||
)
|
||||
table = _section(readme, "## Download BIOS packs")
|
||||
self.assertNotIn("/releases/download/", table)
|
||||
released = set(list_release_pack_platforms("platforms", include_archived=True))
|
||||
for key in list_registered_platforms("platforms", include_archived=True):
|
||||
with self.subTest(platform=key):
|
||||
self.assertIn(f"]({SITE_URL}get/{key}/)", table)
|
||||
row = next(line for line in table.splitlines()
|
||||
if f"get/{key}/)" in line)
|
||||
if key not in released:
|
||||
self.assertIn("[Use the command]", row)
|
||||
|
||||
def test_the_download_page_rows_link_assets(self):
|
||||
if not RECORD.get("packs"):
|
||||
self.skipTest("release.json is not written")
|
||||
def test_the_download_page_rows_link_get_pages(self):
|
||||
page = _which_pack()
|
||||
rows = [line for line in page.splitlines() if line.startswith("| ")]
|
||||
self.assertTrue(rows)
|
||||
for row in rows:
|
||||
self.assertNotIn("releases/latest", row)
|
||||
for pack in RECORD["packs"].values():
|
||||
for asset in pack["assets"]:
|
||||
self.assertRegex(page, re.escape(f"/releases/download/{RECORD['tag']}/{asset}")
|
||||
+ r"[)\"]")
|
||||
self.assertNotIn("/releases/", row)
|
||||
|
||||
def test_a_platform_the_release_lacks_links_no_release(self):
|
||||
def test_a_platform_without_a_zip_offers_the_command(self):
|
||||
"""KNULLI, AmberELEC and EmuELEC were registered after the recorded
|
||||
release: a row for them linked a release page holding no such pack.
|
||||
Every platform is on the picker now, so the row offers the command."""
|
||||
if not RECORD.get("packs"):
|
||||
self.skipTest("release.json is not written")
|
||||
release, and the platforms without release_pack never get a ZIP: a
|
||||
row for them offers the command."""
|
||||
from common import list_registered_platforms, load_platform_config
|
||||
from release_record import pack_for
|
||||
import generate_site
|
||||
|
||||
record = release_record.load_advertised()
|
||||
page = _which_pack()
|
||||
for key in list_registered_platforms("platforms", include_archived=True):
|
||||
if pack_for(key, RECORD) is not None:
|
||||
if release_record.pack_for(key, record) is not None:
|
||||
continue
|
||||
label = load_platform_config(key, "platforms").get("platform", key)
|
||||
row = next(line for line in page.splitlines() if line.startswith(f"| [{label}]("))
|
||||
state = "No ZIP yet." if generate_site.has_release_pack(key) else "No ZIP."
|
||||
with self.subTest(platform=key):
|
||||
self.assertNotIn("/releases/", row)
|
||||
self.assertIn(f"No ZIP yet. [Use the command](platforms/{key}.md#install).", row)
|
||||
self.assertIn(f"{state} [Use the command](platforms/{key}.md#install).", row)
|
||||
|
||||
|
||||
class SlicesLeaveWithTheirRelease(unittest.TestCase):
|
||||
|
||||
@@ -429,7 +429,9 @@ class TheSitePageOfACardPlatform(unittest.TestCase):
|
||||
self.assertNotIn("0/0", page)
|
||||
self.assertNotIn("## Systems overview", page)
|
||||
self.assertIn("| `Bios/PS/` | [pcsx_rearmed](../emulators/pcsx_rearmed.md) |", page)
|
||||
self.assertIn("| Unzip the pack into | `<SD card>/` |", page)
|
||||
# MinUI has no release pack: no unzip row, the command instead.
|
||||
self.assertNotIn("| Unzip the pack into |", page)
|
||||
self.assertIn("No ZIP for MinUI. The command installs every file and checks it.", page)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -536,8 +536,8 @@ class TheReleaseNotesReadTheRecord(unittest.TestCase):
|
||||
text = self.render()
|
||||
row = next(line for line in text.splitlines() if line.startswith("| RetroArch"))
|
||||
self.assertIn("8,504", row)
|
||||
self.assertIn("part1of2.zip", row)
|
||||
self.assertIn("part2of2.zip", row)
|
||||
self.assertIn("[Download, 2 parts](https://abdess.github.io/retrobios/get/retroarch/)", row)
|
||||
self.assertNotIn("/releases/download/", row)
|
||||
self.assertIn("`system/`", row)
|
||||
self.assertIn("| Platform | Download | Extracted size | Files |", text)
|
||||
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
"""Which platforms a release publishes a ZIP for, and how every surface says it.
|
||||
|
||||
The release carried a pack for every platform, 35 GB, while five of them
|
||||
drew 83% of the downloads. `release_pack: true` in the registry is now the
|
||||
list: the build, the pack checks, the release record and every download
|
||||
table read it. The other platforms keep their install manifest, and each
|
||||
page that would show a ZIP says there is none and gives the command.
|
||||
|
||||
Every release also deleted the previous tag, and with it each asset URL:
|
||||
six of the eight links a community wiki had to our packs answered 404. Each
|
||||
platform has a permanent page at get/<platform>/ instead.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import yaml
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
|
||||
import common # noqa: E402
|
||||
import release_record # noqa: E402
|
||||
|
||||
PLATFORMS = str(REPO_ROOT / "platforms")
|
||||
TMP_ROOT = REPO_ROOT / "tmp"
|
||||
SITE = "https://abdess.github.io/retrobios/"
|
||||
# The owner's choice of 2026-10-23, from the download counts of v2026.10.10.
|
||||
RELEASED = {"retroarch", "emudeck", "retrobat", "batocera", "romm", "misterfpga"}
|
||||
|
||||
|
||||
def _write_registry(root: Path, platforms: dict) -> str:
|
||||
platforms_dir = root / "platforms"
|
||||
platforms_dir.mkdir()
|
||||
for name in platforms:
|
||||
(platforms_dir / f"{name}.yml").write_text(
|
||||
yaml.dump({"platform": name.title(), "systems": {}}), encoding="utf-8"
|
||||
)
|
||||
registry = {name: {"config": f"{name}.yml", **entry} for name, entry in platforms.items()}
|
||||
(platforms_dir / "_registry.yml").write_text(
|
||||
yaml.dump({"platforms": registry}), encoding="utf-8"
|
||||
)
|
||||
return str(platforms_dir)
|
||||
|
||||
|
||||
def _which_pack() -> str:
|
||||
import generate_site
|
||||
|
||||
with contextlib.chdir(REPO_ROOT):
|
||||
return generate_site.generate_which_pack()
|
||||
|
||||
|
||||
class TheFieldIsTheList(unittest.TestCase):
|
||||
def test_the_registry_marks_the_most_downloaded_setups(self):
|
||||
self.assertEqual(
|
||||
set(common.list_release_pack_platforms(PLATFORMS, include_archived=True)), RELEASED
|
||||
)
|
||||
|
||||
def test_only_marked_platforms_are_listed_and_archived_ones_on_request(self):
|
||||
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as tmp:
|
||||
platforms_dir = _write_registry(Path(tmp), {
|
||||
"big": {"status": "active", "release_pack": True},
|
||||
"small": {"status": "active"},
|
||||
"off": {"status": "active", "release_pack": False},
|
||||
"old": {"status": "archived", "release_pack": True},
|
||||
})
|
||||
self.assertEqual(common.list_release_pack_platforms(platforms_dir), ["big"])
|
||||
self.assertEqual(
|
||||
common.list_release_pack_platforms(platforms_dir, include_archived=True),
|
||||
["big", "old"],
|
||||
)
|
||||
self.assertEqual(
|
||||
common.list_registered_platforms(platforms_dir), ["big", "off", "small"]
|
||||
)
|
||||
|
||||
def test_the_schema_holds_the_field(self):
|
||||
try:
|
||||
from jsonschema import Draft202012Validator
|
||||
except ImportError:
|
||||
self.skipTest("jsonschema is a tooling dependency")
|
||||
|
||||
schema = json.loads((REPO_ROOT / "schemas" / "registry.schema.json").read_text())
|
||||
validator = Draft202012Validator(schema)
|
||||
registry = yaml.safe_load((REPO_ROOT / "platforms" / "_registry.yml").read_text())
|
||||
self.assertEqual([e.message for e in validator.iter_errors(registry)], [])
|
||||
registry["platforms"]["knulli"]["release_pack"] = "yes"
|
||||
self.assertTrue(list(validator.iter_errors(registry)))
|
||||
|
||||
|
||||
class TheBuildReadsTheField(unittest.TestCase):
|
||||
def _groups(self, *argv: str) -> list[str]:
|
||||
import generate_pack
|
||||
|
||||
built: list[str] = []
|
||||
|
||||
def capture(args, groups, *rest, **kw):
|
||||
built.extend(name for group, _rep in groups for name in group)
|
||||
|
||||
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as out, \
|
||||
contextlib.chdir(REPO_ROOT), \
|
||||
mock.patch.object(generate_pack, "_run_platform_packs", capture), \
|
||||
mock.patch.object(generate_pack, "_run_manifest_mode", capture), \
|
||||
mock.patch.object(sys, "argv", ["generate_pack.py", *argv, "--output-dir", out]), \
|
||||
contextlib.redirect_stdout(io.StringIO()):
|
||||
generate_pack.main()
|
||||
return built
|
||||
|
||||
def test_all_builds_the_release_packs_only(self):
|
||||
self.assertEqual(set(self._groups("--all", "--offline")), RELEASED)
|
||||
|
||||
def test_archived_platforms_without_the_field_stay_out(self):
|
||||
self.assertEqual(
|
||||
set(self._groups("--all", "--include-archived", "--offline")), RELEASED
|
||||
)
|
||||
|
||||
def test_manifests_cover_every_platform(self):
|
||||
self.assertEqual(
|
||||
set(self._groups("--all", "--manifest", "--offline")),
|
||||
set(common.list_registered_platforms(PLATFORMS, include_archived=True)),
|
||||
)
|
||||
|
||||
def test_the_release_owners_are_the_marked_platforms(self):
|
||||
owners = release_record.release_owners(PLATFORMS)
|
||||
served = {p for platforms in owners.values() for p in platforms}
|
||||
self.assertEqual(served, RELEASED)
|
||||
|
||||
def test_the_list_marks_installer_only_platforms(self):
|
||||
import subprocess
|
||||
|
||||
proc = subprocess.run(
|
||||
[sys.executable, "scripts/generate_pack.py", "--list"],
|
||||
capture_output=True, text=True, cwd=REPO_ROOT, check=True, timeout=120,
|
||||
)
|
||||
lines = proc.stdout.splitlines()
|
||||
for name in RELEASED:
|
||||
self.assertIn(name, lines)
|
||||
self.assertIn("recalbox (installer only)", lines)
|
||||
|
||||
|
||||
class APlatformWithoutAPackFailsNoCheck(unittest.TestCase):
|
||||
VERIFY = (
|
||||
"RetroArch: 10/10 OK [existence]\n"
|
||||
"KNULLI: 5/5 OK [md5]\n"
|
||||
)
|
||||
PACK = (
|
||||
"Generating pack for RetroArch...\n"
|
||||
" dist/RetroArch_BIOS_Pack.zip: 10 files packed, 10/10 files OK [existence]\n"
|
||||
)
|
||||
|
||||
def test_the_consistency_step_skips_a_setup_served_by_the_installer(self):
|
||||
import pipeline
|
||||
|
||||
with contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
self.assertTrue(pipeline.check_consistency(
|
||||
self.VERIFY, self.PACK, pipeline.release_pack_keys(PLATFORMS)))
|
||||
self.assertIn("KNULLI: 5/5 (no release pack, served by the installer)", out.getvalue())
|
||||
|
||||
def test_a_released_platform_without_its_pack_still_fails(self):
|
||||
import pipeline
|
||||
|
||||
verify = self.VERIFY + "Batocera: 3/3 OK [md5]\n"
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
self.assertFalse(pipeline.check_consistency(
|
||||
verify, self.PACK, pipeline.release_pack_keys(PLATFORMS)))
|
||||
|
||||
def test_the_pipeline_passes_the_release_platforms(self):
|
||||
source = (REPO_ROOT / "scripts" / "pipeline.py").read_text(encoding="utf-8")
|
||||
self.assertIn("release_pack_keys(include_archived=args.include_archived)", source)
|
||||
|
||||
def test_verify_packs_sweeps_the_release_packs_only(self):
|
||||
import subprocess
|
||||
|
||||
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as directory:
|
||||
proc = subprocess.run(
|
||||
[sys.executable, "scripts/generate_pack.py", "--all", "--verify-packs",
|
||||
"--output-dir", directory],
|
||||
capture_output=True, text=True, cwd=REPO_ROOT, timeout=600,
|
||||
)
|
||||
swept = {line.split(":")[0].strip() for line in proc.stdout.splitlines()
|
||||
if "SKIP (no pack in" in line}
|
||||
self.assertEqual(swept, RELEASED)
|
||||
|
||||
|
||||
class TheReleaseCheckReadsTheField(unittest.TestCase):
|
||||
RECORD = {"tag": "v1", "packs": {
|
||||
"A_BIOS_Pack.zip": {"download_size": 1, "assets": ["A_BIOS_Pack.zip"],
|
||||
"platforms": ["retroarch"]},
|
||||
"K_BIOS_Pack.zip": {"download_size": 1, "assets": ["K_BIOS_Pack.zip"],
|
||||
"platforms": ["knulli"]},
|
||||
}}
|
||||
|
||||
def test_a_missing_and_an_unlisted_pack_are_reported(self):
|
||||
from check_release import Asset, compare_release
|
||||
|
||||
sums = {"A_BIOS_Pack.zip": "a" * 64, "K_BIOS_Pack.zip": "b" * 64}
|
||||
assets = {name: Asset(1, digest) for name, digest in sums.items()}
|
||||
assets["SHA256SUMS.txt"] = Asset(1, None)
|
||||
findings = compare_release(self.RECORD, sums, assets, "v1", None,
|
||||
{"retroarch", "batocera"})
|
||||
kinds = {(f.kind, f.name) for f in findings}
|
||||
self.assertIn(("no-pack", "batocera"), kinds)
|
||||
self.assertIn(("not-released", "K_BIOS_Pack.zip"), kinds)
|
||||
self.assertNotIn(("no-pack", "retroarch"), kinds)
|
||||
|
||||
|
||||
class TheTablesFollowTheField(unittest.TestCase):
|
||||
def test_a_pack_of_a_platform_without_the_field_is_not_advertised(self):
|
||||
record = {"tag": "v1", "packs": {
|
||||
"RetroArch_Lakka_BIOS_Pack.zip": {"platforms": ["lakka", "retroarch"],
|
||||
"assets": ["x.zip"]},
|
||||
"Recalbox_BIOS_Pack.zip": {"platforms": ["recalbox"], "assets": ["y.zip"]},
|
||||
}}
|
||||
shown = release_record.advertised(record, PLATFORMS)
|
||||
self.assertEqual(list(shown["packs"]), ["RetroArch_Lakka_BIOS_Pack.zip"])
|
||||
self.assertEqual(shown["packs"]["RetroArch_Lakka_BIOS_Pack.zip"]["platforms"],
|
||||
["retroarch"])
|
||||
self.assertIsNone(release_record.pack_for("lakka", shown))
|
||||
self.assertEqual(release_record.advertised({}, PLATFORMS), {})
|
||||
|
||||
|
||||
class EverySurfaceSaysWhy(unittest.TestCase):
|
||||
def test_a_setup_page_says_no_zip_with_the_command_beside_it(self):
|
||||
import generate_site
|
||||
|
||||
line = generate_site.no_zip_line("KNULLI")
|
||||
self.assertEqual(line, "No ZIP for KNULLI. The command installs every file and checks it.")
|
||||
with contextlib.chdir(REPO_ROOT):
|
||||
block = generate_site._pack_buttons("knulli")
|
||||
guide = generate_site._guide_download(
|
||||
"knulli", "KNULLI", generate_site.GUIDES["knulli"], None, "## Download")
|
||||
self.assertIn(line, block)
|
||||
self.assertIn(generate_site.NO_ZIP_FAQ, block)
|
||||
self.assertIn(line, "\n".join(guide))
|
||||
self.assertNotIn("yet", block)
|
||||
|
||||
def test_a_built_setup_page_has_the_line(self):
|
||||
page = REPO_ROOT / "website" / "src" / "content" / "docs" / "platforms" / "knulli.md"
|
||||
if not page.is_file():
|
||||
self.skipTest("the site is not generated")
|
||||
text = page.read_text(encoding="utf-8")
|
||||
self.assertIn("No ZIP for KNULLI. The command installs every file and checks it.", text)
|
||||
install = text.index("## Install")
|
||||
self.assertLess(install, text.index("No ZIP for KNULLI"))
|
||||
|
||||
def test_get_your_files_explains_the_rows(self):
|
||||
page = _which_pack()
|
||||
self.assertIn("No ZIP. [Use the command](platforms/knulli.md#install).", page)
|
||||
self.assertIn("ZIPs are made for the setups most people download.", page)
|
||||
self.assertIn("wiki/faq.md#why-is-there-no-zip-for-my-setup", page)
|
||||
|
||||
def test_the_readme_rows_read_use_the_command(self):
|
||||
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
|
||||
row = next(line for line in readme.splitlines() if line.startswith("| KNULLI |"))
|
||||
self.assertIn(f"[Use the command]({SITE}get/knulli/)", row)
|
||||
self.assertIn("ZIPs are made for the setups most people download.", readme)
|
||||
|
||||
def test_the_faq_answers_why(self):
|
||||
faq = (REPO_ROOT / "wiki" / "faq.md").read_text(encoding="utf-8")
|
||||
section = faq.split("## Why is there no ZIP for my setup?", 1)[1].split("\n## ", 1)[0]
|
||||
self.assertIn("most people download", section)
|
||||
self.assertIn("checks each one", section)
|
||||
self.assertIn("--dry-run", section)
|
||||
|
||||
def test_the_release_process_documents_the_field_and_the_counts(self):
|
||||
page = (REPO_ROOT / "wiki" / "release-process.md").read_text(encoding="utf-8")
|
||||
self.assertIn("release_pack: true", page)
|
||||
self.assertIn("download_count", page)
|
||||
self.assertIn("part1of", page)
|
||||
|
||||
|
||||
class TheReleaseNotesNameTheSetupsWithoutAZip(unittest.TestCase):
|
||||
RECORD = {"tag": "v2026.10.24", "packs": {
|
||||
"RomM_5_BIOS_Pack.zip": {
|
||||
"files": 1, "extracted_size": 1, "download_size": 1,
|
||||
"assets": ["RomM_5_BIOS_Pack.zip"], "platforms": ["romm"]},
|
||||
}}
|
||||
|
||||
def test_the_paragraph_names_them_and_points_to_the_command(self):
|
||||
import release_notes
|
||||
|
||||
text = release_notes.render(self.RECORD, PLATFORMS, "### What changed\n\n- x\n")
|
||||
paragraph = next(line for line in text.splitlines() if line.startswith("No ZIP for "))
|
||||
for name in ("KNULLI", "Recalbox", "RetroPie", "Lakka"):
|
||||
self.assertIn(name, paragraph)
|
||||
for name in ("RetroArch", "Batocera", "RomM"):
|
||||
self.assertNotIn(f" {name},", paragraph)
|
||||
self.assertIn("The command above", paragraph)
|
||||
self.assertIn("--dry-run", paragraph)
|
||||
self.assertLess(text.index(paragraph), text.index("### What changed"))
|
||||
|
||||
def test_the_rows_link_get_pages(self):
|
||||
import release_notes
|
||||
|
||||
text = release_notes.render(self.RECORD, PLATFORMS, "x")
|
||||
self.assertIn(f"[Download]({SITE}get/romm/)", text)
|
||||
self.assertNotIn("/releases/download/", text)
|
||||
|
||||
|
||||
class EveryPlatformHasAPermanentPage(unittest.TestCase):
|
||||
def test_a_page_for_every_registered_platform(self):
|
||||
import generate_site
|
||||
|
||||
record = release_record.load_advertised()
|
||||
with contextlib.chdir(REPO_ROOT):
|
||||
for name in common.list_registered_platforms(PLATFORMS, include_archived=True):
|
||||
page = generate_site.generate_get_page(name)
|
||||
pack = release_record.pack_for(name, record)
|
||||
with self.subTest(platform=name):
|
||||
self.assertIn("# Download ", page)
|
||||
if name in RELEASED and pack:
|
||||
for asset in pack["assets"]:
|
||||
self.assertIn(
|
||||
f"/releases/download/{record['tag']}/{asset}", page)
|
||||
elif name not in RELEASED:
|
||||
self.assertNotIn("_BIOS_Pack", page)
|
||||
self.assertIn(f"install.sh | sh -s -- --platform {name}", page)
|
||||
self.assertIn("No ZIP for ", page)
|
||||
|
||||
def test_the_generator_writes_and_sweeps_them(self):
|
||||
import generate_site
|
||||
|
||||
self.assertIn(release_record.GET_DIR, generate_site.GENERATED_DIRS)
|
||||
source = Path(generate_site.__file__).read_text(encoding="utf-8")
|
||||
self.assertIn('write_if_changed(str(get_dir / f"{name}.md"), generate_get_page(name))',
|
||||
source)
|
||||
|
||||
def test_built_pages_exist(self):
|
||||
docs = REPO_ROOT / "website" / "src" / "content" / "docs" / release_record.GET_DIR
|
||||
if not docs.is_dir():
|
||||
self.skipTest("the site is not generated")
|
||||
for name in common.list_registered_platforms(PLATFORMS, include_archived=True):
|
||||
with self.subTest(platform=name):
|
||||
self.assertTrue(any(docs.glob(f"{name}.md*")))
|
||||
|
||||
|
||||
class TheSiteCheckResolvesEveryGetPage(unittest.TestCase):
|
||||
def _site(self, root: Path, pages: dict[str, str]) -> Path:
|
||||
site = root / "dist"
|
||||
for rel, body in pages.items():
|
||||
path = site / rel
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(f"<!doctype html><html lang=en><body>{body}</body></html>",
|
||||
encoding="utf-8")
|
||||
return site
|
||||
|
||||
def test_missing_and_wrong_pages_are_reported(self):
|
||||
import validate_site
|
||||
|
||||
record = {"tag": "v1", "packs": {"RomM_BIOS_Pack.zip": {
|
||||
"platforms": ["romm"], "assets": ["RomM_BIOS_Pack.zip"],
|
||||
"files": 1, "extracted_size": 1, "download_size": 1}}}
|
||||
download = validate_site.RELEASE_DOWNLOAD
|
||||
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as tmp:
|
||||
root = Path(tmp)
|
||||
record_path = root / "release.json"
|
||||
record_path.write_text(json.dumps(record), encoding="utf-8")
|
||||
pages = {
|
||||
f"get/{name}/index.html": "install.sh"
|
||||
for name in common.list_registered_platforms(PLATFORMS, include_archived=True)
|
||||
}
|
||||
pages["get/romm/index.html"] = f'<a href="{download}v1/RomM_BIOS_Pack.zip">x</a>'
|
||||
pages["get/knulli/index.html"] = f'<a href="{download}v1/K_BIOS_Pack.zip">x</a>'
|
||||
del pages["get/lakka/index.html"]
|
||||
site = self._site(root, pages)
|
||||
parsed = {p.resolve(): validate_site._parse_page(p) for p in site.rglob("*.html")}
|
||||
issues = validate_site._check_get_pages(parsed, site.resolve(), Path(PLATFORMS),
|
||||
record_path)
|
||||
joined = "\n".join(issues)
|
||||
self.assertIn("get/lakka/index.html: no download page", joined)
|
||||
self.assertIn("get/knulli/index.html: links a pack", joined)
|
||||
self.assertIn("get/knulli/index.html: gives no install command", joined)
|
||||
self.assertNotIn("get/romm/", joined)
|
||||
self.assertNotIn("get/retroarch/", joined)
|
||||
|
||||
def test_the_cli_runs_the_check(self):
|
||||
source = (REPO_ROOT / "scripts" / "validate_site.py").read_text(encoding="utf-8")
|
||||
self.assertIn("validate_site(Path(args.site_dir), args.base, args.platforms_dir, args.record)",
|
||||
source)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -175,12 +175,17 @@ class GetYourFilesOrder(unittest.TestCase):
|
||||
def test_a_setup_after_the_release_says_no_zip_yet(self):
|
||||
text = _page("which-pack.md")
|
||||
self.assertNotIn("Not in v", text)
|
||||
record = release_record.load_record()
|
||||
missing = [k for k in REGISTRY if not release_record.pack_for(k, record)]
|
||||
if record.get("tag") and missing:
|
||||
record = release_record.load_advertised()
|
||||
pending = [k for k in REGISTRY if REGISTRY[k].get("release_pack")
|
||||
and not release_record.pack_for(k, record)]
|
||||
if record.get("tag") and pending:
|
||||
self.assertIn("No ZIP yet. [Use the command]", text)
|
||||
self.assertIn("Setups added after the last release have no ZIP until the next one.",
|
||||
text)
|
||||
else:
|
||||
self.assertNotIn("No ZIP yet.", text)
|
||||
if any(not entry.get("release_pack") for entry in REGISTRY.values()):
|
||||
self.assertIn("No ZIP. [Use the command]", text)
|
||||
|
||||
def test_zip_advice_answers_the_usual_mistakes(self):
|
||||
text = _page("which-pack.md")
|
||||
|
||||
@@ -140,10 +140,15 @@ class PackNamesItsVersion(unittest.TestCase):
|
||||
f"RetroArch {load_platform_config('retroarch', 'platforms')['version'].lstrip('v')}")
|
||||
|
||||
def test_every_setup_page_names_a_version(self):
|
||||
"""A setup with a release ZIP names the version it was made for. One
|
||||
without says there is no ZIP: the command installs current files."""
|
||||
for key in REGISTRY:
|
||||
with self.subTest(platform=key):
|
||||
text = _guide_part(_page(f"platforms/{key}.md"))
|
||||
self.assertRegex(text, r"(?:[Mm]ade for|This pack was built for) \S")
|
||||
if generate_site.has_release_pack(key):
|
||||
self.assertRegex(text, r"(?:[Mm]ade for|This pack was built for) \S")
|
||||
else:
|
||||
self.assertIn(generate_site.no_zip_line(_display(key)), text)
|
||||
|
||||
|
||||
class SetupPagesOfferPartOfThePack(unittest.TestCase):
|
||||
@@ -214,10 +219,12 @@ class GetYourFilesListsEachSetupOnce(unittest.TestCase):
|
||||
self.assertTrue(release_record.pack_for(key, record))
|
||||
|
||||
def test_parts_are_buttons_with_the_size_after(self):
|
||||
rows = [line for line in self.text.splitlines() if "Part 1 of" in line]
|
||||
"""A row links the setup's get/ page, which lists every part, with the
|
||||
download size after the button."""
|
||||
rows = [line for line in self.text.splitlines() if ">ZIP</a>" in line]
|
||||
self.assertTrue(rows)
|
||||
for row in rows:
|
||||
self.assertRegex(row, r'<a class="rb-button" href="[^"]+">Part 1 of \d</a>')
|
||||
self.assertRegex(row, r'<a class="rb-button" href="/retrobios/get/[a-z0-9]+/">ZIP</a>')
|
||||
self.assertRegex(row, r"</a> \(\d[\d.]* [KMG]B\) \|$")
|
||||
|
||||
def test_the_zip_note(self):
|
||||
|
||||
@@ -346,7 +346,8 @@ class ListPlatformsTakesTheSharedFlag(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(proc.returncode, 0, proc.stderr)
|
||||
lines = proc.stdout.splitlines()
|
||||
self.assertIn("retropie (archived)", lines)
|
||||
self.assertIn("retropie (archived, installer only)", lines)
|
||||
self.assertIn("knulli (installer only)", lines)
|
||||
self.assertIn("retroarch", lines)
|
||||
|
||||
|
||||
|
||||
@@ -140,12 +140,19 @@ class VerifyPackChoice(unittest.TestCase):
|
||||
self._run(self._args())
|
||||
self.assertEqual(out.getvalue().strip().splitlines()[-1], "Verified 1 pack: all OK")
|
||||
|
||||
def test_include_archived_reaches_the_archived_pack(self):
|
||||
def test_the_sweep_checks_release_packs_and_a_named_platform_its_own(self):
|
||||
"""--all checks the packs a release publishes (release_pack): the
|
||||
RetroPie pack, archived and served by the installer, is left to a
|
||||
check that names it."""
|
||||
stem = self.gp._platform_pack_stem(["retropie"], "retropie", "platforms")
|
||||
self._touch(f"{stem}_BIOS_Pack.zip")
|
||||
self._touch(f"{self.stem}_BIOS_Pack.zip")
|
||||
checked = self._run(
|
||||
self._args(platform=None, all=True, include_archived=True)
|
||||
)
|
||||
self.assertIn(("retroarch", f"{self.stem}_BIOS_Pack.zip"), checked)
|
||||
self.assertNotIn(("retropie", f"{stem}_BIOS_Pack.zip"), checked)
|
||||
checked = self._run(self._args(platform="retropie"))
|
||||
self.assertIn(("retropie", f"{stem}_BIOS_Pack.zip"), checked)
|
||||
|
||||
|
||||
|
||||
@@ -26,20 +26,25 @@ class WhichPackRows(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def test_platform_rows_name_their_own_pack(self):
|
||||
"""A row links its own get/ page, never another setup's and never a
|
||||
release asset: the asset URL dies with its tag."""
|
||||
import generate_site # noqa: PLC0415
|
||||
import release_record # noqa: PLC0415
|
||||
|
||||
record = release_record.load_record()
|
||||
record = release_record.load_advertised()
|
||||
page = generate_site.generate_which_pack()
|
||||
row = re.compile(r"^\| \[[^\]]+\]\(platforms/([a-z0-9]+)\.md\) \|(.*)$", re.M)
|
||||
asset = re.compile(r"/releases/download/[^/]+/([^)\s\"]+)[)\"]")
|
||||
get = re.compile(r'href="get/([a-z0-9]+)\.md"')
|
||||
checked = 0
|
||||
for key, rest in row.findall(page):
|
||||
linked = set(asset.findall(rest))
|
||||
pack = release_record.pack_for(key, record) or {}
|
||||
linked = set(get.findall(rest))
|
||||
pack = release_record.pack_for(key, record)
|
||||
with self.subTest(platform=key):
|
||||
self.assertLessEqual(linked, set(pack.get("assets") or []))
|
||||
self.assertNotIn("/releases/download/", rest)
|
||||
self.assertLessEqual(linked, {key})
|
||||
if pack:
|
||||
checked += 1
|
||||
self.assertTrue(linked)
|
||||
self.assertGreater(checked, 5)
|
||||
self.assertEqual(linked, {key})
|
||||
elif not generate_site.has_release_pack(key):
|
||||
self.assertIn(f"No ZIP. [Use the command](platforms/{key}.md#install).", rest)
|
||||
self.assertGreater(checked, 3)
|
||||
Reference in new issue
Block a user