chore: merge the last MAME fixes

This commit is contained in:
Abdessamad Derraz committed 2026-11-11 15:23:57 +01:00
commit 3bebf75da5
77 files changed
+2424 -327

No files matched your search

+5
View File
@@ -52,6 +52,11 @@ class ListxmlIndex(unittest.TestCase):
def test_device_with_roms_only(self) -> None:
self.assertEqual(self.index["devices"], {"m68705p3": ["mslug"]})
def test_a_clone_lists_its_parent_game(self) -> None:
# MAME looks a clone's shared ROMs up in its parent's archive
# (src/emu/driver.cpp:31-38): mslugh reads mslug.zip.
self.assertEqual(self.index["parents"], {"mslug": ["mslugh"]})
def test_chd_and_missing_parent(self) -> None:
self.assertEqual(self.index["chd"], ["ikaruga"])
self.assertEqual(self.index["missing_parents"], {"orphan": "gonebios"})
+46
View File
@@ -145,6 +145,52 @@ class ACoreOnlyTheDownloaderBrings(unittest.TestCase):
self.assertEqual([f["dest"] for f in files], ["kick31.rom", "roms/a4091.rom"])
class CoreOfferedOnSomeBoards(unittest.TestCase):
"""ROCKNIX writes uae4arm for roms/amiga under the devices it builds it
for (emulators/package.mk:188), and s922x runs uae4arm for amigacd32:
the core list alone would keep its roms/amiga files there."""
MANIFEST = {
"core_names": {"uae4arm": [], "puae": []},
"offered_boards": {
"uae4arm": [{"root": "roms", "folder": "amiga", "boards": ["h700", "rk3326"]}],
},
"files": [
{"dest": "amiga/kick.rom", "cores": ["uae4arm"], "root": "roms"},
{"dest": "amigacd32/kick.rom", "cores": ["uae4arm"], "root": "roms"},
{"dest": "amiga/shared.rom", "cores": ["uae4arm", "puae"], "root": "roms"},
],
"omitted_files": [],
}
def dests(self, target):
files, _ = install.narrow_to_cores(self.MANIFEST, ["uae4arm"], target=target)
return [f["dest"] for f in files]
def test_a_board_it_is_offered_on_keeps_the_folder(self):
self.assertEqual(self.dests("rk3326"),
["amiga/kick.rom", "amigacd32/kick.rom", "amiga/shared.rom"])
def test_another_board_keeps_only_the_folders_offering_it(self):
self.assertEqual(self.dests("s922x"), ["amigacd32/kick.rom"])
def test_a_core_offered_everywhere_keeps_a_shared_file(self):
files, _ = install.narrow_to_cores(self.MANIFEST, ["uae4arm", "puae"], target="s922x")
self.assertEqual([f["dest"] for f in files], ["amigacd32/kick.rom", "amiga/shared.rom"])
def test_installed_cores_are_no_board(self):
files, _ = install.narrow_to_cores(self.MANIFEST, ["uae4arm"])
self.assertEqual(len(files), 3)
def test_the_manifest_check_refuses_a_malformed_map(self):
base = {"manifest_version": 2, "platform": "rocknix", "files": [], "omitted_files": []}
install._validate_manifest(
{**base, "offered_boards": self.MANIFEST["offered_boards"]}, "rocknix")
with self.assertRaisesRegex(ValueError, "offered_boards"):
install._validate_manifest(
{**base, "offered_boards": {"uae4arm": [{"folder": "amiga"}]}}, "rocknix")
class ACoreTheListDoesNotKnow(unittest.TestCase):
"""A core newer than the list (parallel_dc, issue 87) loses nothing its .info declares."""
+92 -3
View File
@@ -13,6 +13,7 @@ import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
import hashlib
import io
import json
import shutil
import sys
import tempfile
@@ -985,12 +986,12 @@ class AdvanceMameOnEmuELEC(unittest.TestCase):
# read in both.
view = standalone_view("advancemame", "emuelec")
sets = [e for e in view["files"] if e.get("category") == "bios_zip"]
self.assertEqual(len(sets), 58)
self.assertEqual(len(sets), 52)
places: dict[str, set[str]] = {}
for entry in sets:
self.assertTrue(entry.get("unprefixed"))
places.setdefault(entry["name"], set()).add(entry["standalone_path"])
self.assertEqual(len(places), 29)
self.assertEqual(len(places), 26)
for name, paths in places.items():
self.assertEqual(paths, {"mame/" + name, "arcade/" + name})
@@ -1017,7 +1018,7 @@ class AdvanceMameOnEmuELEC(unittest.TestCase):
by_dest = {f["dest"]: advancemame_copy(f) for f in manifest["files"]}
sets = [d.removeprefix("arcade/") for d, sha1 in by_dest.items()
if d.startswith("arcade/") and sha1]
self.assertEqual(len(sets), 29)
self.assertEqual(len(sets), 26)
for name in sets:
self.assertEqual(by_dest["arcade/" + name], by_dest["mame/" + name], name)
@@ -1208,6 +1209,94 @@ class EveryCoreAFolderOffers(unittest.TestCase):
self.assertIn("arcade", folders, platform)
class CloneReadsItsParent(unittest.TestCase):
"""ES-DE starts odyssey2 with -rompath on roms/odyssey2 alone, and MAME
finds o2bios.rom in the parent's videopac.zip (src/emu/driver.cpp:31-38)."""
def test_the_parent_set_goes_to_the_clone_folder(self) -> None:
from common import rom_search_sets
self.assertIn("videopac", rom_search_sets("mame", "odyssey2", ["odyssey2"], False))
for platform, dest in (("emudeck", "odyssey2/videopac.zip"),
("retrodeck", "roms/odyssey2/videopac.zip")):
with self.subTest(platform=platform):
dests = {f["dest"] for f in platform_manifest(platform)["files"]}
self.assertIn(dest, dests)
class AdvanceMameOnRetroPie(unittest.TestCase):
"""RetroPie writes dir_rom $romdir/mame-advmame:$romdir/arcade into each
AdvanceMAME build's rc (advmame.sh:100), the list the code splits on ':'
(advance/linux/file.c:138-141)."""
def test_the_sets_go_to_the_first_folder_searched(self) -> None:
config = load_platform_config("retropie", str(REPO_ROOT / "platforms"))
profiles = load_emulator_profiles(str(REPO_ROOT / "emulators"))
tags = standalone_set(config)
for emu in ("advmame", "advmame-1.4", "advmame-0.94"):
with self.subTest(emu=emu):
placed = [
(f.get("root"), f.get("standalone_path"))
for view, standalone in platform_layouts(emu, profiles[emu], tags)
if standalone
for f in view["files"] if isinstance(f, dict) and f["name"] == "neogeo.zip"
]
self.assertEqual(placed, [("roms", "mame-advmame/neogeo.zip")])
class CoreOfferedOnSomeBoards(unittest.TestCase):
"""ROCKNIX writes some add_emu_core calls under a `case "${DEVICE}"`
branch (emulators/package.mk:188 offers uae4arm for amiga on the devices
it builds it for). A board's pack places such a core in that folder only
when the branch names the board, and a manifest for every board says
where it does."""
ROW = {
"folder": "amiga", "core": "puae", "emulator": "retroarch",
"offers": [
{"emulator": "retroarch", "core": "puae"},
{"emulator": "retroarch", "core": "uae4arm", "when": "DEVICE H700|RK3326"},
{"emulator": "retroarch", "core": "puae2021",
"when": 'if [ "${PREFER_GLES}" = "yes" ]; then'},
],
}
def config(self, **extra) -> dict:
base = load_platform_config("rocknix", str(REPO_ROOT / "platforms"))
return {**base, "_rom_folders": [self.ROW], **extra}
def test_a_branch_names_the_boards(self) -> None:
from common import when_admits
self.assertTrue(when_admits("DEVICE H700|RK3326", "rk3326"))
self.assertFalse(when_admits("DEVICE H700|RK3326", "s922x"))
self.assertTrue(when_admits("DEVICE AMD64; DEVICE RK3588*", "rk3588"))
self.assertTrue(when_admits('if [ "${PREFER_GLES}" = "yes" ]; then', "h700"))
def test_an_offer_carries_the_boards_its_branch_names(self) -> None:
from common import offer_boards
boards = offer_boards(self.config())
self.assertEqual(boards[("uae4arm", "amiga")], frozenset({"h700", "rk3326"}))
self.assertIsNone(boards[("puae", "amiga")])
self.assertIsNone(boards[("puae2021", "amiga")])
def test_a_board_pack_places_the_core_where_the_board_offers_it(self) -> None:
from common import CONTENT_FOLDER_PREFIX, TARGET_KEY, content_folder_tags
def placed(config: dict) -> dict:
rows = [json.loads(t[len(CONTENT_FOLDER_PREFIX):]) for t in content_folder_tags(config)
if t.startswith(CONTENT_FOLDER_PREFIX)]
return {core: on for core, _folder, _where, on in rows}
every = placed(self.config())
self.assertEqual(every["uae4arm"], ["h700", "rk3326"])
self.assertIsNone(every["puae"])
self.assertIn("uae4arm", placed(self.config(**{TARGET_KEY: "rk3326"})))
self.assertNotIn("uae4arm", placed(self.config(**{TARGET_KEY: "s922x"})))
self.assertIn("puae", placed(self.config(**{TARGET_KEY: "s922x"})))
def platform_manifest(platform: str) -> dict:
from common import load_database
+32
View File
@@ -278,6 +278,38 @@ class IniSearchLists(unittest.TestCase):
self.assertFalse([r for r in found if "hash" in str(r.get("search_dir"))])
ADVMAME = module("advmame", """
function configure_advmame() {
iniConfig " " "" "$md_conf_root/mame-advmame/$md_id.rc"
iniSet "dir_rom" "$romdir/mame-advmame:$romdir/arcade"
iniSet "dir_artwork" "$romdir/mame-advmame/artwork"
addEmulator 1 "$md_id" "arcade" "$md_inst/bin/advmame %BASENAME%"
addSystem "arcade"
}
""", 95)
class AdvanceMameSearchList(unittest.TestCase):
"""AdvanceMAME splits dir_rom on ':' (advance/linux/file.c:138-141,
advance/osd/fileio.c:696-699), and advmame.sh:100 lists two folders."""
def test_a_colon_list_below_the_roms_root_is_a_search_path(self):
found = [r for r in records({f"{EMULATORS}/advmame.sh": ADVMAME}, "advmame")
if r["kind"] == "passes_path"]
self.assertEqual(
[(r["search_dir"], r["passes_to"].split(" in ")[0]) for r in found],
[("$romdir/mame-advmame:$romdir/arcade", "dir_rom")],
)
def test_mame_does_not_read_a_colon_as_a_separator(self):
from scraper.retropie_launchers import LIST_SEPARATORS
self.assertNotIn("mame", LIST_SEPARATORS)
self.assertEqual(
{LIST_SEPARATORS[p] for p in ("advmame", "advmame-0.94", "advmame-1.4")}, {":"}
)
class CommandLines(unittest.TestCase):
def test_sdltrs_passes_one_rom_per_machine(self):
found = records({f"{EMULATORS}/sdltrs.sh": SDLTRS}, "sdltrs")
+12 -5
View File
@@ -126,10 +126,8 @@ class HeldPinsReadTheirBuildFileAgain(unittest.TestCase):
class EveryBuildReferenceNamesThePin(unittest.TestCase):
"""Reads the repository: a pin_build value is what the platform builds,
so on a pin_frozen profile it is the profile's source_commit or names
its core_version, and the platform's own YAML was scraped from that
repository. A pin_build standing for config_reads names the build those
reads are made at, a revision of its own (ROCKNIX's DuckStation)."""
so it is the profile's source_commit or names its core_version, and the
platform's own YAML was scraped from that repository."""
def test_the_values_agree(self):
from common import load_emulator_profiles
@@ -147,11 +145,20 @@ class EveryBuildReferenceNamesThePin(unittest.TestCase):
else {str(versions)} if versions else set()
)
source = str(profile.get("source") or "").rstrip("/").removesuffix(".git")
def names_it(value: str) -> bool:
if profile.get("pin_follows"):
# A build of the branch tip names the repository it
# clones, at no revision: RetroPie's advmame.
return value.split()[-1].rstrip("/").removesuffix(".git") == source
token = value.split()[-1].rsplit("/", 1)[-1].removesuffix(".tar.gz")
return value in pins or any(token in v or v in value for v in versions)
if profile.get("pin_frozen") and not any(names_it(str(r["value"])) for r in references):
# A reference that only dates a config_reads entry names the build
# the platform reads that file at, not this profile's pin.
justifies_pin = profile.get("pin_frozen") or profile.get("pin_follows")
if justifies_pin and not any(names_it(str(r["value"])) for r in references):
disagree.append(name)
for reference in references:
if not cf.pin_build_revision(reference, REPO_ROOT / "platforms"):
+63 -3
View File
@@ -3077,9 +3077,14 @@ class TestDeliberatelyFrozenPin(TestBuildReport):
# live build can add a system its frozen revision lacked
# (advancemame's coh1002v, after advmame-1.4), so the frozen one's
# systems are a subset, not an equal set. A live build can also drop
# a system upstream deleted: advancemame removed its Hyper Neo Geo 64
# driver in dff25b6ee7, which advmame-1.4 still carries.
removed_upstream = {("advmame-1.4", "advancemame"): {"hyper-neogeo64"}}
# a system upstream deleted: AdvanceMAME removed its Hyper Neo Geo 64
# driver in dff25b6ee7, which advmame-1.4 and advancemame (AmberELEC's
# 0f6a767) still carry.
removed_upstream = {
("advmame-1.4", "advancemame"): {"hyper-neogeo64"},
("advmame-1.4", "advmame"): {"hyper-neogeo64"},
("advancemame", "advmame"): {"hyper-neogeo64"},
}
pairs = 0
for names in by_source.values():
frozen = [
@@ -3373,6 +3378,61 @@ class TestBumpCommit(unittest.TestCase):
self.assertFalse(bump_commit(self.path, report))
self.assertNotIn("newhead", self.path.read_text())
def test_a_build_of_the_branch_tip_follows_it(self):
# RetroPie builds AdvanceMAME from the tip of master
# (advmame.sh:16, 39-41): HEAD is the build it ships.
self.path.write_text(
SAMPLE + "built_by: [retropie]\n"
+ 'pin_follows: "RetroPie builds the branch tip"\n',
encoding="utf-8",
)
part = PartResult(
RefPart("a.c", 10, 12, "a.c:10-12"), "ANCHORED", None, 10, 12, []
)
report = ProfileReport(
name="p", repo="o/n", pin="pin", head="newhead",
entries=[
EntryReport("a.bin", "a.c:10-12, (just a note)", "ANCHORED", [part])
],
counts={"ANCHORED": 1},
)
self.assertTrue(bump_commit(self.path, report))
self.assertIn("newhead", self.path.read_text())
def test_a_config_read_at_the_pin_follows_it(self):
# RetroPie reads advmame.rc at the build it installs, the pin: the
# config_reads revision is the pin, and moves with it.
pin = "a" * 40
self.path.write_text(
SAMPLE.replace('profiled_date: "2026-03-29"\n',
f'profiled_date: "2026-03-29"\nsource_commit: "{pin}"\n', 1)
+ "built_by: [retropie]\n"
+ 'pin_follows: "RetroPie builds the branch tip"\n'
+ "config_reads:\n"
+ ' - file: "advmame.rc"\n'
+ " platforms: [retropie]\n"
+ f' revision: "{pin}"\n'
+ ' source_ref: "a.c:10-12"\n'
+ " - file: \"other.rc\"\n"
+ " platforms: [emuelec]\n"
+ f' revision: "{"b" * 40}"\n'
+ ' source_ref: "a.c:10-12"\n',
encoding="utf-8",
)
part = PartResult(
RefPart("a.c", 10, 12, "a.c:10-12"), "ANCHORED", None, 10, 12, []
)
head = "c" * 40
report = ProfileReport(
name="p", repo="o/n", pin=pin, head=head,
entries=[EntryReport("a.bin", "a.c:10-12", "ANCHORED", [part])],
counts={"ANCHORED": 1},
)
self.assertTrue(bump_commit(self.path, report))
document = yaml.safe_load(self.path.read_text(encoding="utf-8"))
self.assertEqual(document["source_commit"], head)
self.assertEqual([r["revision"] for r in document["config_reads"]], [head, "b" * 40])
def test_refused_when_no_ref_reads_the_pinned_repository(self):
"""gam4980: every bare libretro.c ref anchored in upstream, and the
bump wrote the fork's HEAD over refs that never described it."""