"""Slot resolution: keep one BIOS per slot only where the code declares an order.""" from __future__ import annotations import os import sys import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts")) import slot def _pairs(*names: str) -> list[tuple[str, str]]: return [(n, n) for n in names] class TestSlotIndex(unittest.TestCase): def setUp(self): self.index = slot.build_slot_index( { "picodrive": { "files": [ {"name": "us_scd2_9306.bin", "priority": 1}, {"name": "SegaCDBIOS9303.bin", "priority": 2}, {"name": "us_scd1_9210.bin", "priority": 3}, {"name": "bios_CD_U.bin", "priority": 4}, ] }, "duckstation": { "files": [ {"name": "scph5501.bin", "priority": 5}, {"name": "scph1001.bin", "priority": 10}, ] }, "launcher_profile": { "type": "launcher", "files": [{"name": "ignored.bin", "priority": 1}], }, "engine": { "files": [ {"name": "assets.pk3", "category": "game_data", "priority": 1}, ] }, } ) def test_priority_is_indexed(self): self.assertEqual(self.index["us_scd2_9306.bin"]["rank"], 1) def test_duckstation_priorities_are_read(self): self.assertEqual(self.index["scph5501.bin"]["rank"], 5) def test_launcher_profiles_are_skipped(self): self.assertNotIn("ignored.bin", self.index) def test_non_bios_categories_are_skipped(self): self.assertNotIn("assets.pk3", self.index) def test_lookup_falls_back_from_destination_to_name(self): entry = slot.lookup_slot( self.index, "picodrive/us_scd2_9306.bin", "us_scd2_9306.bin" ) self.assertIsNotNone(entry) self.assertEqual(entry["rank"], 1) class TestResolveSlotDrops(unittest.TestCase): def setUp(self): self.index = slot.build_slot_index( { "picodrive": { "files": [ {"name": "a.bin", "priority": 1}, {"name": "b.bin", "priority": 2}, {"name": "c.bin", "priority": 3}, ] }, "other": { "files": [ {"name": "x.bin"}, {"name": "y.bin"}, {"name": "tie1.bin", "priority": 1}, {"name": "tie2.bin", "priority": 1}, ] }, } ) def test_first_rank_wins_and_the_rest_drop(self): drops, undecidable = slot.resolve_slot_drops( {"segacd|us": _pairs("a.bin", "b.bin", "c.bin")}, self.index ) self.assertEqual(drops, {"b.bin", "c.bin"}) self.assertEqual(undecidable, []) def test_an_unranked_candidate_makes_the_group_undecidable(self): drops, undecidable = slot.resolve_slot_drops( {"psx|us": _pairs("a.bin", "x.bin")}, self.index ) self.assertEqual(drops, set()) self.assertEqual(undecidable, ["psx|us"]) def test_no_rank_at_all_is_undecidable(self): drops, undecidable = slot.resolve_slot_drops( {"psx|us": _pairs("x.bin", "y.bin")}, self.index ) self.assertEqual(drops, set()) self.assertEqual(undecidable, ["psx|us"]) def test_a_tie_on_the_first_rank_is_undecidable(self): drops, undecidable = slot.resolve_slot_drops( {"t|us": _pairs("tie1.bin", "tie2.bin")}, self.index ) self.assertEqual(drops, set()) self.assertEqual(undecidable, ["t|us"]) def test_a_lone_candidate_is_never_dropped(self): drops, undecidable = slot.resolve_slot_drops( {"solo|us": _pairs("b.bin")}, self.index ) self.assertEqual(drops, set()) self.assertEqual(undecidable, []) def test_a_destination_kept_by_another_group_survives(self): drops, _u = slot.resolve_slot_drops( { "segacd|us": _pairs("a.bin", "b.bin", "c.bin"), "segacd|world": _pairs("b.bin"), }, self.index, ) self.assertNotIn("b.bin", drops) def test_unknown_files_are_never_dropped(self): drops, undecidable = slot.resolve_slot_drops( {"u|us": _pairs("a.bin", "b.bin", "nowhere.bin")}, self.index ) self.assertNotIn("nowhere.bin", drops) class TestRepoProfiles(unittest.TestCase): """Guards on the real profiles, not on synthetic fixtures.""" def setUp(self): import glob import yaml self.paths = sorted( glob.glob( os.path.join(os.path.dirname(__file__), "..", "emulators", "*.yml") ) ) if not self.paths: self.skipTest("emulators/ not present") self.profiles = {} for path in self.paths: with open(path, encoding="utf-8") as fh: self.profiles[os.path.basename(path)[:-4]] = yaml.safe_load(fh) or {} def test_declared_priorities_are_non_negative_integers(self): offenders = [ f"{name}: {f.get('name')} -> {f['priority']!r}" for name, profile in self.profiles.items() for f in (profile.get("files") or []) if isinstance(f, dict) and f.get("priority") is not None and (not isinstance(f["priority"], int) or f["priority"] < 0) ] self.assertEqual(offenders, [], "\n".join(offenders)) def test_lowest_priority_picks_the_reference_playstation_bios(self): """DuckStation ranks scph5501 at 5 and de-prioritizes by raising the number, so the US slot must resolve to it and not to a PS2 image.""" index = slot.build_slot_index(self.profiles) ds = self.profiles.get("duckstation") if not ds: self.skipTest("duckstation profile not present") members = [ (f["name"], f["name"]) for f in ds["files"] if f.get("region") == ["north-america"] and f.get("priority") is not None ] if len(members) < 2: self.skipTest("no US candidate set to decide") drops, undecidable = slot.resolve_slot_drops( {"sony-playstation": members}, index ) self.assertEqual(undecidable, []) kept = {n for n, _ in members} - drops self.assertEqual(kept, {"scph5501.bin"}) def test_ties_leave_the_group_untouched(self): """Japanese PlayStation ties at 5, so nothing may be dropped there.""" index = slot.build_slot_index(self.profiles) ds = self.profiles.get("duckstation") if not ds: self.skipTest("duckstation profile not present") members = [ (f["name"], f["name"]) for f in ds["files"] if f.get("region") == ["japan"] and f.get("priority") is not None ] drops, undecidable = slot.resolve_slot_drops( {"sony-playstation": members}, index ) self.assertEqual(drops, set()) self.assertEqual(undecidable, ["sony-playstation|japan"]) if __name__ == "__main__": unittest.main()