mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 21:43:23 -05:00
feat: report pack contents instead of list coverage
This commit is contained in:
1 parent
38d8e78eb3
commit
b586eddb2a
3 files changed
+151
-99
No files matched your search
+59
-26
@@ -51,6 +51,9 @@ def compute_coverage(
|
||||
total = result["total_files"]
|
||||
present = ok + untested
|
||||
pct = (present / total * 100) if total > 0 else 0
|
||||
undeclared = result.get("undeclared_files", [])
|
||||
core_present = sum(1 for u in undeclared if u.get("in_repo"))
|
||||
core_missing = len(undeclared) - core_present
|
||||
return {
|
||||
"platform": config.get("platform", platform_name),
|
||||
"total": total,
|
||||
@@ -59,6 +62,10 @@ def compute_coverage(
|
||||
"missing": missing,
|
||||
"present": present,
|
||||
"percentage": pct,
|
||||
"core_present": core_present,
|
||||
"core_missing": core_missing,
|
||||
"pack_files": present + core_present,
|
||||
"total_missing": missing + core_missing,
|
||||
"mode": config.get("verification_mode", "existence"),
|
||||
"details": result["details"],
|
||||
"config": config,
|
||||
@@ -69,6 +76,33 @@ def compute_coverage(
|
||||
}
|
||||
|
||||
|
||||
def manifest_totals(
|
||||
platform_name: str, install_dir: str = "install"
|
||||
) -> tuple[int | None, int | None]:
|
||||
"""Files and bytes a platform's pack ships, from its install manifest.
|
||||
|
||||
The manifest is written when packs are built, so it reflects the real
|
||||
pack contents (platform list, core complement and data directories).
|
||||
Returns (None, None) when no manifest exists yet.
|
||||
"""
|
||||
path = os.path.join(install_dir, f"{platform_name}.json")
|
||||
if not os.path.exists(path):
|
||||
return None, None
|
||||
try:
|
||||
with open(path) as f:
|
||||
manifest = json.load(f)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return None, None
|
||||
return manifest.get("total_files"), manifest.get("total_size")
|
||||
|
||||
|
||||
def format_size(size: int) -> str:
|
||||
"""Human-readable pack size."""
|
||||
if size >= 1024**3:
|
||||
return f"{size / 1024**3:.1f} GB"
|
||||
return f"{size / 1024**2:.0f} MB"
|
||||
|
||||
|
||||
SITE_URL = "https://abdess.github.io/retrobios/"
|
||||
RELEASE_URL = "../../releases/latest"
|
||||
REPO = "Abdess/retrobios"
|
||||
@@ -195,8 +229,8 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
|
||||
"",
|
||||
"Pick your platform, download the ZIP, extract to the BIOS path.",
|
||||
"",
|
||||
"| Platform | BIOS files | Extract to | Download |",
|
||||
"|----------|-----------|-----------|----------|",
|
||||
"| Platform | Size | Extract to | Download |",
|
||||
"|----------|------|-----------|----------|",
|
||||
]
|
||||
|
||||
# Where the pack itself is extracted, which is not always the BIOS folder:
|
||||
@@ -227,8 +261,10 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
|
||||
if name in archived:
|
||||
display = f"{display} *"
|
||||
path = extract_paths.get(cov["platform"], "")
|
||||
_, size = manifest_totals(name)
|
||||
size_cell = format_size(size) if size else "-"
|
||||
lines.append(
|
||||
f"| {display} | {cov['total']} | {path} | [Download]({RELEASE_URL}) |"
|
||||
f"| {display} | {size_cell} | {path} | [Download]({RELEASE_URL}) |"
|
||||
)
|
||||
|
||||
if archived:
|
||||
@@ -313,40 +349,37 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
|
||||
"",
|
||||
"## Coverage",
|
||||
"",
|
||||
"| Platform | Coverage | Verified | Untested | Missing | Source-backed |",
|
||||
"|----------|----------|----------|----------|---------|---------------|",
|
||||
"| Platform | Files in pack | Still missing | Verified by |",
|
||||
"|----------|--------------:|--------------:|-------------|",
|
||||
]
|
||||
)
|
||||
|
||||
mode_labels = {
|
||||
"md5": "MD5 hash",
|
||||
"sha1": "SHA1 hash",
|
||||
"existence": "file presence",
|
||||
}
|
||||
|
||||
for name, cov in sorted(coverages.items(), key=lambda x: x[1]["platform"]):
|
||||
pct = f"{cov['percentage']:.1f}%"
|
||||
gt = cov["ground_truth"]
|
||||
if not gt.get("applicable", True):
|
||||
gt_cell = "-"
|
||||
elif gt["total"]:
|
||||
gt_pct = f"{gt['with_validation'] / gt['total'] * 100:.0f}%"
|
||||
gt_cell = f"{gt['with_validation']}/{gt['total']} ({gt_pct})"
|
||||
else:
|
||||
gt_cell = "0/0"
|
||||
display = f"{cov['platform']} *" if name in archived else cov["platform"]
|
||||
files = manifest_totals(name)[0] or cov["pack_files"]
|
||||
checked = mode_labels.get(cov["mode"], cov["mode"])
|
||||
lines.append(
|
||||
f"| {display} | {cov['present']}/{cov['total']} ({pct}) | "
|
||||
f"{cov['verified']} | {cov['untested']} | {cov['missing']} | "
|
||||
f"{gt_cell} |"
|
||||
f"| {display} | {files:,} | {cov['total_missing']} | {checked} |"
|
||||
)
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"Coverage is measured against the file list each platform declares,"
|
||||
" using that platform's own verification mode.",
|
||||
"Source-backed counts the files whose content the emulator's own"
|
||||
" code checks: a size or hash read from its source, reproduced at"
|
||||
" verification. A dash means no profiled emulator applies to the"
|
||||
" platform, whose own source is then the only authority.",
|
||||
f"The [gap analysis]({SITE_URL}gaps/) page counts separately the"
|
||||
" files a profile documents without a content check, and tracks"
|
||||
" where platform lists and emulator source code disagree.",
|
||||
"A pack carries what the platform declares plus what its emulators"
|
||||
" load without the platform listing it, so it holds more files than"
|
||||
" the platform's own list.",
|
||||
"Still missing counts files an emulator needs that are not in the"
|
||||
" collection yet; verified by is the check the platform itself runs"
|
||||
" on them.",
|
||||
f"The [gap analysis]({SITE_URL}gaps/) page names those missing files"
|
||||
" and details how far each platform's files are corroborated against"
|
||||
" emulator source code.",
|
||||
"",
|
||||
"## Build your own pack",
|
||||
"",
|
||||
|
||||
+58
-39
@@ -32,7 +32,7 @@ from common import (
|
||||
)
|
||||
|
||||
yaml = require_yaml()
|
||||
from generate_readme import compute_coverage
|
||||
from generate_readme import compute_coverage, manifest_totals
|
||||
from provenance_report import build_report
|
||||
|
||||
DOCS_DIR = "docs"
|
||||
@@ -1731,58 +1731,77 @@ def generate_gap_analysis(
|
||||
# ---- Verification per platform ----
|
||||
|
||||
lines.extend([
|
||||
"## Verification by Platform",
|
||||
"## What Each Pack Contains",
|
||||
"",
|
||||
"| Platform | Files | Verified | Untested | Missing | Mode | Profiled | Source-backed |",
|
||||
"|----------|------:|---------:|---------:|--------:|------|---------:|--------------:|",
|
||||
"| Platform | Files in pack | Declared by platform | Still missing | Verified by |",
|
||||
"|----------|--------------:|---------------------:|--------------:|-------------|",
|
||||
])
|
||||
|
||||
mode_labels = {
|
||||
"md5": "MD5 hash",
|
||||
"sha1": "SHA1 hash",
|
||||
"existence": "file presence",
|
||||
}
|
||||
|
||||
for pname, cov in sorted(coverages.items(), key=lambda x: x[1]["platform"]):
|
||||
display = cov["platform"]
|
||||
m = cov["missing"]
|
||||
u = cov["untested"]
|
||||
gone = cov["total_missing"]
|
||||
missing_str = (
|
||||
f'<span class="rb-badge rb-badge-danger">{m}</span>'
|
||||
if m > 0
|
||||
f'<span class="rb-badge rb-badge-danger">{gone}</span>'
|
||||
if gone > 0
|
||||
else '<span class="rb-badge rb-badge-success">0</span>'
|
||||
)
|
||||
untested_str = (
|
||||
f'<span class="rb-badge rb-badge-warning">{u}</span>'
|
||||
if u > 0
|
||||
else str(u)
|
||||
)
|
||||
gt = cov["ground_truth"]
|
||||
if not gt.get("applicable", True):
|
||||
gt_cell = prof_cell = "-"
|
||||
elif gt["total"]:
|
||||
gt_pct = f"{gt['with_validation'] / gt['total'] * 100:.0f}%"
|
||||
gt_cell = f"{gt['with_validation']}/{gt['total']} ({gt_pct})"
|
||||
prof_pct = f"{gt.get('with_profile', 0) / gt['total'] * 100:.0f}%"
|
||||
prof_cell = f"{gt.get('with_profile', 0)}/{gt['total']} ({prof_pct})"
|
||||
else:
|
||||
gt_cell = prof_cell = "0/0"
|
||||
files = manifest_totals(pname)[0] or cov["pack_files"]
|
||||
lines.append(
|
||||
f"| [{display}](platforms/{pname}.md) "
|
||||
f"| {cov['total']} "
|
||||
f"| {cov['verified']} "
|
||||
f"| {untested_str} "
|
||||
f"| {files:,} "
|
||||
f"| {cov['present']:,} "
|
||||
f"| {missing_str} "
|
||||
f"| {cov['mode']} "
|
||||
f"| {prof_cell} "
|
||||
f"| {gt_cell} |"
|
||||
f"| {mode_labels.get(cov['mode'], cov['mode'])} |"
|
||||
)
|
||||
lines.extend([
|
||||
"",
|
||||
"Verification follows each platform's own runtime check "
|
||||
"([how each mode works](wiki/verification-modes.md)): the counts "
|
||||
"measure the repository against the file list each platform declares. "
|
||||
"Profiled counts the files documented in an emulator profile (name or "
|
||||
"alias read from source). Source-backed is stricter: the emulator's "
|
||||
"own code checks the file's content (a size or hash read from its "
|
||||
"source, reproduced at verification). A file can be profiled without "
|
||||
"being source-backed when the code loads it but never checks it. "
|
||||
"A dash means no profiled emulator applies to the platform, whose "
|
||||
"own source is then the only authority.",
|
||||
"A pack carries the platform's own file list plus every file its "
|
||||
"emulators load without that list mentioning them, which is why it "
|
||||
"ships several times what the platform declares. Still missing counts "
|
||||
"files an emulator needs that are not in the collection yet, named in "
|
||||
"the sections below. Verified by is the check the platform runs at "
|
||||
"runtime, replicated here "
|
||||
"([how each mode works](wiki/verification-modes.md)).",
|
||||
"",
|
||||
"## Corroboration Against Emulator Source",
|
||||
"",
|
||||
"| Platform | Declared by platform | Profiled | Content checked by emulator code |",
|
||||
"|----------|---------------------:|---------:|---------------------------------:|",
|
||||
])
|
||||
|
||||
for pname, cov in sorted(coverages.items(), key=lambda x: x[1]["platform"]):
|
||||
gt = cov["ground_truth"]
|
||||
if not gt.get("applicable", True):
|
||||
prof_cell = gt_cell = "-"
|
||||
elif gt["total"]:
|
||||
prof_pct = f"{gt.get('with_profile', 0) / gt['total'] * 100:.0f}%"
|
||||
prof_cell = f"{gt.get('with_profile', 0)} ({prof_pct})"
|
||||
gt_pct = f"{gt['with_validation'] / gt['total'] * 100:.0f}%"
|
||||
gt_cell = f"{gt['with_validation']} ({gt_pct})"
|
||||
else:
|
||||
prof_cell = gt_cell = "0"
|
||||
lines.append(
|
||||
f"| [{cov['platform']}](platforms/{pname}.md) "
|
||||
f"| {gt['total']:,} | {prof_cell} | {gt_cell} |"
|
||||
)
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
"Both columns count the files a platform declares. Profiled means the "
|
||||
"file is documented in an emulator profile read from source. Content "
|
||||
"checked is stricter: the emulator's code verifies a size or hash for "
|
||||
"it, and this tool reproduces that check. The gap between the two is "
|
||||
"not a defect, it is what the emulator code does: many emulators load "
|
||||
"a file without ever checking its content, and no amount of profiling "
|
||||
"can invent a check the code does not perform. A dash means no "
|
||||
"profiled emulator applies to the platform, whose own source is then "
|
||||
"the only authority.",
|
||||
"",
|
||||
])
|
||||
|
||||
|
||||
Reference in new issue
Block a user