fix: write truth systems in a stable order

Systems a core covers without a file were added while walking a set,
so the hash seed placed them and two runs wrote dist/truth/<platform>.yml
in two orders. The normalized system map was built from a set too.
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 06:57:49 +02:00
1 parent bbd778b390
commit bc2233e725
2 files changed
+62 -5

No files matched your search

+8 -5
View File
@@ -550,10 +550,12 @@ def generate_platform_truth(
if fname:
declared_by_name.setdefault(fname, []).append((psid, fe))
# Normalized ID -> platform system ID
# Normalized ID -> platform system ID. Walked in the platform's order,
# not the set's: two IDs normalizing alike went to whichever the hash
# seed put last.
norm_to_platform: dict[str, str] = {}
for psid in platform_sys_ids:
norm_to_platform[_norm_system_id(psid)] = psid
for psid in config.get("systems", {}):
norm_to_platform.setdefault(_norm_system_id(psid), psid)
def _map_sys_id(profile_sid: str) -> str:
"""Map a profile system ID to the platform's system ID."""
@@ -669,8 +671,9 @@ def generate_platform_truth(
# Ensure all systems of resolved cores have entries (even with 0 files).
# This documents that the system is covered: the core was analyzed and
# needs no external files for this system.
for emu_name in cores_profiled:
# needs no external files for this system. Sorted: the set's order is
# the hash seed's, and it decided where each empty system was written.
for emu_name in sorted(cores_profiled):
profile = profiles[emu_name]
for prof_sid in profile.get("systems", []):
sys_id = _map_sys_id(prof_sid)
+54
View File
@@ -8,6 +8,10 @@ out on RetroBat, EmuDeck and RetroDECK.
from __future__ import annotations
import json
import os
import random
import subprocess
import sys
import unittest
from pathlib import Path
@@ -400,5 +404,55 @@ class TruthKnowsWhatTheEmulatorInstalls(unittest.TestCase):
self.assertEqual(names, [])
class TheModelIsAFunctionOfItsInputs(unittest.TestCase):
"""Two runs on the same inputs wrote dist/truth/<platform>.yml with its
systems in two orders. The systems a core covers without a file were
added by walking a set, so the hash seed decided where each was written.
"""
NAMES = [f"core{i:02d}" for i in range(16)]
SCRIPT = (
"import json, sys\n"
"sys.path.insert(0, sys.argv[1])\n"
"from truth import generate_platform_truth\n"
"names = [f'core{i:02d}' for i in range(16)]\n"
"profiles = {n: {'emulator': n, 'type': 'libretro',\n"
" 'systems': [f'maker-system-{n}'], 'files': []} for n in names}\n"
"config = {'cores': names,\n"
" 'systems': {f'maker-system-{n}': {'files': []} for n in names}}\n"
"print(json.dumps(generate_platform_truth('p', config, {}, profiles)))\n"
)
def _model(self, seed: str) -> str:
proc = subprocess.run(
[sys.executable, "-c", self.SCRIPT, str(REPO_ROOT / "scripts")],
env={**os.environ, "PYTHONHASHSEED": seed},
capture_output=True, text=True, timeout=120, check=True,
)
return proc.stdout
def test_the_hash_seed_changes_nothing(self):
first = self._model("0")
for seed in ("1", "2", "3", "4"):
with self.subTest(seed=seed):
self.assertEqual(self._model(seed), first)
def test_the_profile_order_changes_nothing(self):
profiles = {
n: {"emulator": n, "type": "libretro", "systems": [f"maker-system-{n}"],
"files": [{"name": f"{n}.bin", "system": f"maker-system-{n}",
"md5": f"{i:032x}"}]}
for i, n in enumerate(self.NAMES)
}
config = {"cores": self.NAMES,
"systems": {f"maker-system-{n}": {"files": []} for n in self.NAMES}}
expected = json.dumps(generate_platform_truth("p", config, {}, profiles))
shuffled = list(profiles.items())
random.Random(7).shuffle(shuffled)
self.assertEqual(
json.dumps(generate_platform_truth("p", config, {}, dict(shuffled))), expected
)
if __name__ == "__main__":
unittest.main()