Files
libretro/tests/test_emuelec_targets.py
T
Abdessamad Derraz 057fd2d0f6 fix: isolate every test module however it runs
tests/__init__.py keeps a run offline, its scratch under tmp/ and its
installer cache its own, but only discover -s tests -t . imported it.
discover tests ran with RETROBIOS_OFFLINE unset, scratch on the /tmp
tmpfs and the user's real installer cache, the setting in which e2e
fixtures had written into the shared .cache/large. Every test module now
imports the package first, and a test fails on a module that does not.
test_verify_listing.py took its line with the previous commit.
2026-11-06 15:14:05 +01:00

269 lines
12 KiB
Python

"""EmuELEC's images differ by device, and only its metapackage says how.
packages/sx05re/emuelec/package.mk (v4.8) builds the S922X cores, Dolphin
and the MAME core only on Amlogic-ng, Amlogic-no, RK356x and OdroidM1, and
takes DuckStation out on the two handhelds with a sed that replaces the
first match only. With no target list, an OdroidGoAdvance pack carried the
firmware of cores its image never had.
"""
from __future__ import annotations
import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
import json
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from scraper.targets import emuelec_targets_scraper as scraper # noqa: E402
# packages/sx05re/emuelec/package.mk:8-77 at v4.8, PKG_EMUS and
# PKG_EXPERIMENTAL shortened.
PACKAGE_MK = """PKG_DEPENDS_TARGET="toolchain ${OPENGLES} emuelec-emulationstation retroarch"
PKG_EXPERIMENTAL="nestopiaCV quasi88 yabasanshiroSA_1_11 yabasanshiroSA_1_5 fbneoSA"
PKG_EMUS="${LIBRETRO_CORES} advancemame duckstation pcsx_rearmed flycastsa"
PKG_DEPENDS_TARGET+=" emuelec-tools ${PKG_EMUS} ${PKG_EXPERIMENTAL}"
if [ "${DEVICE}" == "Amlogic-ng" ] || [ "${DEVICE}" == "Amlogic-no" ] || [ "${DEVICE}" == "RK356x" ] || [ "${DEVICE}" == "OdroidM1" ]; then
\tPKG_DEPENDS_TARGET+=" ${LIBRETRO_S922X_CORES}"
fi
if [ "${DEVICE}" == "OdroidGoAdvance" ] || [ "${DEVICE}" == "GameForce" ]; then
\tPKG_DEPENDS_TARGET+=" kmscon odroidgoa-utils"
\tfor discore in duckstation mesen-s virtualjaguar quicknes MC; do
\t\tPKG_DEPENDS_TARGET=$(echo ${PKG_DEPENDS_TARGET} | sed "s|${discore} | |")
\tdone
\tPKG_DEPENDS_TARGET+=" yabasanshiro"
else
\tPKG_DEPENDS_TARGET+=" fbterm"
fi
if [ "${ARCH}" == "aarch64" ]; then
for discore in quicknes parallel-n64 pcsx_rearmed; do
\t\tPKG_DEPENDS_TARGET=$(echo ${PKG_DEPENDS_TARGET} | sed "s|${discore}| |")
\tdone
PKG_DEPENDS_TARGET+=" swanstation \\
lib32-essential \\
lib32-pcsx_rearmed \\
lib32-parallel-n64
lib32-libusb"
if [ "${DEVICE}" == "Amlogic-ng" ] || [ "${DEVICE}" == "Amlogic-no" ] || [ "${DEVICE}" == "RK356x" ] || [ "${DEVICE}" == "OdroidM1" ]; then
PKG_DEPENDS_TARGET+=" dolphinSA"
fi
if [ "${DEVICE}" == "Amlogic-old" ]; then
for discore in yabasanshiroSA_1_11 yabasanshiroSA_1_5 yabasanshiro same_cdi duckstation; do
PKG_DEPENDS_TARGET=$(echo ${PKG_DEPENDS_TARGET} | sed "s|${discore} | |")
done
fi
fi
if [ "${DEVICE}" == "Amlogic-ng" ] || [ "${DEVICE}" == "Amlogic-no" ] || [ "${DEVICE}" == "RK356x" ] || [ "${DEVICE}" == "OdroidM1" ]; then
\tPKG_DEPENDS_TARGET+=" mame"
fi
if [ "${DEVICE}" == "RK356x" ] || [ "${DEVICE}" == "OdroidM1" ]; then
for discore in flycast-dojo; do
\t\tPKG_DEPENDS_TARGET=$(echo ${PKG_DEPENDS_TARGET} | sed "s|${discore}| |")
\tdone
fi
"""
# distributions/EmuELEC/options:274-373 at v4.8, shortened.
OPTIONS = """LIBRETRO_CORES="mgba \\
parallel-n64 \\
pcsx_rearmed \\
flycast"
LIBRETRO_S922X_CORES="beetle-saturn \\
mesen \\
quicknes \\
virtualjaguar"
"""
# packages/sx05re/emuelec-emulationstation/package.mk:114-143 at v4.8.
ES_MK = """CORESFILE="${INSTALL}/usr/config/emulationstation/es_systems.cfg"
if [[ "${DEVICE}" != "Amlogic-ng" || "${DEVICE}" != "Amlogic-ne" || "${DEVICE}" != "Amlogic-no" ]]; then
if [[ ${DEVICE} == "OdroidGoAdvance" || "${DEVICE}" == "GameForce" ]]; then
remove_cores="mesen-s quicknes mame2016 mesen"
elif [ "${DEVICE}" == "Amlogic-old" ]; then
remove_cores="mesen-s quicknes mame2016 mesen yabasanshiroSA yabasanshiro"
xmlstarlet ed -L -P -d "/systemList/system[name='saturn']" ${CORESFILE}
xmlstarlet ed -L -P -d "/systemList/system[name='phillips-cdi']" ${CORESFILE}
xmlstarlet ed -L -P -d "/systemList/system/emulators/emulator[@name='Duckstation']" ${CORESFILE}
fi
for discore in ${remove_cores}; do
sed -i "s|<core>${discore}</core>||g" ${CORESFILE}
sed -i '/^[[:space:]]*$/d' ${CORESFILE}
done
fi
if [[ ${DEVICE} != "OdroidGoAdvance" ]] && [[ "${DEVICE}" != "GameForce" ]]; then
\txmlstarlet ed -L -P -d "/systemList/system/emulators/emulator[@name='retrorun']" ${CORESFILE}
else
\txmlstarlet ed -L -P -d "/systemList/system/emulators/emulator[@name='Duckstation']" ${CORESFILE}
fi
"""
def _system(name: str, *emulators: tuple[str, list[dict]]) -> dict:
return {"name": name, "emulators": [{"name": e, "cores": c} for e, c in emulators]}
ES_JSON = json.dumps({"systems": [
_system("nes", ("libretro", [{"name": "mesen", "default": True}])),
_system("snes", ("libretro", [{"name": "snes9x", "default": True}, {"name": "mesen-s"}])),
_system("psx",
("libretro", [{"name": "pcsx_rearmed", "default": True},
{"name": "pcsx_rearmed_32b"}, {"name": "swanstation"}]),
("Duckstation", [{"name": "Duckstation"}]),
("retrorun", [{"name": "pcsx_rearmed_32b"}])),
_system("n64", ("libretro", [{"name": "parallel_n64_32b"}])),
]})
ARCHS = {"mesen": "", "duckstation": "aarch64", "pcsx_rearmed": "any",
"lib32-pcsx_rearmed": "aarch64", "swanstation": "any",
"lib32-parallel-n64": "aarch64"}
ASSETS = [
"EmuELEC-Amlogic-ng.aarch64-4.8-Generic.img.gz",
"EmuELEC-Amlogic-ng.aarch64-4.8.tar",
"EmuELEC-GameForce.aarch64-4.8-chi.img.gz.sha256",
"EmuELEC-OdroidGoAdvance.aarch64-4.8-odroidgo2.tar",
"EmuELEC-OdroidM1.aarch64-4.8.img.gz",
]
class TheMetapackageDecidesEachDevice(unittest.TestCase):
def setUp(self):
self.meta = scraper.read_metapackage(PACKAGE_MK, OPTIONS)
def test_devices_are_the_images_the_release_publishes(self):
self.assertEqual(scraper.release_devices(ASSETS), {
"Amlogic-ng": "aarch64", "GameForce": "aarch64",
"OdroidGoAdvance": "aarch64", "OdroidM1": "aarch64",
})
def test_the_lists_the_rules_work_on(self):
self.assertEqual(self.meta.emus, ["mgba", "parallel-n64", "pcsx_rearmed", "flycast",
"advancemame", "duckstation", "pcsx_rearmed",
"flycastsa"])
self.assertEqual(self.meta.lib32, ["swanstation", "lib32-essential",
"lib32-pcsx_rearmed", "lib32-parallel-n64",
"lib32-libusb"])
def test_the_big_devices_build_the_s922x_cores_dolphin_and_mame(self):
packages = scraper.device_packages(self.meta, "Amlogic-ng", "aarch64")
for package in ("beetle-saturn", "mesen", "virtualjaguar", "dolphinSA", "mame",
"duckstation", "fbterm"):
self.assertIn(package, packages)
# quicknes is added, then the aarch64 loop takes it out.
self.assertNotIn("quicknes", packages)
def test_the_handhelds_lose_duckstation_and_gain_their_tools(self):
packages = scraper.device_packages(self.meta, "OdroidGoAdvance", "aarch64")
for package in ("duckstation", "mesen", "beetle-saturn", "dolphinSA", "mame", "fbterm"):
self.assertNotIn(package, packages)
self.assertEqual(packages[-1:], ["lib32-libusb"])
self.assertIn("kmscon", packages)
self.assertIn("yabasanshiro", packages)
def test_sed_takes_out_the_first_match_only(self):
# pcsx_rearmed is written twice, in LIBRETRO_CORES and in PKG_EMUS:
# the aarch64 loop removes the first and the 64-bit core is built.
for device in ("Amlogic-ng", "OdroidGoAdvance"):
packages = scraper.device_packages(self.meta, device, "aarch64")
self.assertEqual(packages.count("pcsx_rearmed"), 1, device)
self.assertNotIn("parallel-n64", packages, device)
def test_a_pattern_with_a_trailing_space_misses_the_last_word(self):
self.assertEqual(scraper._sed_first("a duckstation", "duckstation "), "a duckstation")
self.assertEqual(scraper._sed_first("a duckstation b", "duckstation "), "a b")
# Without the space, the pattern takes a word's tail too.
self.assertEqual(scraper._sed_first("lib32-pcsx_rearmed", "pcsx_rearmed"), "lib32- ")
def test_a_changed_rule_stops_the_scrape(self):
changed = PACKAGE_MK.replace("duckstation mesen-s virtualjaguar quicknes MC",
"duckstation mesen-s")
with self.assertRaises(RuntimeError):
scraper.read_metapackage(changed, OPTIONS)
class EachPackageBuildsWhereItsArchAllows(unittest.TestCase):
def test_pkg_arch(self):
self.assertTrue(scraper.arch_builds("", "aarch64"))
self.assertTrue(scraper.arch_builds("any", "aarch64"))
self.assertTrue(scraper.arch_builds("aarch64 arm", "aarch64"))
# mupen64plus and uae4arm say PKG_ARCH="arm": skipped on aarch64.
self.assertFalse(scraper.arch_builds("arm", "aarch64"))
self.assertFalse(scraper.arch_builds("any !aarch64", "aarch64"))
def test_every_cited_install_line_is_read(self):
install = scraper.PROVIDERS["mgba"][0]
text = f'PKG_ARCH="any"\nmakeinstall_target() {{\n {install.line}\n}}\n'
with self.assertRaises(RuntimeError):
scraper._require(text.replace("mgba_libretro", "mgba2_libretro"),
(install.line,), install.path)
scraper._require(text, (install.line,), install.path)
class TheFrontendOffersWhatTheDeviceKeeps(unittest.TestCase):
def test_handhelds_drop_bare_cores_and_duckstation(self):
systems = scraper.es_systems_for(ES_JSON, ES_MK, "GameForce")
# mesen is the nes default: written with an attribute, sed misses it.
self.assertEqual(systems["nes"], [("libretro", ["mesen"])])
self.assertEqual(systems["snes"], [("libretro", ["snes9x"])])
self.assertEqual([e for e, _ in systems["psx"]], ["libretro", "retrorun"])
def test_other_devices_drop_retrorun(self):
systems = scraper.es_systems_for(ES_JSON, ES_MK, "Amlogic-ng")
self.assertEqual([e for e, _ in systems["psx"]], ["libretro", "Duckstation"])
self.assertEqual(systems["snes"], [("libretro", ["snes9x", "mesen-s"])])
def test_a_device_keeps_what_it_builds_and_offers(self):
meta = scraper.read_metapackage(PACKAGE_MK, OPTIONS)
archs = {**dict.fromkeys(scraper.PROVIDERS, "any"), **ARCHS}
cores = {}
for device in ("Amlogic-ng", "OdroidGoAdvance"):
packages = [p for p in scraper.device_packages(meta, device, "aarch64")
if p in scraper.PROVIDERS or p in scraper.NO_ES_CORE]
systems = scraper.es_systems_for(ES_JSON, ES_MK, device)
cores[device] = scraper.device_cores(packages, "aarch64", archs, systems)
self.assertEqual(cores["Amlogic-ng"], ["Duckstation", "mesen", "parallel_n64_32b",
"pcsx_rearmed", "pcsx_rearmed_32b",
"swanstation"])
self.assertEqual(cores["OdroidGoAdvance"], ["parallel_n64_32b", "pcsx_rearmed",
"pcsx_rearmed_32b", "swanstation"])
def test_a_package_never_read_stops_the_scrape(self):
archs = dict.fromkeys(scraper.PROVIDERS, "any")
with self.assertRaises(RuntimeError):
scraper.device_cores(["mgba", "newcore"], "aarch64", archs, {})
class TheCommittedListFollowsTheRules(unittest.TestCase):
def test_the_handhelds_list_no_core_they_do_not_build(self):
import yaml # noqa: PLC0415
targets = yaml.safe_load((REPO_ROOT / "platforms" / "targets" / "emuelec.yml")
.read_text(encoding="utf-8"))["targets"]
self.assertEqual(set(targets), {"amlogic-ng", "gameforce", "odroidgoadvance", "odroidm1"})
big = set(targets["amlogic-ng"]["cores"])
for device in ("gameforce", "odroidgoadvance"):
self.assertEqual(big - set(targets[device]["cores"]), {
"Duckstation", "dolphin", "mame", "mednafen_saturn", "mesen", "mesen-s",
"virtualjaguar",
})
self.assertEqual(targets["odroidm1"]["cores"], targets["amlogic-ng"]["cores"])
if __name__ == "__main__":
unittest.main()