diff --git a/tests/test_file_consumers.py b/tests/test_file_consumers.py index 94b23f13..74aa8aad 100644 --- a/tests/test_file_consumers.py +++ b/tests/test_file_consumers.py @@ -87,34 +87,60 @@ class ReadersFollowTheDataTrees(unittest.TestCase): """Dolphin names codehandler.bin without a hash. The pack takes it from data/dolphin-sys, and the site credited Dolphin and Triforce with the Dolphin-MMJR copy, another revision, because the consumers resolved - without the data trees. Reads the repository.""" + without the data trees.""" - def test_dolphin_does_not_read_the_mmjr_copy(self): + def setUp(self): import os - from common import load_database, load_emulator_profiles + self._previous = os.getcwd() + self._tree = tempfile.TemporaryDirectory() + root = Path(self._tree.name) + fork = root / "bios" / "Other" / "dolphin-mmjr" / "codehandler.bin" + fork.parent.mkdir(parents=True) + fork.write_bytes(b"dolphin-mmjr codehandler") + own = root / "data" / "dolphin-sys" / "codehandler.bin" + own.parent.mkdir(parents=True) + own.write_bytes(b"dolphin codehandler") + self.fork_sha1 = hashlib.sha1(fork.read_bytes()).hexdigest() + self.db = { + "files": {self.fork_sha1: { + "name": "codehandler.bin", "sha1": self.fork_sha1, + "path": "bios/Other/dolphin-mmjr/codehandler.bin", + "md5": hashlib.md5(fork.read_bytes()).hexdigest(), + "size": fork.stat().st_size, + }}, + "indexes": { + "by_name": {"codehandler.bin": [self.fork_sha1]}, + "by_md5": {}, "by_path_suffix": {}, + }, + } + self.registry = {"dolphin-sys": {"local_cache": str(own.parent)}} + self.profiles = {"dolphin": {"type": "standalone + libretro", "files": [ + {"name": "codehandler.bin", "path": None, "mode": "libretro"}, + ]}} + os.chdir(root) - if not (REPO_ROOT / "database.json").is_file() or not ( - REPO_ROOT / "data" / "dolphin-sys" / "codehandler.bin" - ).is_file(): - self.skipTest("database.json or data/dolphin-sys is absent") - previous = os.getcwd() - os.chdir(REPO_ROOT) - try: - db = load_database("database.json") - profiles = load_emulator_profiles("emulators") - consumers = FileConsumers(db) - consumers.add_emulators({"dolphin": profiles["dolphin"]}) - finally: - os.chdir(previous) - mmjr = next( - (sha1 for sha1, entry in db["files"].items() - if entry["path"] == "bios/Other/dolphin-mmjr/codehandler.bin"), - None, - ) - if mmjr is None: - self.skipTest("the Dolphin-MMJR copy is not collected") - self.assertNotIn("dolphin", consumers.emulators.get(mmjr, set())) + def tearDown(self): + import os + + from common import forget_data_dir_index + + os.chdir(self._previous) + forget_data_dir_index() + self._tree.cleanup() + + def _readers(self, registry: dict) -> set[str]: + consumers = FileConsumers(self.db, data_dir_registry=registry) + consumers.add_emulators(self.profiles) + return consumers.emulators.get(self.fork_sha1, set()) + + def test_dolphin_does_not_read_the_mmjr_copy(self): + self.assertNotIn("dolphin", self._readers(self.registry)) + + def test_without_the_data_trees_the_name_step_takes_the_fork_copy(self): + """The case above has to reach the data trees to pass: resolved + without them, the name alone hands Dolphin the fork's copy.""" + self.assertIn("dolphin", self._readers({})) def tearDownModule(): diff --git a/tests/test_owner_resolution.py b/tests/test_owner_resolution.py index b9ffcb8b..f135bd1f 100644 --- a/tests/test_owner_resolution.py +++ b/tests/test_owner_resolution.py @@ -9,7 +9,10 @@ armsx2's pack ships without a reader, and MAME's neogeo.zip without MAME. from __future__ import annotations +import hashlib +import os import sys +import tempfile import unittest from pathlib import Path @@ -30,7 +33,7 @@ class ReadersNameTheOwner(unittest.TestCase): def setUpClass(cls): db_path = REPO_ROOT / "database.json" if not db_path.exists(): - raise unittest.SkipTest("database.json is not built") + raise AssertionError("database.json is committed and missing") cls.db = load_database(str(db_path)) cls.profiles = load_emulator_profiles(str(REPO_ROOT / "emulators")) cls.sha1_by_path = {r["path"]: s for s, r in cls.db["files"].items()} @@ -40,15 +43,9 @@ class ReadersNameTheOwner(unittest.TestCase): self.db, build_zip_contents_index(self.db) ) consumers.add_emulators(self.profiles) - for path, owner in ( - ("bios/Other/armsx2/game_controller_db.txt", "armsx2"), - ("bios/Arcade/MAME/neogeo.zip", "mame"), - ): - with self.subTest(path=path): - sha1 = self.sha1_by_path.get(path) - if sha1 is None: - self.skipTest(f"{path} is not collected") - self.assertIn(owner, consumers.emulators.get(sha1, set())) + sha1 = self.sha1_by_path.get("bios/Arcade/MAME/neogeo.zip") + self.assertIsNotNone(sha1, "MAME's neogeo.zip is not collected") + self.assertIn("mame", consumers.emulators.get(sha1, set())) def test_slot_claims_resolve_the_owners_tree(self): claims = slots.profile_claims({"pcem": self.profiles["pcem"]}, self.db) @@ -57,12 +54,60 @@ class ReadersNameTheOwner(unittest.TestCase): } # pcem declares ide_xt.bin and the collection holds it: an unclaimed # file here is a claiming defect, not missing data. - if not any(e.get("path", "").endswith("/ide_xt.bin") for e in self.db["files"].values()): - self.skipTest("ide_xt.bin is not collected") + self.assertTrue( + any(e.get("path", "").endswith("/ide_xt.bin") + for e in self.db["files"].values()), + "ide_xt.bin is not collected", + ) local = held.get("ide_xt.bin") self.assertIsNotNone(local, "pcem's ide_xt.bin is not claimed") self.assertTrue(local.startswith("bios/Other/pcem/"), local) +class EachForkReadsItsOwnCopy(unittest.TestCase): + """Two emulators ship a file under one name in their own trees. Without + the owner, the site credited aethersx2's game_controller_db.txt to + armsx2 and left the copy armsx2's pack ships without a reader.""" + + NAME = "game_controller_db.txt" + + def setUp(self): + self._previous = os.getcwd() + self._tree = tempfile.TemporaryDirectory() + root = Path(self._tree.name) + files: dict[str, dict] = {} + self.sha1_of: dict[str, str] = {} + for owner in ("aethersx2", "armsx2"): + rel = f"bios/Other/{owner}/{self.NAME}" + payload = f"{owner} mappings".encode() + (root / rel).parent.mkdir(parents=True) + (root / rel).write_bytes(payload) + sha1 = hashlib.sha1(payload).hexdigest() + files[sha1] = { + "name": self.NAME, "sha1": sha1, "path": rel, + "md5": hashlib.md5(payload).hexdigest(), "size": len(payload), + } + self.sha1_of[owner] = sha1 + self.db = {"files": files, "indexes": { + "by_name": {self.NAME: sorted(files)}, + "by_md5": {}, "by_path_suffix": {}, + }} + os.chdir(root) + + def tearDown(self): + os.chdir(self._previous) + self._tree.cleanup() + + def test_the_site_credits_each_owner_with_its_copy(self): + consumers = cross_reference.FileConsumers(self.db, data_dir_registry={}) + consumers.add_emulators({ + owner: {"type": "standalone", "files": [{"name": self.NAME}]} + for owner in self.sha1_of + }) + for owner, sha1 in self.sha1_of.items(): + with self.subTest(owner=owner): + self.assertEqual(consumers.emulators.get(sha1), {owner}) + + if __name__ == "__main__": unittest.main()