From 4a6b654bdc40310e011125c7b0c12c813e5da2d3 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 05:13:46 +0200 Subject: [PATCH] fix: keep a romm entry whole without a size RomM compares file_size_bytes with the entry's size before any hash. A new hash the truth gives no size for went out beside the old size and was reported as a size removal. The entry now stays as written and the correction is withheld and named. --- scripts/exporter/romm_exporter.py | 38 ++++++++++++++++++++++++++++--- tests/test_export_counts.py | 3 ++- tests/test_exporters.py | 14 +++++++++--- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/scripts/exporter/romm_exporter.py b/scripts/exporter/romm_exporter.py index 2786ae3b..74f09ff0 100644 --- a/scripts/exporter/romm_exporter.py +++ b/scripts/exporter/romm_exporter.py @@ -26,6 +26,10 @@ SOURCE_URL = ( ) +# Fixture field, model field, in the order RomM writes them. +_FIELDS = (("size", "size"), ("crc", "crc32"), ("md5", "md5"), ("sha1", "sha1")) + + class Exporter(BaseExporter): """Write RomM's known_bios_files.json, corrected.""" @@ -35,6 +39,8 @@ class Exporter(BaseExporter): # addition that claims each other key first: both set by render. self._platform_keys: set[tuple[str, str]] = set() self._first_addition: dict[tuple[str, str], int] = {} + # Entries of theirs the truth takes over without a size: left whole. + self._unsized: dict[int, str] = {} @staticmethod def platform_name() -> str: @@ -112,6 +118,7 @@ class Exporter(BaseExporter): if fe.platform is not None } self._first_addition = {} + self._unsized = {} self.normalized: list[str] = [] self.normalized_values: list[tuple[NativeFile, str, str, str]] = [] for system in sorted(systems.values(), key=lambda s: s.native_id): @@ -133,11 +140,22 @@ class Exporter(BaseExporter): # Their entry keeps its fields and their order, ours fill in. entry: OrderedDict[str, str] = OrderedDict(output.get(key, {})) # The fixture states every value as a string, size included. - # Where the model settles no size, RomM's own value stands. # str(None) wrote "None", and RomM's int() on it raised. size = fe.size() - if size is None and fe.platform is not None: - size = fe.platform.get("size") + if size is None and fe.platform is not None and fe.platform.get("size"): + # The truth took the entry over without a size: RomM's + # size describes the dump the truth rejects, and RomM + # compares the size before any hash, so the new hashes + # beside it never verify. The entry stays as written. + self._unsized[id(fe)] = key + if key not in output: + output[key] = OrderedDict( + (field, value) + for field, name in _FIELDS + if (value := ",".join(_hash_values(fe.platform, name)) + if name != "size" else str(fe.platform["size"])) + ) + continue if size is not None: self._write(entry, "size", str(int(size)), fe, "size") for field, hash_name in (("crc", "crc32"), ("md5", "md5"), ("sha1", "sha1")): @@ -160,6 +178,20 @@ class Exporter(BaseExporter): does not keep.""" return bool(_hash_values(fe.platform or {}, name)) and not fe.hashes(name) + def states(self, fe: NativeFile, field_name: str) -> bool: + """An entry left whole carries none of the truth's values.""" + if id(fe) in self._unsized: + return False + return super().states(fe, field_name) + + def withheld(self) -> list[str]: + """Each entry kept whole for want of the new dump's size.""" + return [ + f"{key} keeps its values: the truth states no size for the new " + "dump, and RomM compares the size before any hash" + for key in sorted(self._unsized.values()) + ] + def written(self, fe: NativeFile, field_name: str) -> str: """A record holds one value per field, the one record_hash names.""" return fe.record_hash(field_name) diff --git a/tests/test_export_counts.py b/tests/test_export_counts.py index 740f9778..a1a2f490 100644 --- a/tests/test_export_counts.py +++ b/tests/test_export_counts.py @@ -913,7 +913,8 @@ class TheSummaryCountsWhatTheFileCarries(unittest.TestCase): return NativeFile( "scph5501.bin", "scph5501.bin", "psx", platform={"size": 524288, "crc32": "deadbeef", "md5": A, "sha1": "1" * 40}, - truth={"md5": B}, + # With a size of its own: without one, RomM keeps its entry whole. + truth={"md5": B, "size": 524288}, ) def test_romm_drops_the_hashes_it_counts_cleared(self): diff --git a/tests/test_exporters.py b/tests/test_exporters.py index fc3abc00..3c7f4dfc 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -1509,9 +1509,17 @@ class RommSizeIsNeverNone(unittest.TestCase): produced = exporter.render(systems, report, {}, None) return exporter, systems, produced, json.loads(produced["known_bios_files.json"]) - def test_the_platform_size_stands_when_none_is_settled(self): - _, _, _, data = self._export(524288) - self.assertEqual(data["psx:scph5501.bin"]["size"], "524288") + def test_an_entry_the_truth_takes_without_a_size_stays_whole(self): + """RomM compares the size before any hash: the new md5 beside the + old size never verified, and the report said the size was removed.""" + from export_native import landed_changes # noqa: PLC0415 + + exporter, systems, _, data = self._export(524288) + self.assertEqual( + data["psx:scph5501.bin"], {"size": "524288", "md5": "a" * 32} + ) + self.assertEqual(landed_changes(exporter, systems).records, []) + self.assertEqual(len(exporter.withheld()), 1) def test_no_size_is_written_rather_than_none(self): exporter, systems, produced, data = self._export(None)