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; } -}