"""An export writes what its summary announces, and nothing else. RetroDECK announced 1474 additions and wrote none, counted 161 requirement corrections while keeping the platform's prose labels, and wrote one system's ATARIOSB.ROM over another's. RetroBat and EmuDeck turned empty or partial hash lists into content checks nobody counted, and a size was taken from a file other than the one the hash describes. """ from __future__ import annotations import json import sys import unittest from collections import OrderedDict from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT / "scripts")) from exporter.baseline import NativeFile, NativeSystem, build_native_model # noqa: E402 from exporter.emudeck_exporter import Exporter as EmuDeck # noqa: E402 from exporter.retrobat_exporter import Exporter as RetroBat # noqa: E402 from exporter.retrodeck_exporter import Exporter as RetroDeck # noqa: E402 A = "a" * 32 B = "b" * 32 C = "c" * 32 class RetroDeckWritesWhatItCounts(unittest.TestCase): def test_same_name_in_two_systems_stays_two_entries(self): existing = [ {"filename": "ATARIOSB.ROM", "system": "atari5200", "md5": f"{A},{B}"}, {"filename": "ATARIOSB.ROM", "system": "atari800", "md5": B}, ] ours = [ OrderedDict(filename="ATARIOSB.ROM", system="atari800", md5=C), ] merged = RetroDeck._merge(existing, ours) by_system = {e["system"]: e["md5"] for e in merged} self.assertEqual(by_system, {"atari5200": f"{A},{B}", "atari800": C}) self.assertEqual(len(merged), 2) def test_revisions_under_one_name_keep_their_own_hash(self): existing = [ {"filename": "64DD_IPL.bin", "system": "n64dd", "md5": A}, {"filename": "64DD_IPL.bin", "system": "n64dd", "md5": B}, ] ours = [OrderedDict(filename="64DD_IPL.bin", system="n64dd", md5=f"{A},{C}")] merged = RetroDeck._merge(existing, ours) self.assertEqual([e["md5"] for e in merged], [f"{A},{C}", B]) def test_a_list_of_systems_is_kept(self): existing = [{"filename": "neogeo.zip", "system": ["neogeo", "fbneo"], "md5": A}] ours = [OrderedDict(filename="neogeo.zip", system="fbneo", md5=B)] merged = RetroDeck._merge(existing, ours) self.assertEqual(len(merged), 1) self.assertEqual(merged[0]["system"], ["neogeo", "fbneo"]) self.assertEqual(merged[0]["md5"], B) def test_prose_label_is_neither_rewritten_nor_counted(self): exporter = RetroDeck() prose = NativeFile( "x.bin", "bios/x.bin", "psx", platform={"required": True, "required_label": "At least one BIOS file required"}, truth={"required": False}, ) plain = NativeFile( "y.bin", "bios/y.bin", "psx", platform={"required": True, "required_label": "Required"}, truth={"required": False}, ) self.assertFalse(exporter.states(prose, "required")) self.assertEqual(RetroDeck._entry(prose)["required"], "At least one BIOS file required") self.assertTrue(exporter.states(plain, "required")) self.assertEqual(RetroDeck._entry(plain)["required"], "Optional") def test_addition_goes_to_the_system_component(self): declared = NativeFile("a.bin", "bios/a.bin", "psx", platform={"component": "duckstation", "md5": A}) added = NativeFile("b.bin", "bios/b.bin", "psx", truth={"md5": B}) spread = NativeSystem("amiga", files=[ NativeFile("k1", "bios/k1", "amiga", platform={"component": "retroarch"}), NativeFile("k2", "bios/k2", "amiga", platform={"component": "puae"}), NativeFile("k3", "bios/k3", "amiga", truth={"md5": C}), ]) systems = {"psx": NativeSystem("psx", files=[declared, added]), "amiga": spread} manifest = json.dumps({"duckstation": {"name": "DuckStation", "bios": []}}) produced = RetroDeck().render( systems, None, {"duckstation/component_manifest.json": manifest} ) written = json.loads(produced["duckstation/component_manifest.json"]) names = [e["filename"] for e in written["duckstation"]["bios"]] self.assertIn("b.bin", names) self.assertFalse(RetroDeck.writable(spread.files[2])) class HashesTheFormatCannotHold(unittest.TestCase): def test_retrobat_fills_only_a_single_accepted_value(self): exporter = RetroBat() single = NativeFile("s.bin", "s.bin", "nds", platform={"md5": ""}, truth={"md5": A}, filled=["md5"]) several = NativeFile("m.bin", "m.bin", "nds", platform={"md5": ""}, truth={"md5": [A, B]}, filled=["md5"]) self.assertEqual(RetroBat._md5(single), A) self.assertTrue(exporter.states(single, "md5")) self.assertEqual(RetroBat._md5(several), "") self.assertFalse(exporter.states(several, "md5")) def test_emudeck_never_grows_an_array(self): unhashed = NativeFile("scph5501.bin", "scph5501.bin", "psx", platform={"md5": ""}, truth={"md5": C}, filled=["md5"]) extra = NativeFile("scph1001.bin", "scph1001.bin", "psx", platform={"md5": A}, truth={"md5": [A, B]}) corrected = NativeFile("scph7001.bin", "scph7001.bin", "psx", platform={"md5": B}, truth={"md5": C}, corrections=["md5"]) systems = {"psx": NativeSystem("psx", files=[unhashed, extra, corrected])} self.assertEqual(EmuDeck._md5s(systems, "psx"), [A, C]) self.assertFalse(EmuDeck().states(unhashed, "md5")) class ModelKeepsOneFileOneEntry(unittest.TestCase): def test_size_describes_the_hash_written(self): fe = NativeFile("boot.bin", "dc/boot.bin", "dc", platform={"size": 2097152, "md5": A}, truth={"size": 480}) self.assertEqual(fe.size(), 2097152) hashed = NativeFile("boot.bin", "dc/boot.bin", "dc", platform={"size": 2097152, "md5": A}, truth={"size": 480, "md5": B}) self.assertEqual(hashed.size(), 480) def test_second_core_entry_for_a_declared_file_adds_nothing(self): scraped = {"systems": {"psx": {"files": [ {"name": "scph101.bin", "destination": "bios/scph101.bin", "md5": A}, ]}}} truth = {"systems": {"psx": {"files": [ {"name": "scph101.bin", "path": "scph101.bin", "md5": A}, {"name": "scph101.bin", "path": "scph101.bin", "md5": B}, ]}}} systems, report = build_native_model(truth, scraped) self.assertEqual(report.files_added, 0) self.assertEqual(len(systems["psx"].files), 1) class OneFileOneIdentity(unittest.TestCase): def test_a_contradicting_truth_does_not_borrow_platform_fields(self): fe = NativeFile("boot.bin", "dc/boot.bin", "dc", platform={"size": 2097152, "md5": A, "sha1": "1" * 40, "crc32": "aaaaaaaa"}, truth={"size": 480, "crc32": "bbbbbbbb"}) self.assertEqual(fe.hashes("sha1"), []) self.assertEqual(fe.hashes("crc32"), ["bbbbbbbb"]) self.assertEqual(fe.size(), 480) def test_agreeing_sides_still_merge(self): fe = NativeFile("x.bin", "x.bin", "s", platform={"md5": A, "sha1": "1" * 40}, truth={"md5": A}) self.assertEqual(fe.hashes("sha1"), ["1" * 40]) def test_a_name_with_another_size_is_another_file(self): scraped = {"systems": {"dc": {"files": [ {"name": "boot.bin", "destination": "dc/boot.bin", "size": 2097152, "md5": A}, ]}}} truth = {"systems": {"dc": {"files": [ {"name": "boot.bin", "path": "fbneo/boot.bin", "size": 480, "crc32": "bbbbbbbb"}, ]}}} systems, _report = build_native_model(truth, scraped) declared = next(f for f in systems["dc"].files if f.platform is not None) self.assertIsNone(declared.truth) if __name__ == "__main__": unittest.main()