From e19b82ea471e86409f8f103c55aa9a4d0f659377 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 28 Oct 2026 07:29:58 +0100 Subject: [PATCH] refactor: keep proof lines, drop the test showcase Emulator pages show a run only when a profile's evidence names it or it answers an issue, as one line naming the BIOS the core ran. The Tested blocks, region outcomes, coverage, campaign and pack tables and the benchmark figures are gone from the site; the records stay. --- .github/workflows/deploy-site.yml | 1 - scripts/generate_site.py | 50 +- scripts/sitemeasurements.py | 934 ++---------------- tests/test_campaign.py | 45 - tests/test_measurements.py | 208 +--- tests/test_pack_probe.py | 59 +- tests/test_region_check.py | 80 +- tests/test_site_r3_emulators.py | 9 - .../src/components/MeasurementsExplorer.astro | 4 +- website/src/styles/retrobios.css | 85 -- 10 files changed, 168 insertions(+), 1307 deletions(-) diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index cca04dae2..0f34b97ee 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -40,7 +40,6 @@ on: - "scripts/provenance_report.py" - "scripts/refresh_data_dirs.py" - "scripts/region.py" - - "scripts/region_check.py" - "scripts/release_record.py" - "scripts/restore_large_files.py" - "scripts/safeparse.py" diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 2e6af67d8..c6b2b42e5 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -2452,7 +2452,7 @@ def _page_description(markdown: str, relative: Path, title: str) -> str: "cross-reference.md": "Every emulator core each retrogaming platform runs, the systems it emulates and the upstream project it comes from.", "gaps.md": "Current RetroBIOS verification gaps, platform-to-emulator divergences, missing files and documented source limitations.", "exports.md": "Each platform's own BIOS list (System.dat, batocera-systems, es_bios.xml and the rest) with the hashes and files the emulator source code shows, offered to its maintainers.", - sitemeasurements.EXPLORER_PAGE: "Emulator cores run headless in real conditions: each test with its claim, result, method, computer and full record, filterable, and as SQLite.", + sitemeasurements.EXPLORER_PAGE: "The records of emulator cores run headless on demand, each with its claim, verdict, the BIOS it ran, method, computer and full record, filterable, and as SQLite.", "provenance.md": "Hash-based comparison of RetroBIOS metadata with No-Intro, Redump and TOSEC catalog snapshots.", "explore.md": "Systems, emulators and installer options to explore, with the figures of the BIOS collection.", "which-pack.md": "Pick your device or setup and get its BIOS files: the folder they go in, one command that installs and checks them, or a ZIP pack to extract.", @@ -4329,9 +4329,6 @@ def generate_platform_page( no_list = not cov["total"] and registry.get(name, {}).get("native_list") == "none" if name in GUIDES: lines += _platform_guide(name, display, cov, registry, no_list) - # Before the reference: the links below it are read under that heading. - lines.extend(sitemeasurements.pack_tested_section(_PACK_ITEMS.get(name, []))) - lines += [ "## Reference: every file by system { #reference-every-file-by-system }", "", @@ -7182,11 +7179,6 @@ def system_pages( _section_label(manufacturer, console), _install_pairs(installs.get(console, []), lead, consumers)) - def region_block(console: str, level: int = 3) -> list[str]: - # Under the console's heading on a maker's page, a section of its - # own on the console's page. - return sitemeasurements.region_section(_REGION_RESULTS.get((slug, console), []), level) - def all_sections() -> dict[str, list[Card]]: anchors: set[str] = set() return {console: cards_of(console, anchors) for console in ordered} @@ -7228,7 +7220,6 @@ def system_pages( lines.append("") lines.extend(start_of(console)) lines.extend(install_block(console)) - lines.extend(region_block(console)) lines.extend(_cards_markdown(cards)) lines.append("") lines.extend(_next_block([*_NEXT_FROM_SYSTEMS, json_next])) @@ -7291,7 +7282,6 @@ def system_pages( if console not in moved: summary.extend(start_of(console)) summary.extend(install_block(console)) - summary.extend(region_block(console)) summary.extend(_cards_markdown(cards_of(console, summary_anchors))) summary.append("") continue @@ -7376,8 +7366,6 @@ def system_pages( lines.extend(legend) lines.extend(_romset_note({console: consoles[console]})) lines.extend(install_block(console)) - if index == 0: - lines.extend(region_block(console, 2)) lines.extend(["## Files", ""]) lines.extend(_cards_markdown(part)) lines.append("") @@ -9106,13 +9094,8 @@ def _said_as_list(items: list[str]) -> str: # Data directory members and revisions, read once per build. _DATA_MEMBERS: dict[str, dict[str, dict]] = {} _DATA_REVISIONS: dict[str, dict] = {} -# The measurement index items of each emulator, for its Tested block. +# The measurement index items of each emulator, for the proof lines of its page. _MEASUREMENT_ITEMS: dict[str, list[dict]] = {} -# (manufacturer slug, console) -> the region outcomes of its systems -# (region_check.py), shown under the console on its system page. -_REGION_RESULTS: dict[tuple[str, str], list[dict]] = {} -# Pack probes by platform key, for the Tested block of a platform page. -_PACK_ITEMS: dict[str, list[dict]] = {} # The emulator pages that list each data directory, by ref. _DATA_READERS: dict[str, list[tuple[str, str]]] = {} @@ -9339,19 +9322,6 @@ def _summary_table(files: list[dict], indexes: list[int], cell: Callable[[int, s ] -def _tested_file_hrefs(name: str, files: list[dict]) -> dict[str, str]: - """The card each file name a test varies leads to: its name, aliases - and path, folded to lower case, the first card of a name winning.""" - hrefs: dict[str, str] = {} - for f, anchor in zip(files, emulator_file_anchors(files)): - page = emulator_card_page(name, anchor) - href = f"#{anchor}" if page == name else markup.site_href(f"emulators/{page}/") + f"#{anchor}" - for label in (f.get("name"), *(f.get("aliases") or ()), f.get("path")): - if label: - hrefs.setdefault(Path(str(label)).name.casefold(), href) - return hrefs - - def plan_emulator_pages(profiles: dict) -> None: """Record which page holds each emulator card, before any page links one.""" stems = set() @@ -9743,8 +9713,7 @@ def emulator_pages( [files[i] for i in entries], [rows[i] for i in entries] if table_form else None) - lines.extend(sitemeasurements.tested_section( - name, _MEASUREMENT_ITEMS.get(name, []), _tested_file_hrefs(name, files))) + lines.extend(sitemeasurements.proof_section(_MEASUREMENT_ITEMS.get(name, []))) blocks = _render_structured_blocks(profile) if blocks: @@ -11841,13 +11810,6 @@ def _generate(args: argparse.Namespace) -> None: _system_pages_from_data(manufacturers, coverages, db, profiles) ) print(f" {len(_system_page_map)} system IDs mapped to pages") - from siterender import _page_by_norm # noqa: PLC0415 - - _REGION_RESULTS.clear() - for result in sitemeasurements.site_region_results(repo=REPO_URL): - place = _system_page_map.get(result["system"]) or _page_by_norm(result["system"]) - if place: - _REGION_RESULTS.setdefault(place, []).append(result) set_console_sections(manufacturers, program_names(profiles)) from siterender import link_coverage, system_ids # noqa: PLC0415 @@ -11900,13 +11862,9 @@ def _generate(args: argparse.Namespace) -> None: _record(path) _MEASUREMENT_ITEMS.clear() _MEASUREMENT_ITEMS.update(sitemeasurements.by_emulator(measured.index)) - _PACK_ITEMS.clear() - _PACK_ITEMS.update(sitemeasurements.by_platform(measured.index)) print(f" {measured.index['records']} records, {measured.index['count']} tests") write_if_changed(str(docs / sitemeasurements.EXPLORER_PAGE), - sitemeasurements.generate_explorer_page( - measured.index, {sys_id: system_name(sys_id) for sys_id in sorted(set(_system_page_map))}) - + reference_next()) + sitemeasurements.generate_explorer_page(measured.index) + reference_next()) print("Generating static API and bulk data exports...") exports = generate_data_exports( diff --git a/scripts/sitemeasurements.py b/scripts/sitemeasurements.py index ff5774c8e..f56acddb6 100644 --- a/scripts/sitemeasurements.py +++ b/scripts/sitemeasurements.py @@ -10,10 +10,14 @@ never committed: public/downloads/measurements.sqlite every record, normalised public/downloads/measurements/.sqlite one database per emulator -Three places read the index: the Tested block of each emulator page, the -Measurements explorer page and the host table of the methodology page. A -number is never shown without its method and host beside it. A record links -its line at the last commit that touched measurements/. +Three places read the index: the Proof lines of an emulator page (only the +records a profile's `evidence:` names, or one tied to an issue), the +Measurements explorer page and the host table of the methodology page. Runs +are made on demand, to settle a dispute or an issue, to back a correction +sent to a platform, or to smoke-test packs before a release: there is no +showcase of every run. A record links its line at the last commit that +touched measurements/. Benchmarks stay in the records and the SQLite files, +never on a page. """ from __future__ import annotations @@ -63,14 +67,6 @@ def host_summary(host: dict) -> str: return ", ".join(part for part in parts if part) -def bench_statement(record: dict) -> str: - timing = record["timing"] - frame, fps = timing["ms_per_frame"], timing["fps"] - return (f"median {frame['median']:.3f} ms per frame ({fps['median']:.1f} fps), " - f"min {frame['min']:.3f}, max {frame['max']:.3f}, over " - f"{record['inputs']['repetitions']} repetitions of {record['inputs']['frames']} frames") - - def permalink(repo: str, commit: str, path: str, line: int | None = None) -> str: return f"{repo}/blob/{commit}/{path}" + (f"#L{line}" if line else "") @@ -105,8 +101,6 @@ def result_of(record: dict) -> tuple[str, str]: """ check = record.get("check") or {} result = check.get("result") or {} - if record["kind"] == "bench": - return "measured", bench_statement(record) if record.get("timing") else "" verdicts = verdicts_of(record) by_frames = record["kind"] == "run" or check.get("observable") == "frame_hashes" if verdicts and by_frames: @@ -122,7 +116,7 @@ def result_of(record: dict) -> tuple[str, str]: def index_item(entry: mr.Stored, runs: int, source: dict, profiles: dict, - system_names: dict[str, str]) -> dict: + system_names: dict[str, str], ancestry: set[str] | None = None) -> dict: record = entry.record profile = profiles.get(entry.emulator) or {} check = record.get("check") or {} @@ -159,35 +153,26 @@ def index_item(entry: mr.Stored, runs: int, source: dict, profiles: dict, "runs": runs, "record_url": permalink(source["repo"], source["commit"], mr.records_path(entry.emulator), entry.line), "sqlite": f"downloads/measurements/{entry.emulator}.sqlite", - "host_short": short_host(record["host"]), - "setup": setup_of(record), - "groups": variant_groups(record), - "proves": proves(record["id"], profile), - "timing": ({"ms_per_frame": record["timing"]["ms_per_frame"]["median"], - "fps": record["timing"]["fps"]["median"], - "stdev": record["timing"]["ms_per_frame"]["stdev"]} - if record.get("timing") else None), + "proves": proves(ancestry or {record["id"]}, profile), } -def with_title(item: dict) -> dict: - return {**item, "title": short_title(item)} - - def build_index(stored: list[mr.Stored], source: dict | None, profiles: dict, system_names: dict[str, str] | None = None) -> dict: """One item per (emulator, subject): its latest record not superseded.""" by_subject: dict[tuple[str, str], list[mr.Stored]] = {} live_ids = {r["id"] for r in mr.live(s.record for s in stored)} + ancestors = lineage(stored) for entry in stored: - if entry.record["id"] in live_ids: + # A benchmark only chooses between variants: it stays in the records. + if entry.record["id"] in live_ids and entry.record["kind"] != "bench": by_subject.setdefault((entry.emulator, entry.record["subject"]), []).append(entry) items = [] for key in sorted(by_subject): entries = by_subject[key] latest = max(entries, key=lambda s: (s.record["recorded_at"], s.line)) - items.append(with_title(index_item(latest, len(entries), source or {}, profiles, - system_names or {}))) + items.append(index_item(latest, len(entries), source or {}, profiles, system_names or {}, + ancestors.get(latest.record["id"]))) items.sort(key=lambda item: (item["emulator"], item["date"], item["subject"])) return { "schema_version": INDEX_SCHEMA_VERSION, @@ -200,418 +185,10 @@ def build_index(stored: list[mr.Stored], source: dict | None, profiles: dict, } -def campaign_summary(root: Path, stored: list[mr.Stored], source: dict | None) -> dict: - """The last campaign pass and the findings still waiting for review. - - A finding's history is its lines under one key: the latest line is its - state, and only an open or flagged one is listed. - """ - runs = mr.read_campaign(root, mr.CAMPAIGN_RUNS) - latest: dict[str, dict] = {} - applied = 0 - for line in mr.read_campaign(root, mr.FINDINGS): - latest[line["key"]] = line - applied += line["status"] == "applied" - where = {s.record["id"]: s for s in stored} - links = {} - for line in latest.values(): - for rid in line["evidence"]: - found = where.get(rid) - if found and source and source.get("commit"): - links[rid] = permalink(source["repo"], source["commit"], - mr.records_path(found.emulator), found.line) - open_items = [{**line, "links": [links.get(rid) for rid in line["evidence"]]} - for _, line in sorted(latest.items()) if line["status"] in ("open", "flagged")] - return {"last_run": runs[-1] if runs else None, "passes": len(runs), "applied": applied, - "open": open_items} - - -def pack_summary(root: Path, source: dict | None) -> list[dict]: - """The latest pack probe of each platform, install path, system, core - and game, with a link to its line.""" - latest: dict[tuple, tuple[int, dict]] = {} - for number, line in enumerate(mr.read_campaign(root, mr.PACK_PROBES), 1): - key = (line["platform"], line["install"], line["system"], line["core"]["file"], - line["content"]["label"]) - latest[key] = (number, line) - items = [] - for key in sorted(latest): - number, line = latest[key] - url = (permalink(source["repo"], source["commit"], mr.records_path(mr.PACK_PROBES), number) - if source and source.get("commit") else None) - items.append({ - "platform": line["platform"], "platform_version": line["platform_version"], - "install": line["install"], "system": line["system"], "emulator": line["emulator"], - "core": line["core"]["file"].removesuffix("_libretro.so"), - "content": line["content"]["label"], "verdict": line["verdict"], - "date": line["probed_at"], "opened": len(line["files_opened"]), - "findings": len(line["findings"]), "id": line["id"], "record_url": url, - "files": [f"{f['path']} ({f['entry'] or 'written by the core'})" for f in line["files_opened"]], - "finding_list": [f"{f['kind']} {f['path']}" for f in line["findings"]], - "detail": (line.get("verdict_evidence") or {}).get("detail") or line.get("error") or "", - }) - return items - - -def by_platform(index: dict) -> dict[str, list[dict]]: - out: dict[str, list[dict]] = {} - for item in index.get("packs") or (): - out.setdefault(item["platform"], []).append(item) - return out - - -INSTALL_WORDS = {"pack": "pack", "installer": "installer"} - - -def _pack_row(item: dict) -> str: - link = f"[line]({item['record_url']})" if item["record_url"] else _code(item["id"]) - cells = [_plain(item["platform"]), _plain(item["system"]), INSTALL_WORDS[item["install"]], - _code(item["core"]), _plain(item["content"]), f"`{item['verdict']}`", - str(item["findings"]), link] - return "| " + " | ".join(cells) + " |" - - -def packs_section(items: list[dict]) -> list[str]: - """The Measurements page's table of packs tested the way a user installs - them: one row per platform, system, install path, core and game.""" - if not items: - return [] - columns = ["Platform", "System", "Installed by", "Core", "Game", "Verdict", "Findings", "Record"] - return [ - "## Packs", - "", - "A game started with the platform's BIOS folder as a user gets it, from the pack or " - f"from the installer ([how]({METHOD_PAGE}#packs)). A finding is a file the core looked " - "for that the folder lacks or holds under another path.", - "", - "| " + " | ".join(columns) + " |", - "|" + "|".join("---" for _ in columns) + "|", - *(_pack_row(item) for item in - sorted(items, key=lambda i: (i["platform"], i["system"], i["install"], i["core"], i["content"]))), - "", - ] - - -def _inert(value: object) -> str: - """Run output (a core's log line, a file name it opened) placed in raw - HTML as text: every character HTML, MDX or Markdown reads as markup, - quotes included, written as a character reference.""" - return _plain(value).replace('"', """).replace("'", "'") - - -def _pack_details(item: dict) -> list[str]: - def line(label: str, value: object) -> str: - return _sentence_html(label, _inert(value), markup_ready=True) - - lines = [ - line("Result", item["detail"]), - line("Core", f"{item['core']} ({item['emulator']})"), - line("Files the core opened", ", ".join(item["files"]) or "none"), - line("Findings", ", ".join(item["finding_list"]) or "none"), - f'

Full record.

' if item["record_url"] else "", - ] - return [line for line in lines if line] - - -def pack_tested_section(items: list[dict]) -> list[str]: - """The Tested block of a platform page, in the emulator pages' Tested - markup: one summary line, then, folded under one line as the emulator - pages fold their earlier tests, a row per game, core and install path - with its verdict chip and date, the files and findings in its details.""" - if not items: - return [] - works = sum(1 for i in items if i["verdict"] == "boots") - last = max(i["date"][:10] for i in items) - summary = (f"{len(items)} game start{'s' if len(items) != 1 else ''} with this BIOS folder as " - f"you install it · {works} work · last run {last}.") - links = (f'How · ' - f'All tests') - lines = ["## Tested", "", '
', - f'

{html.escape(summary)} {links}

', - '
', - "Each start, by system", - '"] - for item in sorted(items, key=lambda i: (i["system"], i["content"], i["core"], i["install"])): - title = (f"{_inert(item['system'])}, {_inert(short_content(item['content']))} " - f"{_inert(item['core'])}") - lines += [ - '
', - ("" - f'{title}' - f'{_chip({"status": item["verdict"]})}' - f'{INSTALL_WORDS[item["install"]]}' - f'{item["date"][:10]}'), - '
', - *_pack_details(item), - "
", - "
", - ] - return [*lines, "
", "
", ""] - - -# Characters a campaign value could use to leave its place on the page: HTML -# and MDX syntax ({ } opens an expression), code spans, emphasis and links. -_UNSAFE = {"&": "&", "<": "<", ">": ">", "{": "{", "}": "}", "`": "`", - "[": "[", "]": "]", "*": "*", "_": "_", "\\": "\", "|": "|"} - - -def _plain(value: object) -> str: - """Text a run produced (core output, a romfetch error, a profile name), - shown as text: every character that is markup on the page is a - character reference.""" - text = " ".join(str(value if value is not None else "").split()) - return "".join(_UNSAFE.get(char, char) for char in text) - - -def _sentence(value: object) -> str: - """A finding's statement as a sentence of the page: capital first.""" - text = _plain(value) - return text[:1].upper() + text[1:] - - -def _code(value: object) -> str: - """A value as code that cannot close its element or open an expression.""" - return f"{_plain(value)}" - - -def _value(value: object) -> str: - return _code(json.dumps(value, ensure_ascii=False)) if value is not None else "nothing" - - -def campaign_section(summary: dict | None) -> list[str]: - """The campaign's last pass and its open findings, for the explorer page.""" - if not summary or not summary.get("last_run"): - return [] - last = summary["last_run"] - counts = last["counts"] - lines = [ - "## Campaign", - "", - f"A maintainer starts a pass, on an idle computer, and nothing runs by itself " - f"([how]({METHOD_PAGE})). " - f"Last pass: {_plain(last['ended_at'][:10])}, {_plain(last['result'])}" - + (f" ({_plain(last['detail'])})" if last.get("detail") else "") + ". " - f"{int(counts['done'])} tests done, {int(counts['failed'])} failed, {int(counts['pending'])} waiting. " - f"{summary['applied']} profile fields were set from a run.", - "", - ] - if last["pending_reasons"]: - lines += ["| Waiting for | Tests |", "|-------------|------:|"] - lines += [f"| {_plain(reason)} | {int(number)} |" - for reason, number in sorted(last["pending_reasons"].items(), key=lambda kv: (-kv[1], kv[0]))] - lines.append("") - if last["failed"]: - lines += ["Failed tests:", ""] - lines += [f"- {_plain(item['emulator'])}, {_code(item['check'])}: {_plain(item['reason'])}" - for item in last["failed"]] - lines.append("") - lines += ["### Open findings", ""] - if not summary["open"]: - lines += ["None: every finding was settled by a rule or resolved by a later run.", ""] - return lines - lines += ["A finding is what a run showed that no rule settles. It waits for someone to " - "read the emulator's code.", ""] - for item in summary["open"]: - records = ", ".join(f"[{_plain(rid)}]({link})" if link else _code(rid) - for rid, link in zip(item["evidence"], item["links"])) - lines += [ - f"- **{_plain(item['emulator'])}**, {_code(item['file'])}, {_code(item['field'])} " - f"({_plain(item['status'])}): the profile says {_value(item['profile_says'])}, the run " - f"shows {_value(item['run_shows'])}. {_sentence(item['statement'])}. Records: {records}.", - "", - ] - return lines - - # -------------------------------------------------------------------------- # Coverage and region results # -------------------------------------------------------------------------- -CONTENT_FILE = Path("tests") / "campaign" / "content.yml" - - -def coverage(root: Path, stored: list[mr.Stored], profiles: dict | None = None) -> dict: - """Per system of the content list: its games, and which emulators ran - each one. A record counts for a system only when its emulator's profile - names that system: one catalog name can be a PlayStation disc and a - Saturn disc. A system no catalog covers comes with the reason and the - error romfetch returned.""" - import yaml # noqa: PLC0415 - - path = root / CONTENT_FILE - if not path.is_file(): - return {"systems": [], "uncovered": []} - data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} - ran: dict[str, set[str]] = {} - for entry in stored: - label = ((entry.record.get("inputs") or {}).get("content") or {}).get("label") - if label: - ran.setdefault(label, set()).add(entry.emulator) - systems = [] - for system, spec in (data.get("systems") or {}).items(): - games = [{"name": item["name"], "region": item.get("region"), - "emulators": sorted(e for e in ran.get(item["name"], ()) - if profiles is None - or system in ((profiles.get(e) or {}).get("systems") or ()))} - for item in spec.get("content") or ()] - listed = {item["name"] for item in spec.get("content") or ()} - # Runs of this system's emulators on games the list does not name. - other = sorted({e for label, emulators in ran.items() if label not in listed for e in emulators - if profiles is not None - and system in ((profiles.get(e) or {}).get("systems") or ())}) - systems.append({"system": system, "games": games, "other_games_by": other, - "tested": any(game["emulators"] for game in games)}) - uncovered = [{"system": system, "want": item.get("want"), "reason": " ".join(str(item["reason"]).split()), - "romfetch": item.get("romfetch") or {}} - for system, item in (data.get("uncovered") or {}).items()] - return {"systems": systems, "uncovered": uncovered} - - -def coverage_section(cov: dict | None, system_names: dict[str, str] | None = None) -> list[str]: - """The Coverage block of the explorer page: tested or not, and why not.""" - if not cov or not (cov.get("systems") or cov.get("uncovered")): - return [] - names = system_names or {} - tested = [s for s in cov["systems"] if s["tested"]] - lines = [ - "## Coverage", - "", - f"Test games come from [`{CONTENT_FILE.as_posix()}`]({REPO}/blob/main/{CONTENT_FILE.as_posix()}), " - f"one per region where a BIOS depends on it. {len(tested)} of {len(cov['systems'])} listed " - f"systems have a recorded run, and {len(cov['uncovered'])} " - f"{'system has' if len(cov['uncovered']) == 1 else 'systems have'} no test game.", - "", - "| System | Games | Tested by |", - "|--------|-------|-----------|", - ] - # Two ids of one console (sega-megacd, mega-cd) share their list: one row. - rows: dict[tuple[str, str], set[str]] = {} - others: dict[tuple[str, str], set[str]] = {} - for item in cov["systems"]: - row = (_plain(names.get(item["system"], item["system"])), - ", ".join(_plain(game["name"]) for game in item["games"])) - rows.setdefault(row, set()).update(e for game in item["games"] for e in game["emulators"]) - others.setdefault(row, set()).update(item.get("other_games_by") or ()) - for (name, games), by in rows.items(): - if by: - status = ", ".join(_plain(e) for e in sorted(by)) - elif others[(name, games)]: - status = ("not yet with these games, other games by " - + ", ".join(_plain(e) for e in sorted(others[(name, games)]))) - else: - status = "not yet: no run recorded" - lines.append(f"| {name} | {games} | {status} |") - lines.append("") - if cov["uncovered"]: - lines += ["Systems without a test game:", "", - "| System | Wanted | Why not | romfetch |", - "|--------|--------|---------|----------|"] - for item in cov["uncovered"]: - code = item["romfetch"].get("code") or "no source" - lines.append(f"| {_plain(names.get(item['system'], item['system']))} | {_plain(item['want'])} | " - f"{_plain(item['reason'])} | exit {_plain(item['romfetch'].get('exit'))}, " - f"{_code(code)} |") - lines.append("") - return lines - - -def region_results(root: Path, stored: list[mr.Stored], source: dict | None) -> list[dict]: - """The region outcomes the records hold (region_check.py), each record - linked at the commit the explorer links.""" - import region_check # noqa: PLC0415 - - lines = {s.record["id"]: s for s in stored} - results = region_check.collect(root, stored) - for result in results: - for item in result["variants"].values(): - found = lines.get(item["record"]) - item["url"] = (permalink(source["repo"], source["commit"], mr.records_path(found.emulator), - found.line) - if found and source and source.get("commit") else None) - return results - - -def site_region_results(root: Path = ROOT, repo: str = REPO) -> list[dict]: - """region_results with the records and commit the explorer uses.""" - stored = mr.read_records(root) - source = {"repo": repo, "commit": records_commit(root) if stored else None} - return region_results(root, stored, source) - - -REGION_SUBJECT = re.compile(r"^region-[a-z0-9_]+-[a-z0-9-]+-(full|own|others|pack)$") - -VARIANT_WORDS = {"full": "every regional BIOS", "own": "its own region's BIOS only", - "others": "the other regions' BIOS only", "pack": "the region's pack"} - - -REGION_SHORT = {"japan": "Japanese", "north-america": "US", "europe": "European"} -OUTCOME_WORDS = {True: "boots", False: "does not boot", None: "no verdict"} - - -def others_words(result: dict) -> str: - """What the run with only the other regions' files did, in a few words, - never calling a boot the other region's BIOS unless the core's log - names that file.""" - boots = result.get("others_boot") - if boots is not True: - return OUTCOME_WORDS[boots] - used = result.get("others_on") - if used == "builtin": - return "built-in BIOS, files refused" - if used and used.startswith("staged:"): - return f"boots on {used.removeprefix('staged:')}" - return "boots, BIOS not named" - - -def core_region_line(results: list[dict]) -> str: - """One core's outcome over its region games, in a sentence: each game - on its own BIOS, then with only the other regions' files.""" - own = {r["own_boots"] for r in results} - others = {r["others_boot"] for r in results} - used = {r.get("others_on") or "unknown" for r in results} - if own == {True} and others == {True} and used == {"builtin"}: - return "Each region's game boots on its own BIOS, and on its built-in BIOS with the others' files refused." - if own == {True} and others == {True} and all(u.startswith("staged:") for u in used): - return "Each region's game boots on its own BIOS, and on another region's file the log names." - if own == {True} and others == {False}: - return "Each region's game boots on its own BIOS only." - parts = [f"{REGION_SHORT.get(r['region'], r['region'])}: {OUTCOME_WORDS[r['own_boots']]}, " - f"with the others {others_words(r)}" for r in results] - return ". ".join(parts) + "." - - -def region_section(results: list[dict], level: int = 3) -> list[str]: - """What the region runs showed for one system: a line per core, the - games and their records in a fold.""" - if not results: - return [] - lines = ["#" * level + " Region check", "", - f"Each game ran with {VARIANT_WORDS['own']}, then with {VARIANT_WORDS['others']} " - f"([method](../{METHOD_PAGE})).", ""] - by_core: dict[str, list[dict]] = {} - for result in results: - by_core.setdefault(result["core"], []).append(result) - for core, items in by_core.items(): - lines.append(f"- **{_plain(core)}**: {_plain(core_region_line(items))}") - lines.append("") - rows = [] - for result in results: - links = ", ".join(_link(item.get("url"), _cell(VARIANT_WORDS[label])) - for label, item in result["variants"].items()) - names = ", ".join(f"{_cell(name)}" for name in result["selected"]) - read = (f" It reads {names} with every regional BIOS there " - f"({_cell(result['claim'])} against the profile)." if result["selected"] else "") - basis = ("" if result["judged_by"] == "verdict" - else " Judged against the run with every regional BIOS until reference frames are recorded.") - rows.append(f"
  • {_cell(result['core'])}, {_cell(result['game'])}: " - f"{_cell(result['sentence'])}{read}{basis} Records: {links}.
  • ") - # One line: MDX reads an HTML block split over lines as paragraphs. - lines += ['
    Each game and its records
    ", ""] - return lines - - # -------------------------------------------------------------------------- # SQLite # -------------------------------------------------------------------------- @@ -834,9 +411,6 @@ def build(public: Path, root: Path = ROOT, emulators_dir: Path = ROOT / "emulato source = {"repo": repo, "commit": commit or (records_commit(root) if stored else None)} profiles = load_profiles(emulators_dir) index = build_index(stored, source, profiles, system_names) - index["campaign"] = campaign_summary(root, stored, source) - index["coverage"] = coverage(root, stored, profiles) - index["packs"] = pack_summary(root, source) paths = [public / INDEX_PATH] (public / INDEX_PATH).parent.mkdir(parents=True, exist_ok=True) write_if_changed(str(public / INDEX_PATH), @@ -855,20 +429,61 @@ def build(public: Path, root: Path = ROOT, emulators_dir: Path = ROOT / "emulato # -------------------------------------------------------------------------- def by_emulator(index: dict) -> dict[str, list[dict]]: - """Each emulator's items, with the count of its findings still open.""" - opened: dict[str, int] = {} - for line in (index.get("campaign") or {}).get("open") or (): - opened[line["emulator"]] = opened.get(line["emulator"], 0) + 1 + """Each emulator's items. A pack probe's run proves a pack, not the + emulator's page.""" out: dict[str, list[dict]] = {} for item in index.get("items", []): - # A pack probe's run is shown on its platform's page, beside the pack. - if item["subject"].startswith(mr.PACK_SUBJECT_PREFIX): - continue - out.setdefault(item["emulator"], []).append( - {**item, "open_findings": opened.get(item["emulator"], 0)}) + if not item["subject"].startswith(mr.PACK_SUBJECT_PREFIX): + out.setdefault(item["emulator"], []).append(item) return out +ISSUE_SUBJECT = re.compile(r"^issue(?P\d+)-") + + +def proof_result(item: dict) -> str: + """What a record showed, in words, naming the BIOS each boot ran: a + boot on the core's built-in BIOS is never the staged file's.""" + def said(verdict: dict) -> str: + used = verdict.get("bios_used") or "unknown" + if verdict["verdict"] == BOOTS_BUILTIN: + return "boots on the core's built-in BIOS" + if verdict["verdict"] == "boots" and used.startswith("staged:"): + return f"boots on {used.removeprefix('staged:')}" + return verdict["verdict"].replace("_", " ") + verdicts = item.get("verdicts") or [] + if len(verdicts) == 1: + return said(verdicts[0]) + if verdicts: + return ". ".join(f"{v['variant']}: {said(v)}" for v in verdicts) + claim = item.get("claim") or f"a run of {item.get('content') or item['subject']}" + return f"{claim.rstrip('.')}, recorded before the boot oracle" + + +def proof_line(item: dict) -> str | None: + """One line for a record a page relies on, or None: a profile field its + `evidence:` names the record for, or an issue the record answers.""" + issue = ISSUE_SUBJECT.match(item["subject"]) + if not item.get("proves") and not issue: + return None + what = ", ".join(item.get("proves") or ()) or f"issue {issue['number']}" + text = f"{_cell(what)}: {_cell(proof_result(item))}".translate(_MARKDOWN) + target = _href(item.get("record_url")) + return f"- {text}" + (f" ([record]({target}))" if target else "") + "." + + +def proof_section(items: list[dict]) -> list[str]: + """The Proof lines of an emulator page: only the runs a claim of the + page rests on, one line each. Every other record stays on the + Measurements page.""" + lines = sorted({line for item in items if (line := proof_line(item))}) + if not lines: + return [] + return ["## Proof", "", + f"Real runs that back this page ([how they are made](../../{METHOD_PAGE.removesuffix('.md')}/)):", + "", *lines, ""] + + def _text(value: object) -> str: """Record text as the page shows it. A semicolon joining two clauses ends a sentence on the site: the record keeps the words as written.""" @@ -881,205 +496,31 @@ def _short(commit: str | None) -> str: return f"`{commit[:12]}`" if commit else "unknown" -# A verdict or a comparison, as the block says it: words and a chip kind. -RESULT_WORDS = { - "boots": ("works", "success"), - BOOTS_BUILTIN: ("works on its built-in BIOS", "success"), - "bios_error": ("BIOS error", "danger"), - "crash": ("crash", "danger"), - "hang": ("hang", "warning"), - "unknown": ("unknown", "muted"), - "mixed": ("depends on the file", "info"), - "same": ("same with every file", "muted"), - "differs": ("depends on the file", "info"), - "inconclusive": ("inconclusive", "warning"), - "no verdict": ("no verdict", "muted"), - "ran": ("ran", "muted"), -} -# Words a subject slug spells that read as one term. -TERMS = {"bios": "BIOS", "vcd": "Video CD", "cd": "CD", "jp": "JP", "us": "US", "eu": "EU", - "cr2": "CR2", "mpeg": "MPEG", "psx": "PSX", "hle": "HLE", "usa": "USA"} -# The campaign's own check names, as the block says them. -CHECK_NAMES = {"files": "full set", "none": "no BIOS", "negative": "wrong files", - "references": "reference run", "variants": "copies", "alias": "other name", - "path": "other path", "bench": "benchmark", "boot": "boot test", "alone": "file alone"} - - -def _words(text: str) -> list[str]: - return re.findall(r"[a-z0-9]+", str(text or "").lower()) - - -def short_content(label: str | None) -> str: - """A content label cut to the game's name: no source prefix, no - edition, region or identity detail.""" - text = str(label or "").strip() - if ":" in text.split("(")[0]: - text = text.split(":", 1)[1].strip() - for cut in (" - ", " (", ", "): - text = text.split(cut, 1)[0] - return text.strip() - - -def short_title(item: dict) -> str: - """A test's name in a few words: the file it varies, the game, and what - the subject adds to them (a build, a region, a check of the campaign).""" - thing = Path(str(item.get("variable") or "")).name - game = short_content(item.get("content")) - known = set(_words(thing)) | set(_words(item.get("content"))) | set(_words(game)) - known |= set(_words(Path(str((item.get("core") or {}).get("file") or "")).name.split("_libretro")[0])) - axis = [] - for word in _words(item.get("subject")): - if word in known or re.fullmatch(r"[0-9a-f]{8}", word): - continue - issue = re.fullmatch(r"issue(\d+)", word) - word = f"issue {issue[1]}" if issue else CHECK_NAMES.get(word, TERMS.get(word, word)) - if word not in axis: - axis.append(word) - head = ", ".join(part for part in (thing, game) if part) - title = f"{head} ({' '.join(axis)})" if axis and head else head or " ".join(axis) - return title or str(item.get("subject") or "") - - -OBSERVABLE_WORDS = {"video_sha1": "video", "frame_hashes": "screens", "audio_sha1": "audio", - "state_sha1": "save state", "frame_sha1": "last frame"} - - -def setup_of(record: dict) -> dict: - """What told two runs of one test apart, each as the words a title - would use: the campaign check, the core build, the core options, the - clock, the number of files tried and what was compared.""" - build = str(record["core"].get("build_command") or "") - patched = re.search(r"\b(patch|edit(?:ed|ing)?)\b", build, re.IGNORECASE) - named = re.search(r"\b([A-Z]+[0-9]+)\b", build) if patched else None - check = record.get("check") or {} - first = (_words(record["subject"]) or [""])[0] - options = {} - for key, value in sorted(record["inputs"].get("options", {}).items()): - options[key] = f"{value} region" if key.endswith("_region") else f"{key.rsplit('_', 1)[-1]} {value}" - observable = str(check.get("observable") or "") - return { - "check": CHECK_NAMES.get(first, ""), - "build": (f"{named[1]} patch" if named else "patched build") if patched else "published build", - "options": options, - "clock": "fixed clock" if "FAKETIME" in record["inputs"].get("env", {}) else "running clock", - "files": (f"{len(record['variants'])} file{'s' if len(record['variants']) != 1 else ''}" - if record["kind"] == "check" else ""), - "observable": (observable.split(":", 1)[1] if observable.startswith("state:") - else OBSERVABLE_WORDS.get(observable, observable)), - } - - -def _features(item: dict) -> dict[str, str]: - """A test's setup as named features, the options one by one.""" - setup = item.get("setup") or {} - out = {name: str(setup.get(name) or "") for name in ("build", "clock", "files", "observable")} - for key, value in (setup.get("options") or {}).items(): - out[f"option:{key}"] = value - return out - - -def distinct_titles(items: list[dict]) -> dict[str, str]: - """A title per item: its game and campaign check, then what sets it - apart from the other tests of the same file, game and core. - - A test whose setup is the common one of its group is named by its game. - Another adds the features where it leaves the common setup (a patched - build, a running clock, another region). Two titles still alike take - every feature that varies in the group, then the subject's own words. - Nothing is cut. - """ - out: dict[str, str] = {} - peers: dict[tuple, list[dict]] = {} - for item in items: - game = short_content(item.get("content")) or short_title(item) - peers.setdefault((item.get("variable") or "", game, item["core"].get("file")), []).append(item) - for (_, game, _), group in peers.items(): - features = [_features(item) for item in group] - names = sorted({name for f in features for name in f}, - key=lambda n: (n.startswith("option:"), ["build", "clock", "files", "observable"].index(n) - if not n.startswith("option:") else 0, n)) - common = {} - for name in names: - values = [f.get(name, "") for f in features] - ranked = sorted(set(values), key=lambda v: -values.count(v)) - # A tie has no common value: every test names its own. - tied = len(ranked) > 1 and values.count(ranked[0]) == values.count(ranked[1]) - common[name] = None if tied else ranked[0] - varying = [n for n in names if len({f.get(n, "") for f in features}) > 1] - - def title(item: dict, own: dict[str, str], every: bool, varying: list[str] = varying, - common: dict[str, str] = common, game: str = game) -> str: - words = [(item.get("setup") or {}).get("check", "")] - for name in varying: - value = own.get(name, "") - if every or value != common[name]: - words.append(value or f"no {name.rsplit('_', 1)[-1]}") - return ", ".join([game, *[w for w in words if w]]) - - for item, own in zip(group, features): - out[item["id"]] = title(item, own, every=False) - for every in (True, None): - seen: dict[str, int] = {} - for item in group: - seen[out[item["id"]]] = seen.get(out[item["id"]], 0) + 1 - for item, own in zip(group, features): - if seen[out[item["id"]]] > 1: - if every: - out[item["id"]] = title(item, own, every=True) - else: - axis = short_title({**item, "variable": None, "content": None}) - out[item["id"]] = f"{out[item['id']]}, {axis}" - return out - - -def short_host(host: dict) -> str: - """The computer as a reader names it: the CPU model and the OS.""" - cpu = str(host.get("cpu_model") or "unknown CPU") - cpu = re.sub(r"\(R\)|\(TM\)|\bCPU\b|@.*$|^\d+(?:st|nd|rd|th) Gen ", "", cpu) - cpu = " ".join(cpu.split()) - system = str(host.get("os") or "") - name = "Linux" if "Linux" in system else system.split("-")[0] or "unknown OS" - if "microsoft" in system.lower(): - name += " (WSL2)" - return f"{cpu}, {name}" - - -def variant_groups(record: dict) -> list[dict]: - """One entry per variant: the result group it falls in, and whether it - staged no file. A run that carries verdicts is grouped by verdict, a - comparison by the value its observable took.""" - check = record.get("check") or {} - position: dict[str, int] = {} - result: dict[str, str] = {} - if all("verdict" in v for v in record["variants"]) and ( - record["kind"] == "run" or check.get("observable") == "frame_hashes"): - for variant in record["variants"]: - position.setdefault(shown_verdict(variant), len(position)) - result[variant["label"]] = RESULT_WORDS[shown_verdict(variant)][0] - groups = {v["label"]: position[shown_verdict(v)] for v in record["variants"]} - else: - groups = {} - for number, group in enumerate((check.get("result") or {}).get("groups") or ()): - for label in group["variants"]: - groups[label] = number - result[label] = f"result {chr(ord('A') + number % 26)}" - varied = check.get("variable") - return [{"variant": v["label"], "group": groups.get(v["label"], 0), - "result": result.get(v["label"], ""), - "absent": bool(varied) and varied in v["system_files"] and v["system_files"][varied] is None} - for v in record["variants"]] - - -def proves(record_id: str, profile: dict) -> list[str]: - """The profile fields this record is evidence for, as `field of file`.""" +def proves(record_ids: set[str], profile: dict) -> list[str]: + """The profile fields a record is evidence for, as `field of file`. A + record judged again stands for the ones it supersedes.""" out = [] for entry in profile.get("files") or (): for name, ids in sorted((entry.get("evidence") or {}).items()): - if record_id in ids: + if record_ids & set(ids): out.append(f"{name} of {entry.get('name')}") return out +def lineage(stored: list[mr.Stored]) -> dict[str, set[str]]: + """Each record id with every id it supersedes, through the chain.""" + parent = {s.record["id"]: s.record.get("supersedes") for s in stored} + out = {} + for rid in parent: + seen = {rid} + node = parent[rid] + while node and node not in seen: + seen.add(node) + node = parent.get(node) + out[rid] = seen + return out + + # An MDX page reads a brace as the start of an expression, in text and in # an attribute alike. _BRACES = str.maketrans({"{": "{", "}": "}"}) @@ -1099,6 +540,11 @@ def _cell(value: object) -> str: return html.escape(text, quote=True).translate(_BRACES) +# The proof line is Markdown: a character that starts a span, a link or +# an image in a run's output stays a character. +_MARKDOWN = str.maketrans({c: f"&#{ord(c)};" for c in "`*_[]()~!|\\"}) + + _SAFE_HREF = re.compile(r"^(?:https?://[^\s\"'<>]+|/[^\s\"'<>]*|\.{0,2}/?[\w.~-][^\s\"'<>:]*)$") @@ -1109,199 +555,6 @@ def _href(url: object) -> str | None: ("javascript:", "data:", "vbscript:")) else None -def _link(url: object, text: str) -> str: - target = _href(url) - return f'{text}' if target else text - - -def _dots(item: dict) -> str: - groups = item.get("groups") or [] - names = ". ".join(f"{g['variant']}: {g['result']}" for g in groups) - dots = "".join( - f'' for g in groups) - return (f'' - f"{dots}") - - -def _chip(item: dict) -> str: - words, kind = RESULT_WORDS.get(item["status"], (item["status"], "muted")) - # A wrong-files test claims the core refuses zeros, half the file and - # other regions' dumps: when every one boots the claim fails, and - # "works" in green read as a pass. - if item["status"] == "boots" and str(item.get("subject") or "").startswith("negative-"): - words, kind = "accepts wrong files", "info" - return f'{_cell(words)}' - - -def _sentence_html(label: str, value: object, markup_ready: bool = False) -> str: - """One line of the details, or nothing: an empty value is never printed.""" - text = (str(value or "") if markup_ready else _cell(value)).strip().rstrip(".") - return f"

    {label}: {text}.

    " if text else "" - - -def _details(item: dict) -> list[str]: - core = item["core"] - build = _cell(core["library"]) + (f" at {_cell(core['commit'][:12])}" - if core.get("commit") else "") - reps = f"{item['variants']} variant{'s' if item['variants'] != 1 else ''}, " \ - f"{item['repetitions']} repetition{'s' if item['repetitions'] != 1 else ''} each" - if item["flags"]: - reps += f", {item['flags']} flag{'s' if item['flags'] != 1 else ''} raised" - runs = "" if item["runs"] == 1 else f", {item['runs']} runs, the latest shown" - lines = [ - _sentence_html("Claim", item.get("claim")), - _sentence_html("Result", item.get("statement")), - _sentence_html("Method", item.get("method")), - _sentence_html("Proves", ", ".join(item.get("proves") or [])), - _sentence_html("Ran on", item.get("host_short") or item["host"]), - _sentence_html("Core build", build and f"{build}. {reps}", markup_ready=True), - f'

    {_link(item["record_url"], "Full record")}{runs}.

    ', - ] - return [line for line in lines if line] - - -def tested_counts(items: list[dict]) -> dict: - tests = [i for i in items if i["kind"] != "bench"] - return {"tests": len(items), "confirmed": sum(1 for i in items if i.get("proves")), - "open": max((i.get("open_findings", 0) for i in items), default=0), - "benchmarks": len(items) - len(tests), - "last": max((i["date"][:10] for i in items), default="")} - - -def core_label(item: dict) -> str: - return Path(str(item["core"].get("file") or "")).name.split("_libretro")[0] - - -def _row(item: dict, title: str, page_host: str, main_core: str) -> list[str]: - elsewhere = (item.get("host_short") or item["host"]) != page_host - title = _cell(title) - if core_label(item) != main_core: - title += f' {_cell(core_label(item))}' - return [ - '
    ', - ("" - f'{title}' - f'{_chip(item)}' - f'{_dots(item)}' - f'{item["date"][:10]}' - + (f' {_attr(item.get("host_short"))}' if elsewhere else "") - + ""), - '
    ', - *_details(item), - "
    ", - "
    ", - ] - - -def by_file(items: list[dict]) -> list[tuple[str, list[dict]]]: - """The items under the file each varies, in order of first appearance; - the runs that vary none last.""" - groups: dict[str, list[dict]] = {} - for item in items: - groups.setdefault(Path(str(item.get("variable") or "")).name, []).append(item) - return sorted(groups.items(), key=lambda kv: kv[0] == "") - - -def folded(item: dict) -> bool: - """A row the block folds away: a test from before the boot oracle, or a - run that recorded reference frames for the oracle, which a reviewer - reads and a visitor does not.""" - return item["status"] == "no verdict" or str(item.get("subject") or "").startswith("references-") - - -def summary_line(items: list[dict], page_host: str) -> str: - """N tests, the confirmed and open counts when there are any, the last - run and its computer.""" - counts = tested_counts(items) - parts = [f"{counts['tests']} test{'s' if counts['tests'] != 1 else ''}"] - if counts["confirmed"]: - parts.append(f"{counts['confirmed']} confirmed in the profile") - if counts["open"]: - parts.append(f"{counts['open']} open finding{'s' if counts['open'] != 1 else ''}") - parts.append(f"last run {counts['last']} on {page_host}") - return " · ".join(parts) + "." - - -def tested_section(name: str, items: list[dict], - file_hrefs: dict[str, str] | None = None) -> list[str]: - """The Tested block of an emulator page, empty when nothing was recorded. - - The answer first: one summary line, then the tests under the file each - one varies, a row per test with its result chip, a dot per variant and - its date. What proves it (method, claim, computer, core build, record) - waits in each row's details. ``file_hrefs`` links a file, by its name - folded to lower case, to its card on the page. Tests recorded before the boot oracle fold - under one line, and benchmarks get their own table at the end. - """ - if not items: - return [] - hosts = [i.get("host_short") or i["host"] for i in items] - page_host = max(sorted(set(hosts)), key=hosts.count) - cores = [core_label(i) for i in items] - main_core = max(sorted(set(cores)), key=cores.count) - titles = distinct_titles(items) - links = (f'How · ' - f'All tests · ' - f'SQLite') - ordered = sorted(items, key=lambda i: (i["date"], i["subject"], core_label(i)), reverse=True) - benches = [i for i in ordered if i["kind"] == "bench"] - # The region runs (region_check.py) read as one outcome per game, on the - # system page: listed here they would be a row per run. - regional = [i for i in ordered if i["kind"] != "bench" and REGION_SUBJECT.match(i["subject"])] - ordered = [i for i in ordered if i not in regional] - earlier = [i for i in ordered if i["kind"] != "bench" and folded(i)] - current = [i for i in ordered if i["kind"] != "bench" and not folded(i)] - lines = ["## Tested", "", '
    ', - f'

    {_cell(summary_line(items, page_host))} {links}

    '] - if current or earlier: - lines.append('") - for file, group in by_file(current): - label = f"{_cell(file)}" if file else "No file varied" - href = (file_hrefs or {}).get(file.casefold()) if file else None - if href: - label = f'{label}' - lines.append(f'

    {label}

    ') - for item in group: - lines += _row(item, titles[item["id"]], page_host, main_core) - if current: - lines.append('

    A dot per file tried, ' - 'the same colour for the same result. ' - " No file staged.

    ") - if earlier: - old = sum(1 for i in earlier if i["status"] == "no verdict") - parts = [f"{old} test{'s' if old != 1 else ''} from before the boot oracle" if old else "", - (f"{len(earlier) - old} reference run{'s' if len(earlier) - old != 1 else ''}" - if len(earlier) - old else "")] - lines += ['
    ', - f"{_cell(' and '.join(p for p in parts if p))}"] - for item in earlier: - file = Path(str(item.get("variable") or "")).name - title = f"{file}, {titles[item['id']]}" if file else titles[item["id"]] - lines += _row(item, title, page_host, main_core) - lines.append("
    ") - if regional: - games = len({i["content"] for i in regional}) - lines.append(f'

    Region check: {len(regional)} runs of ' - f"{games} game{'s' if games != 1 else ''}, read on the system page.

    ") - if benches: - # One line: MDX reads a table split over lines as paragraphs. - table = ['' - ""] - for item in benches: - timing = item.get("timing") or {} - title = _cell(titles[item["id"]]) - if core_label(item) != main_core: - title += f' {_cell(core_label(item))}' - table.append( - f'' - f'' - f'') - lines.append("".join([*table, "
    Benchmarkms per framefpsstdevruns
    {_link(item["record_url"], title)}{timing.get("ms_per_frame", 0):.3f}{timing.get("fps", 0):.1f}{timing.get("stdev", 0):.3f}{item["repetitions"]}
    "])) - return [*lines, "
    ", ""] - - def hosts_table(index: dict) -> list[str]: hosts: dict[str, int] = {} for item in index.get("items", []): @@ -1323,7 +576,7 @@ def with_hosts(page: str, index: dict) -> str: return page.replace(HOSTS_MARKER, "\n".join(hosts_table(index)).rstrip("\n")) -def generate_explorer_page(index: dict, system_names: dict[str, str] | None = None) -> str: +def generate_explorer_page(index: dict) -> str: commit = index.get("commit") repo = index.get("repo") or "" source = (f"[`measurements/`]({repo}/tree/{commit}/{mr.RECORDS_DIR}) at {_short(commit)}" @@ -1340,14 +593,13 @@ def generate_explorer_page(index: dict, system_names: dict[str, str] | None = No markup.button("Index as JSON", "data/measurements/index.json"), ]), "", - "Each row is the latest record of one test, with its method and the computer it " - "ran on. One SQLite file per emulator is linked from its page.", + "Each row is the latest record of one test, with its verdict, the BIOS it ran, its " + "method and the computer it ran on. Runs are made on demand: to settle a dispute or an " + "issue, to back a correction sent to a platform, or to check the packs before a release. " + "One SQLite file per emulator sits under `downloads/measurements/`.", "", "", "", - *packs_section(index.get("packs") or []), - *campaign_section(index.get("campaign")), - *coverage_section(index.get("coverage"), system_names), ]) diff --git a/tests/test_campaign.py b/tests/test_campaign.py index bba7979c8..e27c6c393 100644 --- a/tests/test_campaign.py +++ b/tests/test_campaign.py @@ -34,7 +34,6 @@ import campaign import campaign_profiles import campaign_rules as rules import measurement_records as mr -import sitemeasurements TMP = REPO_ROOT / "tmp" / "tests" @@ -1003,50 +1002,6 @@ class Registry(unittest.TestCase): self.assertEqual(campaign.autorun.job_env(job)[campaign.LOCK_HELD_ENV], "1") -class SitePage(unittest.TestCase): - def test_last_pass_and_open_findings(self) -> None: - summary = { - "last_run": {"ended_at": "2026-10-27T10:00:00+00:00", "result": "complete", "detail": "", - "counts": {"done": 12, "failed": 1, "pending": 3}, - "pending_reasons": {"standalone": 3}, - "failed": [{"emulator": "fake", "check": "fake/x/y/bench", "reason": "timeout"}]}, - "passes": 1, "applied": 2, - "open": [{"emulator": "fake", "file": "bios.bin", "field": "required", "status": "open", - "profile_says": True, "run_shows": False, "statement": "the core boots without it", - "evidence": ["2026-10-27-aaaaaaaaaaaa"], "links": ["https://x/l#L1"]}], - } - page = "\n".join(sitemeasurements.campaign_section(summary)) - self.assertIn("12 tests done, 1 failed, 3 waiting", page) - self.assertIn("| standalone | 3 |", page) - self.assertIn("the profile says true, the run shows false", page) - self.assertIn("[2026-10-27-aaaaaaaaaaaa](https://x/l#L1)", page) - self.assertIn("false. The core boots without it. Records:", page) - self.assertEqual(sitemeasurements.campaign_section({"last_run": None}), []) - - def test_observed_values_cannot_leave_their_place(self) -> None: - # A value comes from a core's output or a romfetch error: it may hold - # backticks, MDX braces and HTML. - hostile = "``}{import x}`` | [a](javascript:x) *b*" - summary = { - "last_run": {"ended_at": "2026-10-27T10:00:00+00:00", "result": "paused", "detail": hostile, - "counts": {"done": 0, "failed": 1, "pending": 1}, - "pending_reasons": {hostile: 1}, - "failed": [{"emulator": hostile, "check": hostile, "reason": hostile}]}, - "passes": 1, "applied": 0, - "open": [{"emulator": "fake", "file": hostile, "field": hostile, "status": "open", - "profile_says": {"k": hostile}, "run_shows": [hostile], "statement": hostile, - "evidence": ["2026-10-27-aaaaaaaaaaaa"], "links": [None]}], - } - page = "\n".join(sitemeasurements.campaign_section(summary)) - for raw in (" None: import core_probe # noqa: PLC0415 @@ -269,11 +266,12 @@ class ReadAndIndex(Workspace): status, statement = sm.result_of(record) self.assertEqual(status, sm.BOOTS_BUILTIN) self.assertIn("built-in BIOS", statement) - item = {**SitePages.ITEM, "host_short": "h", "kind": "run", "status": status, "statement": statement, - "groups": sm.variant_groups(record), "verdicts": sm.verdicts_of(record)} - text = "\n".join(sm.tested_section("alpha", [item])) - self.assertIn("works on its built-in BIOS", text) - self.assertNotIn("other region", text) + # The proof line a profile field rests on names the BIOS the core ran. + item = {**SitePages.ITEM, "kind": "run", "status": status, "statement": statement, + "verdicts": sm.verdicts_of(record), "proves": ["required of scph5501.bin"]} + line = sm.proof_line(item) + self.assertIn("required of scph5501.bin: boots on the core's built-in BIOS", line) + self.assertNotIn("other region", line) def test_links_name_the_last_commit_that_touched_the_records(self) -> None: first = self.repository() @@ -467,157 +465,49 @@ class SitePages(unittest.TestCase): "sqlite": "downloads/measurements/beetle_saturn.sqlite", } - def test_tested_block(self) -> None: - self.assertEqual(sm.tested_section("beetle_saturn", []), []) - text = "\n".join(sm.tested_section("beetle_saturn", [self.ITEM])) - self.assertTrue(text.startswith("## Tested\n")) - for expected in ("same with every file", "Method: Twice per card. Every input fixed.", - "Ran on: Test CPU, 12 CPUs", self.ITEM["record_url"], - "1 flag raised", "2 runs", "downloads/measurements/beetle_saturn.sqlite", - "wiki/measurements/", "<image>", "1 test · last run 2026-10-27", - '

    mpeg_card.bin

    ', - 'class="rb-tested-legend"', "No file staged."): - self.assertIn(expected, text) - self.assertNotIn("confirmed", text) - self.assertNotIn(" open", text) - def item(self, **values: object) -> dict: - return {**self.ITEM, "host_short": "Test CPU, Linux", **values} + return {**self.ITEM, **values} - def test_summary_counts(self) -> None: - items = [self.item(proves=["hle_fallback of a.bin"], open_findings=2), - self.item(id="b", subject="b", date="2026-10-28T00:00:00+00:00", open_findings=2), - self.item(id="c", kind="bench", status="measured", subject="c", - timing={"ms_per_frame": 1.5, "fps": 660.0, "stdev": 0.01})] - self.assertEqual(sm.tested_counts(items), {"tests": 3, "confirmed": 1, "open": 2, "benchmarks": 1, - "last": "2026-10-28"}) - text = "\n".join(sm.tested_section("beetle_saturn", items)) - self.assertIn("3 tests · 1 confirmed in the profile · 2 open findings · last run 2026-10-28 " - "on Test CPU, Linux.", text) - self.assertIn("1.500660.0", text) + def test_only_the_runs_a_page_rests_on_get_a_proof_line(self) -> None: + self.assertIsNone(sm.proof_line(self.item(proves=[]))) + self.assertEqual(sm.proof_section([self.item(proves=[])]), []) + line = sm.proof_line(self.item(proves=["hle_fallback of mpeg_card.bin"], verdicts=[ + {"variant": "default", "verdict": "boots", "bios_used": "staged:mpeg_card.bin", "detail": "d"}])) + self.assertEqual(html.unescape(line), f"- hle_fallback of mpeg_card.bin: boots on mpeg_card.bin " + f"([record]({self.ITEM['record_url']})).") + issue = sm.proof_line(self.item(subject="issue85-lunar-opening-movie", proves=[], verdicts=[], + claim=None, content="Lunar")) + self.assertTrue(issue.startswith("- issue 85: a run of Lunar, recorded before the boot oracle")) + section = sm.proof_section([self.item(proves=["required of a.bin"], verdicts=[])]) + self.assertEqual(section[0], "## Proof") - def test_wrong_files_that_all_boot_fail_the_claim(self) -> None: - negative = self.item(subject="negative-scph5501-bin-x", status="boots") - self.assertIn(">accepts wrong files", sm._chip(negative)) - self.assertIn('rb-badge-info', sm._chip(negative)) - self.assertIn(">works", sm._chip(self.item(subject="alone-scph5501-bin-x", status="boots"))) - - def test_the_file_label_links_its_card(self) -> None: - text = "\n".join(sm.tested_section("beetle_saturn", [self.ITEM], - {"mpeg_card.bin": "#file-mpeg-card-bin"})) - self.assertIn('mpeg_card.bin', text) - - def test_short_titles(self) -> None: - cases = [ - ({"subject": "mpeg-card-lunar-cr2-build", "variable": "mpeg_card.bin", - "content": "Redump: Lunar - Silver Star Story Complete (Japan), T-27904G V1.00"}, - "mpeg_card.bin, Lunar (CR2 build)"), - ({"subject": "negative-scph5501-bin-mednafen-psx-bust-a-move-2-arcade-edition-usa", - "variable": "scph5501.bin", "content": "Bust-A-Move 2 - Arcade Edition (USA)", - "core": {"file": "mednafen_psx_libretro.so"}}, - "scph5501.bin, Bust-A-Move 2 (wrong files)"), - ({"subject": "issue85-lunar-opening-movie", "variable": None, - "content": "Redump: Lunar - Silver Star Story Complete (Japan), T-27904G V1.00"}, - "Lunar (issue 85 opening movie)"), - ({"subject": "with-command-auth-device-and-command-get-9a211715", "variable": "mpeg_card.bin", - "content": "Redump: Lunar"}, - "mpeg_card.bin, Lunar (with command auth device and get)"), - ] - for item, title in cases: - self.assertEqual(sm.short_title(item), title) - self.assertEqual(sm.short_host({"cpu_model": "13th Gen Intel(R) Core(TM) i9-13900K", - "os": "Linux-6.18.54.1-microsoft-standard-WSL2-x86_64"}), - "Intel Core i9-13900K, Linux (WSL2)") - - def test_rows_of_one_file_never_read_alike(self) -> None: - def item(rid: str, build: str, files: int, observable: str, clock: bool = True) -> dict: - record = {"core": {"build_command": build}, "subject": "mpeg-card-lunar", "kind": "check", - "check": {"observable": observable}, "variants": [{}] * files, - "inputs": {"options": {"beetle_saturn_mpeg_card": "enabled"}, - "env": {"FAKETIME": "x"} if clock else {}}} - return self.item(id=rid, content="Redump: Lunar - Silver Star Story", setup=sm.setup_of(record)) - items = [item("a", "make -j8", 11, "video_sha1"), - item("b", "make -j8 after editing cdb.c", 11, "video_sha1"), - item("c", "make -j10, with the CR2 patch to cdb.c", 3, "state:MPEG.AuthState"), - item("d", "make -j8", 11, "video_sha1", clock=False)] - titles = sm.distinct_titles(items) - self.assertEqual(titles, { - "a": "Lunar", - "b": "Lunar, patched build", - "c": "Lunar, CR2 patch, 3 files, MPEG.AuthState", - "d": "Lunar, running clock"}) - self.assertEqual(len(set(titles.values())), len(items)) - self.assertNotIn("...", " ".join(titles.values())) - # Alone in its file, a test is named by its game. - self.assertEqual(sm.distinct_titles(items[:1]), {"a": "Lunar"}) + def test_the_removed_blocks_are_gone(self) -> None: + for name in ("tested_section", "region_section", "core_region_line", "campaign_section", + "coverage_section", "packs_section", "pack_tested_section", "bench_statement"): + self.assertFalse(hasattr(sm, name), name) + docs = REPO_ROOT / "website" / "src" / "content" / "docs" + pages = [*docs.glob("emulators/*.md*"), *docs.glob("systems/*.md*"), + *docs.glob("platforms/*.md*"), *docs.glob("measurements*.md*")] + if not pages: + self.skipTest("the site has not been generated") + for page in pages: + text = page.read_text(encoding="utf-8") + for removed in ("rb-tested", "rb-region-runs", "## Tested", "## Region check", + "ms per frame", "## Campaign", "## Coverage", "## Packs"): + self.assertNotIn(removed, text, f"{page.name}: {removed}") def test_run_output_is_text_never_markup(self) -> None: nasty = "`code` {expr} [x](javascript:alert(1)) \"q\" 'q' **b**" - item = self.item( - claim=nasty, method=nasty, statement=nasty, host=nasty, host_short=nasty, content=nasty, - variable=f"{nasty}.bin", subject="s", title=nasty, proves=[nasty], runs=2, flags=1, - core={"library": nasty, "commit": "{``}", "file": f"{nasty}_libretro.so"}, - groups=[{"variant": nasty, "group": 0, "result": nasty, "absent": False}], - record_url="javascript:alert(1)") - bench = self.item(id="b", kind="bench", status="measured", subject="bench", title=nasty, - content=nasty, record_url="java\nscript:x", - timing={"ms_per_frame": 1.0, "fps": 1.0, "stdev": 0.0}) - earlier = self.item(id="e", status="no verdict", subject="e", content=nasty, claim=nasty) - text = "\n".join(sm.tested_section("alpha", [item, bench, earlier])) - self.assertNotIn("b", text) - self.assertNotIn("code", text) - self.assertNotRegex(text, r'="[^"]*"q"') - self.assertIn("<script>", text) - self.assertIn("{expr}", text) - - def test_no_empty_sentence(self) -> None: - text = "\n".join(sm.tested_section("beetle_saturn", [self.item(claim=None, method=None, statement="", - kind="run", status="ran")])) - self.assertNotRegex(text, r"\s*\.|:\s*\.\s|`ran`\. \.") - self.assertNotIn("Claim:", text) - self.assertNotIn("Method:", text) - - def test_dots_follow_the_result_groups(self) -> None: - record = full_record("alpha", "boot", "2026-10-06T10:00:00+00:00", values=("1" * 40, "2" * 40, "1" * 40)) - record["check"]["variable"] = "card.bin" - record["variants"][2]["system_files"] = {"card.bin": None} - groups = sm.variant_groups(record) - self.assertEqual([g["group"] for g in groups], - [i for i, group in enumerate(record["check"]["result"]["groups"]) - for _ in group["variants"]][:1] + [1, 0]) - self.assertEqual([g["absent"] for g in groups], [False, False, True]) - text = "\n".join(sm.tested_section("alpha", [self.item(groups=groups)])) - strip = text.split('class="rb-dots"', 1)[1].split("", 1)[0] - self.assertEqual(re.findall(r'rb-dot rb-dot-(\d)( rb-dot-empty)?"', strip), - [("0", ""), ("1", ""), ("0", " rb-dot-empty")]) - self.assertIn('aria-label="3 variants. v0: result A. v1: result B. v2: result A"', text) - - def test_the_details_hold_the_method_and_the_host(self) -> None: - text = "\n".join(sm.tested_section("beetle_saturn", [self.item(runs=1)])) - details = text.split('
    ', 1)[1].split("
    ", 1)[0] - self.assertIn("Twice per card", details) - self.assertIn("Ran on: Test CPU, Linux.", details) - self.assertIn(f'Full record.', details) - self.assertNotIn("1 run", details) - - def test_the_beetle_saturn_block_stays_short(self) -> None: - stored = mr.read_records(REPO_ROOT) - profiles = sm.load_profiles(REPO_ROOT / "emulators") - index = sm.build_index(stored, {"repo": REPO_URL, "commit": COMMIT}, profiles) - items = sm.by_emulator(index).get("beetle_saturn") - if not items: - self.skipTest("no beetle_saturn record") - block = "\n".join(sm.tested_section("beetle_saturn", items)) - # What a reader sees before opening anything: the closed details hide their body. - shown = re.sub(r'
    .*?
    ', " ", block, flags=re.S) - shown = re.sub(r'
    .*?
    \s*', " ", shown, - flags=re.S) - shown = re.sub(r"<[^>]+>", " ", shown) - self.assertLess(len(re.findall(r"[A-Za-z0-9][\w.'-]*", shown)), 120, shown) + line = sm.proof_line(self.item(proves=[nasty], verdicts=[ + {"variant": nasty, "verdict": nasty, "bios_used": f"staged:{nasty}", "detail": nasty}, + {"variant": nasty, "verdict": "boots", "bios_used": "builtin", "detail": nasty}], + record_url="javascript:alert(1)")) + self.assertNotIn(" None: page = sm.with_hosts(f"x\n\n{sm.HOSTS_MARKER}\n", diff --git a/tests/test_pack_probe.py b/tests/test_pack_probe.py index d2fbd5aa3..b62253669 100644 --- a/tests/test_pack_probe.py +++ b/tests/test_pack_probe.py @@ -218,43 +218,14 @@ class Line(unittest.TestCase): class SitePages(unittest.TestCase): - """The Measurements page and the platform pages show the latest probe.""" + """A pack probe runs before a release: its records stay in measurements/ + and reach no page.""" - def test_latest_line_per_probe_with_its_link(self) -> None: + def test_the_pack_blocks_are_gone(self) -> None: import sitemeasurements as sm - root = TMP / "site" - shutil.rmtree(root, ignore_errors=True) - root.mkdir(parents=True) - first = Line.line(None) - later = {**first, "probed_at": "2026-10-28T12:00:00+00:00", "verdict": "boots"} - other = {**first, "platform": "batocera", "install": "installer"} - for line in (first, later, other): - pp.append_line(line, root) - items = sm.pack_summary(root, {"repo": "https://example.org/r", "commit": "c" * 40}) - self.assertEqual([(i["platform"], i["install"], i["verdict"]) for i in items], - [("batocera", "installer", "unknown"), ("retroarch", "pack", "boots")]) - self.assertEqual(items[1]["record_url"], - f"https://example.org/r/blob/{'c' * 40}/measurements/pack-probes.jsonl#L2") - self.assertEqual(items[1]["core"], "mednafen_saturn") - explorer = "\n".join(sm.packs_section(items)) - self.assertIn("| Platform | System |", explorer) - self.assertIn("| retroarch | sega-saturn | pack | mednafen_saturn | Game (Japan) " - "| `boots` | 1 | [line](", explorer) - page = sm.pack_tested_section(sm.by_platform({"packs": items})["retroarch"]) - text = "\n".join(page) - self.assertEqual(page[0], "## Tested") - self.assertIn("1 game start with this BIOS folder as you install it · 1 work · last run 2026-10-28.", - text) - self.assertIn("wiki/measurements/#packs", text) - # The emulator pages' row markup and chip, the install path in the third column. - self.assertIn('works', - text) - self.assertIn('pack', text) - self.assertIn("sega-saturn, Game mednafen_saturn", text) - self.assertIn("Files the core opened: sega_101.bin (sega_101.bin).", text) - self.assertIn("Findings: missing_declared x.bin.", text) - self.assertNotIn("| retroarch |", text) + for name in ("pack_summary", "packs_section", "pack_tested_section", "by_platform"): + self.assertFalse(hasattr(sm, name), name) def test_pack_runs_stay_off_the_emulator_page(self) -> None: import sitemeasurements as sm @@ -266,30 +237,14 @@ class SitePages(unittest.TestCase): self.assertEqual([i["subject"] for i in sm.by_emulator(index)["beetle_saturn"]], ["boot-sega-101-bin"]) - def test_run_output_is_inert(self) -> None: - """A file name or log line from a third-party core reaches the page - as text: no tag, link, code span or MDX expression comes out of it.""" + def test_a_markdown_link_keeps_only_safe_targets(self) -> None: + """A Markdown link in site text keeps only http(s) and relative targets.""" import sitemarkup - import sitemeasurements as sm - hostile = "`x` {alert(1)} [x](javascript:alert(1)) \"q\" 'q' **b**" - item = {"platform": "retroarch", "platform_version": "1", "install": "pack", - "system": hostile, "emulator": hostile, "core": hostile, "content": hostile, - "verdict": "boots", "date": "2026-10-28T00:00:00+00:00", "opened": 1, "findings": 1, - "id": "2026-10-28-0123456789ab", "record_url": "https://example.org/r#L1", - "files": [hostile], "finding_list": [hostile], "detail": hostile} - text = "\n".join(sm.pack_tested_section([item])) - for raw in ("", "b", "**b", - "href=\"javascript", '"q"', "'q'"): - self.assertNotIn(raw, text) - self.assertIn("<script>", text) - self.assertIn("{alert(1)}", text) - # A Markdown link in other site text keeps only http(s) and relative targets. self.assertEqual(sitemarkup.inline_html("[x](javascript:alert(1))"), "[x](javascript:alert(1))") self.assertEqual(sitemarkup.inline_html('[x](https://a.org/"onmouseover=1)'), 'x') self.assertEqual(sitemarkup.inline_html("[x](../a.md)"), 'x') - self.assertEqual(sm.pack_tested_section([]), []) class Lock(unittest.TestCase): diff --git a/tests/test_region_check.py b/tests/test_region_check.py index 09df7c33c..f6d9589d3 100644 --- a/tests/test_region_check.py +++ b/tests/test_region_check.py @@ -170,10 +170,6 @@ class Outcome(unittest.TestCase): self.assertEqual(text, "The US BIOS boots the US game, and on its built-in BIOS with the " "others' files refused.") self.assertNotIn("so do the other regions", text) - import sitemeasurements as sm - line = sm.core_region_line([result]) - self.assertIn("built-in BIOS", line) - self.assertNotIn("on the other regions' BIOS", line) def test_no_own_run(self) -> None: self.assertEqual(rc.sentence("japan", rc.outcome({}, self.ENTRIES, "japan")), @@ -243,75 +239,25 @@ class RequiredFromTheRegionRuns(unittest.TestCase): class SitePages(unittest.TestCase): - def test_region_section_links_every_record(self) -> None: - import sitemeasurements as sm - - records = {"full": record("f", Outcome.ALL, ["scph5500.bin"], [[0, "a"]], "boots"), - "own": record("o", ["scph5500.bin"], ["scph5500.bin"], [[0, "a"]], "boots"), - "others": record("x", Outcome.ALL[1:], [], [[0, "b"]], "bios_error")} - result = rc.outcome(records, Outcome.ENTRIES, "japan") - result.update(core="mednafen_psx", game="Puzzle Bobble 2 (Japan)", - sentence=rc.sentence("japan", result)) - for label, item in result["variants"].items(): - item["url"] = f"https://example.org/{label}" - text = "\n".join(sm.region_section([result])) - self.assertIn("- **mednafen_psx**: Each region's game boots on its own BIOS only.", text) - self.assertIn("The Japanese BIOS boots the Japanese game, the other regions' BIOS do not.", text) - # The per-game lines and their records wait in a closed fold. - self.assertIn('
    ', text) - self.assertIn('its own region's BIOS only', text) - self.assertIn("scph5500.bin", text) - self.assertNotIn("reference frames are recorded", text) - self.assertEqual(sm.region_section([]), []) - - def test_the_emulator_page_folds_region_runs_into_one_line(self) -> None: + def test_a_region_run_reaches_a_page_only_as_the_proof_of_a_field(self) -> None: + """No page shows region outcomes. The Beetle PSX page names the runs + its required: false rests on, each with the BIOS the core ran.""" import measurement_records as mr import sitemeasurements as sm stored = mr.read_records(ROOT) index = sm.build_index(stored, {"repo": sm.REPO, "commit": "0" * 40}, sm.load_profiles(ROOT / "emulators")) items = sm.by_emulator(index).get("beetle_psx") or [] - regional = [i for i in items if sm.REGION_SUBJECT.match(i["subject"])] - if not regional: - self.skipTest("no region record") - text = "\n".join(sm.tested_section("beetle_psx", items)) - self.assertIn(f"Region check: {len(regional)} runs of 3 games, read on the system page.", text) - for item in regional: - self.assertNotIn(item["record_url"], text) - - def test_coverage_names_tested_and_uncovered_systems(self) -> None: - import sitemeasurements as sm - - cov = {"systems": [{"system": "sony-playstation", "tested": True, "games": [ - {"name": "A (USA)", "region": "north-america", "emulators": ["beetle_psx"]}]}, - {"system": "sega-32x", "tested": False, "games": [ - {"name": "B (World)", "region": None, "emulators": []}]}], - "uncovered": [{"system": "msx", "want": "Gradius", "reason": "No MSX catalog.", - "romfetch": {"exit": 1, "code": "unknown_system"}}]} - text = "\n".join(sm.coverage_section(cov, {"sony-playstation": "PlayStation"})) - self.assertIn("1 of 2 listed systems have a recorded run, and 1 system has no test game", text) - self.assertIn("| PlayStation | A (USA) | beetle_psx |", text) - self.assertIn("| sega-32x | B (World) | not yet: no run recorded |", text) - self.assertIn("exit 1, unknown_system", text) - - def test_one_catalog_name_on_two_systems_counts_only_where_it_ran(self) -> None: - import measurement_records as mr - import sitemeasurements as sm - - label = "Bust-A-Move 2 - Arcade Edition (USA)" - stored = [mr.Stored("beetle_psx", 1, {"inputs": {"content": {"label": label}}})] - cov = sm.coverage(ROOT, stored, {"beetle_psx": {"systems": ["sony-playstation"]}}) - by_system = {item["system"]: item for item in cov["systems"]} - self.assertTrue(by_system["sony-playstation"]["tested"]) - self.assertFalse(by_system["sega-saturn"]["tested"]) - - def test_coverage_reads_the_real_content_list(self) -> None: - import sitemeasurements as sm - - cov = sm.coverage(ROOT, []) - systems = {item["system"] for item in cov["systems"]} - self.assertIn("sony-playstation", systems) - self.assertTrue(all(item["romfetch"].get("exit") for item in cov["uncovered"])) + if not items: + self.skipTest("no beetle_psx record") + text = "\n".join(sm.proof_section(items)) + self.assertNotIn("Region check", text) + lines = [line for line in text.splitlines() if line.startswith("- ")] + self.assertEqual(len(lines), sum(1 for i in items if i["proves"])) + required = [line for line in lines if line.startswith("- required of scph55")] + self.assertEqual(len(required), 3) + for line in required: + self.assertIn("boots on the core's built-in BIOS", line) if __name__ == "__main__": diff --git a/tests/test_site_r3_emulators.py b/tests/test_site_r3_emulators.py index 17dfef007..934ec0a19 100644 --- a/tests/test_site_r3_emulators.py +++ b/tests/test_site_r3_emulators.py @@ -192,15 +192,6 @@ class AnElementBudget(unittest.TestCase): self.assertLess(page.elements, validate_site.MAX_PAGE_ELEMENTS) -class TestedFilesLeadToTheirCard(unittest.TestCase): - def test_a_name_or_alias_finds_the_card(self): - files = [{"name": "scph5501.bin", "aliases": ["SCPH-5501.BIN"]}, {"name": "other.bin"}] - hrefs = gs._tested_file_hrefs("x", files) - self.assertEqual(hrefs["scph5501.bin"], "#file-scph5501-bin") - self.assertEqual(hrefs["scph-5501.bin"], "#file-scph5501-bin") - self.assertEqual(hrefs["other.bin"], "#file-other-bin") - - if __name__ == "__main__": unittest.main() diff --git a/website/src/components/MeasurementsExplorer.astro b/website/src/components/MeasurementsExplorer.astro index 9fca437d6..6c8a361d7 100644 --- a/website/src/components/MeasurementsExplorer.astro +++ b/website/src/components/MeasurementsExplorer.astro @@ -105,11 +105,11 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/'); const tr = document.createElement('tr'); tr.append(text('td', item.date.slice(0, 10))); const emu = document.createElement('td'); - emu.append(item.profiled ? link(`${base}emulators/${item.emulator}/#tested`, item.emulator_name) : text('span', item.emulator_name)); + emu.append(item.profiled ? link(`${base}emulators/${item.emulator}/`, item.emulator_name) : text('span', item.emulator_name)); if (item.system_names.length) emu.append(text('small', item.system_names.join(', '))); tr.append(emu); const test = document.createElement('td'); - test.append(text('strong', item.claim ?? `Benchmark of ${item.content ?? 'the core alone'}`)); + test.append(text('strong', item.claim ?? `A run of ${item.content ?? 'the core alone'}`)); if (item.method) { const details = document.createElement('details'); details.append(text('summary', 'Method'), text('p', item.method)); diff --git a/website/src/styles/retrobios.css b/website/src/styles/retrobios.css index 748ed0b21..4b560aa21 100644 --- a/website/src/styles/retrobios.css +++ b/website/src/styles/retrobios.css @@ -1051,88 +1051,3 @@ starlight-tabs [role='tab'] { white-space: nowrap; } @media (min-width: 50rem) { header .site-title img { height: calc(4rem - 2 * var(--sl-nav-pad-y)); width: auto; } } - -/* The Tested block of an emulator page (scripts/sitemeasurements.py): a - summary line, then one row per test, each a
    whose summary is - the row and whose body is the proof. Rows stack into two lines on a - phone. A dot per variant, its group in colour and its name in a title - and the strip's label; a variant that staged no file is a ring. */ -.sl-markdown-content .rb-tested-summary { color: var(--rb-text-secondary); font-size: 0.9rem; } -.sl-markdown-content :is(.rb-tested-head, .rb-tested-row > summary) { - display: grid; - grid-template-columns: 1.25rem minmax(0, 1fr) 8.5rem 9rem 6rem; - gap: 0.5rem; - align-items: center; -} -.sl-markdown-content .rb-tested-head { - margin-top: 0.75rem; - padding: 0 0 0.25rem; - border-bottom: 1px solid var(--rb-border-strong); - color: var(--rb-text-secondary); - font-size: 0.8rem; - font-weight: 600; -} -.sl-markdown-content .rb-tested-head::before { content: ""; } -/* Starlight draws a side rule on every details: a row has a rule below only. */ -.sl-markdown-content details:is(.rb-tested-row, .rb-tested-earlier) { border-inline-start: 0; padding-inline-start: 0; } -.sl-markdown-content details.rb-tested-row { margin: 0; border-bottom: 1px solid var(--rb-border); } -.sl-markdown-content details.rb-tested-row:has(> summary:hover) { border-color: var(--rb-border); } -.sl-markdown-content .rb-tested-row > summary { - min-height: 2.25rem; - padding: 0.3rem 0; - cursor: pointer; - font-size: 0.9rem; -} -.sl-markdown-content .rb-tested-row > summary::before { background-color: var(--rb-text-secondary); margin: 0; } -.sl-markdown-content .rb-tested-row > summary:hover { background: var(--rb-surface); } -.sl-markdown-content .rb-tested-test { font-weight: 600; overflow-wrap: anywhere; } -.sl-markdown-content :is(.rb-tested-test, .rb-tested-date) small { color: var(--rb-text-secondary); font-weight: 400; } -.sl-markdown-content .rb-tested-date { color: var(--rb-text-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; } -.sl-markdown-content .rb-tested-more { - margin: 0 0 0.75rem 1.75rem; - padding: 0.5rem 0.75rem; - border: 1px solid var(--rb-border); - border-radius: 4px; - font-size: 0.875rem; -} -.sl-markdown-content .rb-tested-more p { margin: 0.35rem 0 0; } -.sl-markdown-content .rb-tested-more p:first-child { margin-top: 0; } -.sl-markdown-content details.rb-tested-earlier { margin-top: 0.75rem; } -.sl-markdown-content details.rb-tested-earlier > summary { font-size: 0.9rem; color: var(--rb-text-secondary); cursor: pointer; } -.sl-markdown-content .rb-dots { display: inline-flex; flex-wrap: wrap; gap: 3px; } -.sl-markdown-content .rb-dot { - width: 10px; - height: 10px; - border-radius: 3px; - border: 2px solid var(--rb-dot); - background: var(--rb-dot); -} -.sl-markdown-content .rb-dot-empty { background: transparent; } -.sl-markdown-content .rb-dot-0 { --rb-dot: var(--rb-ok-fg); } -.sl-markdown-content .rb-dot-1 { --rb-dot: var(--rb-info-fg); } -.sl-markdown-content .rb-dot-2 { --rb-dot: var(--rb-warn-fg); } -.sl-markdown-content .rb-dot-3 { --rb-dot: var(--rb-err-fg); } -.sl-markdown-content .rb-dot-4 { --rb-dot: var(--rb-muted-fg); } -.sl-markdown-content p.rb-tested-file { - margin: 0.9rem 0 0; - padding-bottom: 0.2rem; - border-bottom: 1px solid var(--rb-border-strong); - font-size: 0.85rem; - font-weight: 600; -} -.sl-markdown-content p.rb-tested-legend { margin-top: 0.5rem; color: var(--rb-text-secondary); font-size: 0.8rem; } -.sl-markdown-content p.rb-tested-legend .rb-dot { display: inline-block; vertical-align: -1px; } -.sl-markdown-content table.rb-tested-bench { margin-top: 1rem; font-size: 0.875rem; } -.sl-markdown-content table.rb-tested-bench td:not(:first-child) { font-variant-numeric: tabular-nums; text-align: right; } -@media (max-width: 50rem) { - .sl-markdown-content div.rb-tested-head { display: none; } - .sl-markdown-content details.rb-tested-row > summary { - grid-template-columns: 1.25rem auto minmax(0, 1fr) auto; - row-gap: 0.2rem; - } - .sl-markdown-content .rb-tested-row > summary::before { grid-row: 1; } - .sl-markdown-content .rb-tested-test { grid-column: 2 / -1; } - .sl-markdown-content .rb-tested-result { grid-column: 2; } - .sl-markdown-content .rb-tested-date { text-align: right; } - .sl-markdown-content .rb-tested-more { margin-left: 0.5rem; } -}