mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 07:33:45 -06:00
feat: show each test as one row with its proof
The Tested block opens on one summary line, then a row per test: a short title, a result chip, a dot per variant and the date. Method, claim, computer, core build and the record wait in the row's details. Tests from before the boot oracle fold under one line, benchmarks get their own table. beetle_saturn shows under 120 words before anything is opened.
This commit is contained in:
1 parent
70c3bbd853
commit
e7d0c0183c
4 files changed
+449
-68
No files matched your search
+269
-41
@@ -18,6 +18,7 @@ its line at the last commit that touched measurements/.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -146,9 +147,20 @@ 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"]),
|
||||
"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),
|
||||
}
|
||||
|
||||
|
||||
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."""
|
||||
@@ -161,7 +173,8 @@ def build_index(stored: list[mr.Stored], source: dict | None, profiles: dict,
|
||||
for key in sorted(by_subject):
|
||||
entries = by_subject[key]
|
||||
latest = max(entries, key=lambda s: (s.record["recorded_at"], s.line))
|
||||
items.append(index_item(latest, len(entries), source or {}, profiles, system_names or {}))
|
||||
items.append(with_title(index_item(latest, len(entries), source or {}, profiles,
|
||||
system_names or {})))
|
||||
items.sort(key=lambda item: (item["emulator"], item["date"], item["subject"]))
|
||||
return {
|
||||
"schema_version": INDEX_SCHEMA_VERSION,
|
||||
@@ -514,9 +527,14 @@ 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
|
||||
out: dict[str, list[dict]] = {}
|
||||
for item in index.get("items", []):
|
||||
out.setdefault(item["emulator"], []).append(item)
|
||||
out.setdefault(item["emulator"], []).append(
|
||||
{**item, "open_findings": opened.get(item["emulator"], 0)})
|
||||
return out
|
||||
|
||||
|
||||
@@ -532,52 +550,262 @@ def _short(commit: str | None) -> str:
|
||||
return f"`{commit[:12]}`" if commit else "unknown"
|
||||
|
||||
|
||||
def tested_item(item: dict) -> list[str]:
|
||||
"""One test: what ran, the result, the method and host beside it, the record."""
|
||||
title = item["claim"] or f"Benchmark of {item['content'] or 'the core alone'}"
|
||||
runs = "1 run" if item["runs"] == 1 else f"{item['runs']} runs, the latest shown"
|
||||
core = item["core"]
|
||||
build = f"{_text(core['library'])} at {_short(core['commit'])}" if core.get("commit") \
|
||||
else _text(core["library"])
|
||||
lines = [f"- **{_text(title)}**", ""]
|
||||
if len(item.get("verdicts") or ()) > 1:
|
||||
lines += [f" **Result:** `{item['status']}`.", ""]
|
||||
lines += [f" - {_text(v['variant'])}: `{v['verdict']}`. {_text(v['detail'])}."
|
||||
for v in item["verdicts"]]
|
||||
lines.append("")
|
||||
# A verdict or a comparison, as the block says it: words and a chip kind.
|
||||
RESULT_WORDS = {
|
||||
"boots": ("works", "success"),
|
||||
"bios_error": ("BIOS error", "danger"),
|
||||
"crash": ("crash", "danger"),
|
||||
"hang": ("hang", "warning"),
|
||||
"unknown": ("unknown", "muted"),
|
||||
"mixed": ("differs", "info"),
|
||||
"same": ("no difference", "muted"),
|
||||
"differs": ("differs", "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"}
|
||||
TITLE_AXIS_WORDS = 4
|
||||
|
||||
|
||||
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)
|
||||
if len(axis) > TITLE_AXIS_WORDS:
|
||||
axis = [*axis[:TITLE_AXIS_WORDS], "..."]
|
||||
head = ", ".join(part for part in (f"`{thing}`" if thing else "", 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 "")
|
||||
|
||||
|
||||
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(variant["verdict"], len(position))
|
||||
result[variant["label"]] = RESULT_WORDS[variant["verdict"]][0]
|
||||
groups = {v["label"]: position[v["verdict"]] for v in record["variants"]}
|
||||
else:
|
||||
lines += [f" **Result:** `{item['status']}`. {_text(item['statement'])}.", ""]
|
||||
if item.get("method"):
|
||||
lines += [f" **Method:** {_text(item['method'])}", ""]
|
||||
lines += [
|
||||
f" **Ran on:** {_text(item['host'])}, {item['date'][:10]}. Core build: {build}. "
|
||||
f"{item['variants']} variant{'s' if item['variants'] != 1 else ''}, "
|
||||
f"{item['repetitions']} repetition{'s' if item['repetitions'] != 1 else ''} each"
|
||||
+ (f", {item['flags']} flag{'s' if item['flags'] != 1 else ''} raised" if item["flags"] else "")
|
||||
+ ".",
|
||||
"",
|
||||
f" [Full record]({item['record_url']}), {runs}.",
|
||||
"",
|
||||
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`."""
|
||||
out = []
|
||||
for entry in profile.get("files") or ():
|
||||
for name, ids in sorted((entry.get("evidence") or {}).items()):
|
||||
if record_id in ids:
|
||||
out.append(f"{name} of {entry.get('name')}")
|
||||
return out
|
||||
|
||||
|
||||
# An MDX page reads a brace as the start of an expression, in text and in
|
||||
# an attribute alike.
|
||||
_BRACES = str.maketrans({"{": "{", "}": "}"})
|
||||
|
||||
|
||||
def _attr(value: object) -> str:
|
||||
return html.escape(" ".join(str(value or "").split()), quote=True).translate(_BRACES)
|
||||
|
||||
|
||||
def _cell(value: object) -> str:
|
||||
"""Record text placed in raw HTML: escaped, code spans kept, a semicolon
|
||||
joining two clauses ending a sentence as on the rest of the site."""
|
||||
text = " ".join(str(value or "").split())
|
||||
text = re.sub(r";\s+(\w)", lambda m: ". " + m[1].upper(), text)
|
||||
return markup.inline_html(text).translate(_BRACES)
|
||||
|
||||
|
||||
def _dots(item: dict) -> str:
|
||||
groups = item.get("groups") or []
|
||||
names = "; ".join(f"{g['variant']}: {g['result']}" for g in groups)
|
||||
dots = "".join(
|
||||
f'<span class="rb-dot rb-dot-{g["group"] % 5}{" rb-dot-empty" if g["absent"] else ""}" '
|
||||
f'title="{_attr(g["variant"] + ": " + g["result"])}"></span>' for g in groups)
|
||||
return (f'<span class="rb-dots" role="img" aria-label="{_attr(f"{len(groups)} variants. {names}")}">'
|
||||
f"{dots}</span>")
|
||||
|
||||
|
||||
def _chip(item: dict) -> str:
|
||||
words, kind = RESULT_WORDS.get(item["status"], (item["status"], "muted"))
|
||||
return f'<span class="rb-badge rb-badge-{kind}">{html.escape(words)}</span>'
|
||||
|
||||
|
||||
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"<p><strong>{label}:</strong> {text}.</p>" if text else ""
|
||||
|
||||
|
||||
def _details(item: dict) -> list[str]:
|
||||
core = item["core"]
|
||||
build = _cell(core["library"]) + (f" at <code>{html.escape(core['commit'][:12])}</code>"
|
||||
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 = "1 run" 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["host"]),
|
||||
_sentence_html("Core build", build and f"{build}. {reps}", markup_ready=True),
|
||||
f'<p><a href="{_attr(item["record_url"])}">Full record</a>, {runs}.</p>',
|
||||
]
|
||||
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, page_host: str, main_core: str) -> list[str]:
|
||||
elsewhere = (item.get("host_short") or item["host"]) != page_host
|
||||
title = _cell(item.get("title") or short_title(item))
|
||||
if core_label(item) != main_core:
|
||||
title += f' <small>{html.escape(core_label(item))}</small>'
|
||||
return [
|
||||
'<details class="rb-tested-row">',
|
||||
("<summary>"
|
||||
f'<span class="rb-tested-test">{title}</span>'
|
||||
f'<span class="rb-tested-result">{_chip(item)}</span>'
|
||||
f'<span class="rb-tested-variants">{_dots(item)}</span>'
|
||||
f'<span class="rb-tested-date">{item["date"][:10]}'
|
||||
+ (f' <small>{_attr(item.get("host_short"))}</small>' if elsewhere else "")
|
||||
+ "</span></summary>"),
|
||||
'<div class="rb-tested-more">',
|
||||
*_details(item),
|
||||
"</div>",
|
||||
"</details>",
|
||||
]
|
||||
return lines
|
||||
|
||||
|
||||
def tested_section(name: str, items: list[dict]) -> list[str]:
|
||||
"""The Tested block of an emulator page, empty when nothing was recorded."""
|
||||
"""The Tested block of an emulator page, empty when nothing was recorded.
|
||||
|
||||
The answer first: one summary line, then one 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. Tests
|
||||
recorded before the boot oracle fold under one line, and benchmarks get
|
||||
their own table at the end.
|
||||
"""
|
||||
if not items:
|
||||
return []
|
||||
lines = [
|
||||
"## Tested",
|
||||
"",
|
||||
f"This emulator was run in real conditions by the [core probe](../{METHOD_PAGE}). "
|
||||
f"Each test below is the latest of its kind. "
|
||||
f"[All measurements](../{EXPLORER_PAGE}), "
|
||||
f"[these records as SQLite](../downloads/measurements/{name}.sqlite).",
|
||||
"",
|
||||
]
|
||||
for item in sorted(items, key=lambda i: (i["date"], i["subject"]), reverse=True):
|
||||
lines += tested_item(item)
|
||||
return lines
|
||||
counts = tested_counts(items)
|
||||
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)
|
||||
tests = f"{counts['tests']} test{'s' if counts['tests'] != 1 else ''}"
|
||||
summary = (f"{tests} · {counts['confirmed']} confirmed · {counts['open']} open · "
|
||||
f"last run {counts['last']} on {page_host}.")
|
||||
links = (f'<a href="{markup.site_href(METHOD_PAGE.removesuffix(".md") + "/")}">How</a> · '
|
||||
f'<a href="{markup.site_href(EXPLORER_PAGE.removesuffix(".md") + "/")}">All tests</a> · '
|
||||
f'<a href="{markup.site_href(f"downloads/measurements/{name}.sqlite")}">SQLite</a>')
|
||||
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"]
|
||||
earlier = [i for i in ordered if i["kind"] != "bench" and i["status"] == "no verdict"]
|
||||
current = [i for i in ordered if i["kind"] != "bench" and i["status"] != "no verdict"]
|
||||
lines = ["## Tested", "", '<div class="rb-tested">',
|
||||
f'<p class="rb-tested-summary">{html.escape(summary)} {links}</p>']
|
||||
if current or earlier:
|
||||
lines.append('<div class="rb-tested-head" aria-hidden="true"><span>Test</span>'
|
||||
"<span>Result</span><span>Variants</span><span>Date</span></div>")
|
||||
for item in current:
|
||||
lines += _row(item, page_host, main_core)
|
||||
if earlier:
|
||||
lines += ['<details class="rb-tested-earlier">',
|
||||
f"<summary>{len(earlier)} earlier test{'s' if len(earlier) != 1 else ''}, "
|
||||
"before the boot oracle</summary>"]
|
||||
for item in earlier:
|
||||
lines += _row(item, page_host, main_core)
|
||||
lines.append("</details>")
|
||||
if benches:
|
||||
# One line: MDX reads a table split over lines as paragraphs.
|
||||
table = ['<table class="rb-tested-bench"><thead><tr><th>Benchmark</th><th>ms per frame</th>'
|
||||
"<th>fps</th><th>stdev</th><th>runs</th></tr></thead><tbody>"]
|
||||
for item in benches:
|
||||
timing = item.get("timing") or {}
|
||||
title = _cell(item.get("title") or short_title(item))
|
||||
if core_label(item) != main_core:
|
||||
title += f' <small>{html.escape(core_label(item))}</small>'
|
||||
table.append(
|
||||
f'<tr><td><a href="{_attr(item["record_url"])}">{title}</a></td>'
|
||||
f'<td>{timing.get("ms_per_frame", 0):.3f}</td><td>{timing.get("fps", 0):.1f}</td>'
|
||||
f'<td>{timing.get("stdev", 0):.3f}</td><td>{item["repetitions"]}</td></tr>')
|
||||
lines.append("".join([*table, "</tbody></table>"]))
|
||||
return [*lines, "</div>", ""]
|
||||
|
||||
|
||||
def hosts_table(index: dict) -> list[str]:
|
||||
|
||||
Reference in new issue
Block a user