fix: give both hashes in the untested table

A row said the declared hash was contradicted by the local file and
named neither. The table now prints the hashes the platform declares
and the path and hashes of the copy the collection holds.
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 10:06:52 +02:00
1 parent 377e53f4c0
commit fac8685819
2 files changed
+88 -7

No files matched your search

+62 -7
View File
@@ -3849,6 +3849,48 @@ def _held_accepted(discrepancy: str, db: dict | None) -> str:
return ""
_PROBLEM_HASHES = ("sha1", "md5", "sha256", "crc32")
def _declared_hashes(config: dict, detail: dict) -> dict[str, list[str]]:
"""The hashes the platform declares for a reported file, by algorithm.
The untested table said "declared hash contradicted by the local file"
and named neither: the reader could not look for the dump RetroDECK asks
for, nor report the difference.
"""
from hashing import parse_hash_list # noqa: PLC0415
system = (config.get("systems") or {}).get(detail.get("system", "")) or {}
for entry in system.get("files") or []:
if isinstance(entry, dict) and entry.get("name") == detail.get("name"):
return {
algo: parse_hash_list(entry.get(algo))
for algo in _PROBLEM_HASHES if parse_hash_list(entry.get(algo))
}
return {}
def _hash_cell(hashes: dict[str, list[str]]) -> str:
return "<br>".join(
f"{algo} " + ", ".join(_code(v) for v in values) for algo, values in hashes.items()
)
def _copy_cell(path: str, algos: list[str], db: dict | None) -> str:
"""The file a path holds, with its hashes in the algorithms declared."""
if not path:
return "-"
record = next(
(rec | {"sha1": sha1} for sha1, rec in (db or {}).get("files", {}).items()
if rec.get("path") == path),
{},
)
hashes = {algo: [str(record[algo]).lower()] for algo in algos if record.get(algo)}
shown = f"[{_code(path)}]({_fetch_url(path)})"
return f"{shown}<br>{_hash_cell(hashes)}" if hashes else shown
def _render_problem_files(
platform_problems: list, settled: list | None = None, db: dict | None = None
) -> list[str]:
@@ -3877,9 +3919,14 @@ def _render_problem_files(
if p["required"]:
entry["required"] = True
elif p["status"] == "untested":
entry = untested_files.setdefault(fname, {
# Keyed by what is declared and what is held: two platforms
# asking two dumps under one name are two rows.
declared = p.get("declared") or {}
key = f"{fname}\0{sorted(declared.items())}\0{p.get('path', '')}"
entry = untested_files.setdefault(key, {
"name": fname, "required": p["required"],
"platforms": [], "reason": p["reason"],
"declared": declared, "path": p.get("path", ""),
})
entry["platforms"].append(p["platform"])
if p.get("discrepancy"):
@@ -3920,15 +3967,21 @@ def _render_problem_files(
f'### Untested <span class="rb-badge rb-badge-warning">'
f"{_count(len(untested_files), 'file')}</span>",
"",
"Present but hash not verified.",
"Present but hash not verified. Declared is what the platform "
"asks for, the collection copy is what the path holds.",
"",
"| File | Platforms | Reason |",
"|------|----------|--------|",
"| File | Platforms | Reason | Declared | Collection copy |",
"|------|----------|--------|----------|-----------------|",
])
for fname in sorted(untested_files):
f = untested_files[fname]
for key in sorted(untested_files):
f = untested_files[key]
plats = ", ".join(sorted(set(f["platforms"])))
lines.append(f"| `{fname}` | {plats} | {f['reason']} |")
declared = f["declared"]
lines.append(
f"| {_code(f['name'])} | {plats} | {f['reason']} "
f"| {_hash_cell(declared) or '-'} "
f"| {_copy_cell(f['path'], list(declared) or ['md5'], db)} |"
)
lines.append("")
if mismatch_files:
@@ -4026,6 +4079,8 @@ def generate_gap_analysis(
"reason": d.get("reason", ""),
"discrepancy": d.get("discrepancy", ""),
"system": d.get("system", ""),
"declared": _declared_hashes(cov.get("config") or {}, d),
"path": d.get("path") or "",
})
pct_verified = (
+26
View File
@@ -44,5 +44,31 @@ class EachRowItsDeclaration(unittest.TestCase):
self.assertNotIn(A, saturn)
class AnUntestedRowNamesBothHashes(unittest.TestCase):
"""The gaps page said "declared hash contradicted by the local file" for
RetroDECK's dsifirmware.bin and named neither the dump RetroDECK asks for
nor the one the collection holds."""
def test_declared_and_held(self):
import generate_site as gs
path = "bios/Nintendo/Nintendo DS/dsifirmware.bin"
declared, held = "1" * 64, "2" * 64
config = {"systems": {"nintendo-ds": {"files": [
{"name": "dsifirmware.bin", "sha256": declared}]}}}
detail = {"name": "dsifirmware.bin", "system": "nintendo-ds"}
problem = {
"platform": "RetroDECK", "name": "dsifirmware.bin", "status": "untested",
"required": True, "reason": "declared hash contradicted by the local file",
"declared": gs._declared_hashes(config, detail), "path": path,
}
db = {"files": {"f" * 40: {"path": path, "sha256": held}}}
table = "\n".join(gs._render_problem_files([problem], db=db))
row = next(line for line in table.splitlines() if "dsifirmware" in line)
self.assertIn(f"sha256 `{declared}`", row)
self.assertIn(f"sha256 `{held}`", row)
self.assertIn(f"`{path}`", row)
if __name__ == "__main__":
unittest.main()