Files
libretro/tests/test_launchers_emuelec.py
T
Abdessamad Derraz 8770b9c067 fix: install emuelec configs from the boot copy
duckstation.sh quotes its glob, so its copy names a file called * and
installs nothing. userconfig-setup copies /usr/config into
/storage/.config at every boot, an existing file kept (false | cp -i),
and /emuelec links there: that copy installs settings.ini and
advmame.rc. A quoted glob is no install, and a redirection ends the
copy's operands. Rescraped at v4.8: only these installs change.
2026-11-11 11:57:12 +01:00

711 lines
25 KiB
Python

"""EmuELEC's launcher layer read from its packages.
Each fixture is a cut-down copy of the lines an extractor reads at EmuELEC
v4.8 (02b45fe53b85c45c75dbad8e44a648092dec2e72). The records must name the
core emuelecRunEmu.sh dispatches, carry the names in the order the code
tries them, point at the fixture's own lines, and a construct that is gone
must stop the scrape.
"""
from __future__ import annotations
import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
import sys
import textwrap
import unittest
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from scraper import emuelec_launchers as el # noqa: E402
from scraper.emuelec_scraper import launcher_revision # noqa: E402
from scraper.shell_launchers import LauncherPatternMissing, RepoTree # noqa: E402
EMU = el.EMULATORS
def text(block: str) -> str:
return textwrap.dedent(block).lstrip("\n")
RUN_EMU = text(
"""
if [[ "${EMULATOR}" = "libretro" ]]; then
EMU="${CORE}_libretro"
fi
if [[ "${EMULATOR}" = "retrorun" ]]; then
EMU="${CORE}_libretro"
fi
MIDI_OUTPUT=$(get_ee_setting "ra_midi_output" "${PLATFORM}" "${BASEROMNAME}")
if [[ ! -z "${MIDI_OUTPUT}" ]]; then
emuelec-utils set_midi_source "${MIDI_OUTPUT}" "${EMULATOR}"
fi
if [ ${EMU} == "freej2me_libretro" ]; then
freej2me.sh
JAVA_HOME='/storage/roms/bios/jdk'
export JAVA_HOME
fi
case ${PLATFORM} in
"atarist")
if [ "${EMU}" = "HATARISA" ]; then
RUNTHIS='${TBASH} hatari.start "${ROMNAME}"'
fi
;;
"dreamcast"|"naomi")
if [ "${EMU}" = "flycastsa" ]; then
RUNTHIS='${TBASH} flycast.sh "${ROMNAME}"'
elif [ "${EMU}" = "flycast_dojo" ]; then
RUNTHIS='flycastdojo.sh "${ROMNAME}"'
fi
;;
"psx")
if [ "${EMU}" = "duckstation" ]; then
RUNTHIS='${TBASH} duckstation.sh "${ROMNAME}"'
fi
;;
"mame")
if [ "${EMU}" = "AdvanceMame" ]; then
RUNTHIS='${TBASH} advmame.sh "${PLATFORM}" "${ROMNAME}"'
fi
;;
"n64")
if [ "${EMU}" = "rice" ]; then
RUNTHIS='${TBASH} m64p.sh "${ROMNAME}"'
elif [ "${EMU}" = "glide64mk2" ]; then
RUNTHIS='${TBASH} m64p.sh "${ROMNAME}" m64p_gl64mk2'
fi
;;
"scummvm")
if [ "${EMU}" = "SCUMMVMSA" ]; then
RUNTHIS='${TBASH} scummvm.start sa "${ROMNAME}"'
fi
;;
"wii"|"gamecube")
if [ "${EMU}" = "dolphin" ]; then
RUNTHIS='${TBASH} dolphin.sh "${ROMNAME}"'
fi
;;
"pico8")
RUNTHIS='${TBASH} pico8.sh "${ROMNAME}"'
;;
"intellivision")
if [ "${EMU}" = "jzintv" ]; then
RUNTHIS='jzintv.sh "${ROMNAME}"'
fi
;;
"saturn")
if [ "${EMU}" = "yabasanshiroSA" ]; then
RUNTHIS='yabasanshiro.sh "${ROMNAME}"'
elif [ "${EMU}" = "yabasanshiroSA1_5" ]; then
RUNTHIS='yabasanshiro1_5.sh "${ROMNAME}"'
fi
;;
esac
case ${PLATFORM} in
"arcade"|"mame")
if [ "$EMU" = "mame_libretro" ]; then
mame.sh
fi
;;
esac
RUNTHIS='${RABIN} ${VERBOSE} ${RA_ARGS} -L /tmp/cores/${EMU}.so --config ${RACONF} "${ROMNAME}"'
RUNTHIS="retrorun"
RUNTHIS+=' ${CMD_ROTATE} --triggers -g -d /storage/roms/bios /tmp/cores/${EMU}.so "${ROMNAME}"'
"""
)
UTILS = text(
"""
function set_midi_source()
{
case ${midi_source} in
fluidsynth)
fluidsynth -is /storage/roms/bios/sc/SC-55.sf2 &
;;
timidity)
timidity -iA --module 1 &
;;
mt32)
mt32d -o 2 -i 24 -f /roms/bios &
;;
esac
}
"""
)
RETROARCH_MK = text(
"""
sed -i -e "s/# savestate_directory =/savestate_directory =\\/storage\\/roms\\/savestates/" ${INSTALL}/etc/retroarch.cfg
sed -i -e "s/# system_directory =/system_directory =\\/storage\\/roms\\/bios/" ${INSTALL}/etc/retroarch.cfg
"""
)
FREEJ2ME_SH = text(
"""
install_java
if [[ -f "/storage/roms/bios/jdk/eeversion" ]]; then
cp -rf /usr/lib/libretro/freej2me-lr.jar ${HOME}/roms/bios
else
exit 1
fi
"""
)
PICO8_SH = text(
"""
if [[ -e "/storage/roms/bios/pico-8" ]]; then
mkdir -p /emuelec/bin/pico-8
cp -rf /storage/roms/bios/pico-8/* /emuelec/bin/pico-8
rm -rf /storage/roms/bios/pico-8
chmod +x /emuelec/bin/pico-8/pico8_dyn
fi
if [[ "${CART}" == *"/splore"* ]]; then
/emuelec/bin/pico-8/pico8_dyn -splore -home /emuelec/configs/pico-8
else
/emuelec/bin/pico-8/pico8_dyn -run "${CART}" -home /emuelec/configs/pico-8
fi
"""
)
FLYCAST_SH = text(
"""
if [ ! -L "/storage/.local/share/flycast" ]; then
mkdir -p "/storage/roms/bios/dc"
rm -rf "/storage/.local/share/flycast"
ln -sf "/storage/roms/bios/dc" "/storage/.local/share/flycast"
fi
"""
)
FLYCASTDOJO_SH = text(
"""
LOCAL_DATA="/storage/roms/bios/dc_dojo"
LOCAL_HOME="/storage/.local/share/flycast-dojo"
if [ ! -L "${LOCAL_HOME}" ]; then
rm -rf "${LOCAL_HOME}"
ln -sf "${LOCAL_DATA}" "${LOCAL_HOME}"
fi
"""
)
AMIBERRY_MK = text(
"""
cp -a ${PKG_DIR}/config/* ${INSTALL}/usr/config/amiberry/
ln -s /storage/roms/bios ${INSTALL}/usr/config/amiberry/kickstarts
"""
)
TIMIDITY_MK = text(
"""
ln -sf /storage/roms/bios/timidity/freepats ${INSTALL}/usr/config/timidity/freepats
ln -sf /storage/.config/timidity/freepats.cfg ${INSTALL}/etc/timidity/freepats.cfg
"""
)
DUCKSTATION_INI = text(
"""
[BIOS]
SearchDirectory = /storage/roms/bios
PathNTSCU =
PathNTSCJ =
PathPAL =
"""
)
DUCKSTATION_MK = text(
"""
cp -rf ${PKG_DIR}/config/* ${INSTALL}/usr/config/emuelec/configs/duckstation
"""
)
DUCKSTATION_SH = text(
"""
CONFIG_DIR="/emuelec/configs/duckstation"
duckstation-nogui -fullscreen -settings "${CONFIG_DIR}/settings.ini" -- "${1}"
if [ ! -d "${CONFIG_DIR}" ]; then
cp -rf "/usr/config/emuelec/configs/duckstation/*" "${CONFIG_DIR}"
fi
"""
)
# packages/sysutils/systemd/scripts/userconfig-setup:11-12 at v4.8.
USERCONFIG = text(
"""
# Copy config files, but don't overwrite - this should replace our sample files
false | cp -iRp /usr/config/* /storage/.config/ &>/dev/null
"""
)
EMUELEC_MK = text(
"""
ln -sf /storage/.config/emuelec ${INSTALL}/emuelec
"""
)
def yabasanshiro_sh(program: str) -> str:
return text(
f"""
BIOS=""
HLEBIOS=$(get_ee_setting hlebios saturn "${{ROMNAME}}")
if [ "${{HLEBIOS}}" != 1 ]; then
if [ -e "/storage/roms/bios/saturn_bios.bin" ]; then
BIOS="-b /storage/roms/bios/saturn_bios.bin"
else
BIOS=""
fi
fi
{{ {program} -r 2 -i "${{1}}" ${{BIOS}}; }} > /emuelec/logs/emuelec.log 2>&1
"""
)
JZINTV_SH = text(
"""
exec jzintv -f1 -z "$RES" -p /storage/roms/bios/ "$ROM" $KBD_OPT
"""
)
M64P_SH = text(
"""
BIOSDIR="/storage/roms/bios/Mupen64plus"
IPLROM="${BIOSDIR}/N64DD IPLROM (J).n64"
[ ! -f "${IPLROM}" ] && IPLROM="${BIOSDIR}/IPL.n64"
if [ -f "${GAMEDIR}/${GAMENAME}.ndd" ] && [ -f "${IPLROM}" ]; then
RENMODE="1"
else
IPLROM=""
fi
mupen64plus --fullscreen --dd-ipl-rom "${IPLROM}" --dd-disk "${DDDISK}" "${FILE}" ;;
"""
)
HATARI_START = text(
"""
HATARI_TMP_CONFIG="$HATARI_TMP_DIR"/.hatari-temp.cfg
cp $HATARI_CONFIG_DIR/Atari-ST-default.cfg "$HATARI_TMP_CONFIG"
hatari --configfile "$HATARI_TMP_CONFIG" --disk-a "$1"
"""
)
HATARI_CFG = text(
"""
[HardDisk]
szHardDiskDirectory = /storage/.hatari
[ROM]
szTosImageFileName = /storage/roms/bios/tos.img
szCartridgeImageFileName =
"""
)
HATARI_MK = text(
"""
cp -R ${PKG_DIR}/config/* ${INSTALL}/usr/config/hatari
"""
)
ADVMAME_MK = text(
"""
if [ "${DEVICE}" == "OdroidGoAdvance" ]; then
cp -r ${PKG_DIR}/config/advmame.rc_oga ${INSTALL}/usr/config/emuelec/configs/advmame/advmame.rc
else
cp -r ${PKG_DIR}/config/advmame.rc ${INSTALL}/usr/config/emuelec/configs/advmame/advmame.rc
fi
"""
)
ADVMAME_RC = text(
"""
dir_rom /storage/roms/mame
dir_sample /storage/roms/bios/advmame/samples
"""
)
ADVMAME_SH = text(
"""
if [[ "${1}" = "arcade" ]]; then
sed -i "s|/roms/mame|/roms/arcade|g" ${CONFIG_DIR}/advmame.rc
else
sed -i "s|/roms/arcade|/roms/mame|g" ${CONFIG_DIR}/advmame.rc
fi
SDL_AUDIODRIVER=alsa advmame ${ARG} -quiet
if [ ! -d "${CONFIG_DIR}" ]; then
cp -rf /usr/config/emuelec/configs/advmame/* "${CONFIG_DIR}/"
fi
"""
)
MAME_SH = text(
"""
if [ ! -f "/storage/roms/bios/mame/hash/fmtowns_cd.xml" ]; then
mkdir -p /storage/roms/bios/mame/hash
cp -rf "/usr/config/emuelec/configs/mame/hash/"fm* "/storage/roms/bios/mame/hash"
fi
if [ ! -f "/storage/roms/bios/mame/ini/mame.ini" ]; then
mkdir -p /storage/roms/bios/mame/ini
cp -rf "/usr/config/emuelec/configs/mame/ini/"mame* "/storage/roms/bios/mame/ini"
fi
if [ ! -f "/storage/.config/retroarch/config/MAME/MAME.opt" ]; then
cp -rf "/usr/config/emuelec/configs/mame/MAME/"MAME* "/storage/.config/retroarch/config/MAME"
fi
"""
)
MAME_INI = text(
"""
rompath /storage/roms/bios/mame/bios;/storage/roms/mame;/storage/roms/arcade
samplepath /storage/roms/bios/mame/samples
cfg_directory /storage/roms/bios/mame/mame/cfg
"""
)
MAME_OPT = text(
"""
mame_boot_to_bios = "disabled"
mame_read_config = "enabled"
"""
)
SCUMMVM_START = text(
"""
CONFIG_DIR="/storage/.config/scummvm"
if [ -d "/storage/.config/scummvm/extra" ]; then
EXTRA="--extrapath=/storage/.config/scummvm/extra"
fi
case $1 in
"sa")
scummvm --fullscreen --joystick=0 $EXTRA "${GAME}"
;;
"libretro")
retroarch -L /tmp/cores/scummvm_libretro.so --config ${RATMPCONF} "${GAME}"
;;
"add")
scummvm --add --path="/storage/roms/scummvm" --recursive
cp ${CONFIG_DIR}/scummvm.ini /storage/roms/bios/scummvm.ini
;;
esac
"""
)
DOLPHIN_SH = text(
"""
XDG_CONFIG_HOME=/emuelec/configs XDG_DATA_HOME=/storage/roms/dolphin /usr/bin/dolphin-emu-nogui -p fbdev "${1}"
"""
)
FILES = {
el.RUN_EMU: RUN_EMU,
el.UTILS: UTILS,
el.RETROARCH_MK: RETROARCH_MK,
f"{el.LIBRETRO}/freej2me/scripts/freej2me.sh": FREEJ2ME_SH,
f"{el.BIN}/pico8.sh": PICO8_SH,
f"{EMU}/flycastsa/scripts/flycast.sh": FLYCAST_SH,
f"{EMU}/flycast-dojo/scripts/flycastdojo.sh": FLYCASTDOJO_SH,
f"{EMU}/amiberry/package.mk": AMIBERRY_MK,
f"{EMU}/amiberry-lite/package.mk": AMIBERRY_MK.replace("amiberry/", "amiberry-lite/"),
f"{el.SX05RE}/tools/sound/timidity/package.mk": TIMIDITY_MK,
f"{EMU}/duckstation/config/settings.ini": DUCKSTATION_INI,
f"{EMU}/duckstation/package.mk": DUCKSTATION_MK,
f"{EMU}/duckstation/scripts/duckstation.sh": DUCKSTATION_SH,
el.USERCONFIG: USERCONFIG,
el.EMUELEC_MK: EMUELEC_MK,
el.YABASANSHIRO_SCRIPTS[0]: yabasanshiro_sh("yabasanshiro"),
el.YABASANSHIRO_SCRIPTS[1]: yabasanshiro_sh("yabasanshiro1_5"),
f"{EMU}/jzintv/scripts/jzintv.sh": JZINTV_SH,
f"{EMU}/mupen64plussa/mupen64plussa-core/m64p.sh": M64P_SH,
f"{EMU}/hatarisa/scripts/hatari.start": HATARI_START,
f"{EMU}/hatarisa/config/conf/Atari-ST-default.cfg": HATARI_CFG,
f"{EMU}/hatarisa/package.mk": HATARI_MK,
f"{EMU}/advancemame/package.mk": ADVMAME_MK,
f"{EMU}/advancemame/config/advmame.rc": ADVMAME_RC,
f"{EMU}/advancemame/config/advmame.rc_oga": ADVMAME_RC,
f"{EMU}/advancemame/bin/advmame.sh": ADVMAME_SH,
f"{el.LIBRETRO}/mame/scripts/mame.sh": MAME_SH,
f"{el.LIBRETRO}/mame/config/ini/mame.ini": MAME_INI,
f"{el.LIBRETRO}/mame/config/MAME/MAME.opt": MAME_OPT,
f"{EMU}/scummvmsa/bin/scummvm.start": SCUMMVM_START,
f"{EMU}/dolphinSA/scripts/dolphin.sh": DOLPHIN_SH,
}
def tree(**changes: str | None) -> RepoTree:
files = dict(FILES)
for path, content in changes.items():
if content is None:
files.pop(path, None)
else:
files[path] = content
return RepoTree.from_files(files)
def records(extractor, **changes: str | None) -> list[dict]:
built = tree(**changes)
return [r.entry() for r in extractor(built, el.Runs(built))]
def ref(path: str, lines: str) -> str:
return f"{path}:{lines}"
class Dispatch(unittest.TestCase):
def test_each_script_is_named_after_the_core_that_starts_it(self):
runs = el.Runs(tree())
self.assertEqual(runs.emulator("flycast.sh"), "flycastsa")
self.assertEqual(runs.emulator("flycastdojo.sh"), "flycast_dojo")
self.assertEqual(runs.emulator("m64p.sh"), "rice, glide64mk2")
self.assertEqual(runs.emulator("mame.sh"), "mame")
self.assertEqual(runs.emulator("freej2me.sh"), "freej2me")
def test_an_arm_without_a_core_test_names_its_platform(self):
self.assertEqual(el.Runs(tree()).emulator("pico8.sh"), "pico8")
def test_a_script_no_longer_started_raises(self):
runs = el.Runs(tree())
with self.assertRaises(LauncherPatternMissing):
runs.of("gone.sh")
def test_a_dispatch_without_any_script_raises(self):
with self.assertRaises(LauncherPatternMissing):
el.Runs(tree(**{el.RUN_EMU: "echo nothing\n"}))
class LibretroRecords(unittest.TestCase):
def test_retroarch_system_directory_comes_from_the_sed(self):
(record,) = records(el.retroarch)
self.assertEqual(record["search_dir"], "/storage/roms/bios")
self.assertEqual(record["kind"], "passes_path")
self.assertEqual(
record["source_ref"], f"{ref(el.RETROARCH_MK, '2')}, {ref(el.RUN_EMU, '80')}"
)
def test_retrorun_is_handed_the_folder_with_d(self):
(record,) = records(el.retrorun)
self.assertEqual(
(record["emulator"], record["search_dir"], record["passes_to"]),
("retrorun", "/storage/roms/bios", "retrorun -d"),
)
self.assertEqual(record["source_ref"], ref(el.RUN_EMU, "4, 81-82"))
def test_freej2me_gets_java_home_and_its_jar_copied(self):
java, jar = records(el.freej2me)
self.assertEqual(java["passes_to"], "environment JAVA_HOME")
self.assertEqual(java["search_dir"], "/storage/roms/bios/jdk")
self.assertEqual(jar["kind"], "copies")
self.assertEqual(jar["names"], ["freej2me-lr.jar"])
self.assertEqual(jar["passes_to"], "${HOME}/roms/bios")
self.assertEqual(jar["depends_on_option"], "/storage/roms/bios/jdk/eeversion exists")
def test_mame_copies_its_own_files_and_hands_no_folder(self):
# The core passes no path option and reads no mame.ini: only the
# copies are the launcher's.
found = records(el.mame)
self.assertEqual([r["kind"] for r in found], ["copies", "copies"])
self.assertEqual([r["names"] for r in found], [["fm*"], ["mame*"]])
class StandaloneRecords(unittest.TestCase):
def test_pico8_moves_the_folder_and_runs_the_binary(self):
(record,) = records(el.pico8)
self.assertEqual(record["kind"], "moves")
self.assertEqual(record["names"], ["pico8_dyn"])
self.assertEqual(record["search_dir"], "/storage/roms/bios/pico-8")
self.assertEqual(record["passes_to"], "/emuelec/bin/pico-8")
self.assertEqual(
record["source_ref"],
f"{ref(el.RUN_EMU, '57-58')}, {ref(f'{el.BIN}/pico8.sh', '1-5, 8-10')}",
)
def test_pico8_without_the_removal_is_not_a_move(self):
changed = PICO8_SH.replace(" rm -rf /storage/roms/bios/pico-8\n", "")
with self.assertRaises(LauncherPatternMissing):
records(el.pico8, **{f"{el.BIN}/pico8.sh": changed})
def test_links_from_scripts_and_packages(self):
found = records(el.links)
self.assertEqual(
[(r["emulator"], r["search_dir"]) for r in found],
[
("flycastsa", "/storage/roms/bios/dc"),
("flycast_dojo", "/storage/roms/bios/dc_dojo"),
("amiberry", "/storage/roms/bios"),
("amiberry-lite", "/storage/roms/bios"),
("timidity", "/storage/roms/bios/timidity/freepats"),
],
)
self.assertEqual(
found[1]["source_ref"],
f"{ref(el.RUN_EMU, '26-27')}, "
f"{ref(f'{EMU}/flycast-dojo/scripts/flycastdojo.sh', '1-5')}",
)
self.assertEqual(
found[2]["passes_to"], "symlink /usr/config/amiberry/kickstarts -> /storage/roms/bios"
)
def test_a_script_without_a_bios_link_raises(self):
with self.assertRaises(LauncherPatternMissing):
records(el.links, **{f"{EMU}/flycastsa/scripts/flycast.sh": "flycast \"${1}\"\n"})
def test_duckstation_scans_the_folder_its_config_names(self):
(record,) = records(el.duckstation)
self.assertEqual((record["kind"], record["search_dir"]), ("scans_dir", "/storage/roms/bios"))
def test_duckstation_config_comes_from_the_boot_copy(self):
"""duckstation.sh quotes its glob: its copy names a file called *, so
the settings.ini in place is the one userconfig-setup copies from
/usr/config, an existing file kept (false | cp -i)."""
(record,) = records(el.duckstation)
self.assertEqual(
record["installs"],
{"path": "/storage/.config/emuelec/configs/duckstation/settings.ini",
"source_ref": f"{EMU}/duckstation/package.mk:1, {el.USERCONFIG}:2"},
)
self.assertIs(record["kept_if_present"], True)
self.assertEqual(record["overrides"], [])
def test_an_unquoted_glob_in_the_start_script_installs_too(self):
unquoted = DUCKSTATION_SH.replace(
'"/usr/config/emuelec/configs/duckstation/*"', "/usr/config/emuelec/configs/duckstation/*"
)
(record,) = records(el.duckstation, **{f"{EMU}/duckstation/scripts/duckstation.sh": unquoted})
self.assertEqual(record["installs"]["path"], "/emuelec/configs/duckstation/settings.ini")
self.assertIs(record["kept_if_present"], True)
def test_without_a_boot_copy_the_quoted_glob_installs_nothing(self):
with self.assertRaises(LauncherPatternMissing):
records(el.duckstation, **{el.USERCONFIG: "#!/bin/sh\n"})
def test_an_overwriting_boot_copy_does_not_keep_the_file(self):
(record,) = records(el.duckstation, **{
el.USERCONFIG: "cp -Rp /usr/config/* /storage/.config/ &>/dev/null\n",
})
self.assertIs(record["kept_if_present"], False)
def test_duckstation_with_a_fixed_bios_path_raises(self):
changed = DUCKSTATION_INI.replace("PathPAL =", "PathPAL = /storage/roms/bios/scph5502.bin")
with self.assertRaises(LauncherPatternMissing):
records(el.duckstation, **{f"{EMU}/duckstation/config/settings.ini": changed})
def test_yabasanshiro_passes_the_bios_unless_hle_is_chosen(self):
new, old = records(el.yabasanshiro)
self.assertEqual((new["emulator"], old["emulator"]), ("yabasanshiroSA", "yabasanshiroSA1_5"))
self.assertEqual(new["names"], ["saturn_bios.bin"])
self.assertEqual(new["passes_to"], "yabasanshiro -b")
self.assertEqual(old["passes_to"], "yabasanshiro1_5 -b")
self.assertEqual(new["depends_on_option"], "hlebios != 1")
self.assertIs(new["fails_when_missing"], False)
def test_jzintv_is_handed_the_bios_folder(self):
(record,) = records(el.jzintv)
self.assertEqual((record["search_dir"], record["passes_to"]), ("/storage/roms/bios", "jzintv -p"))
def test_mupen64plus_tries_its_ipl_names_in_order(self):
(record,) = records(el.mupen64plus)
self.assertEqual(record["kind"], "names_searched")
self.assertEqual(record["names"], ["N64DD IPLROM (J).n64", "IPL.n64"])
self.assertEqual(record["search_dir"], "/storage/roms/bios/Mupen64plus")
self.assertEqual(record["passes_to"], "mupen64plus --dd-ipl-rom")
self.assertEqual(record["depends_on_option"], '-f "${GAMEDIR}/${GAMENAME}.ndd"')
def test_mupen64plus_without_a_fallback_raises(self):
changed = M64P_SH.replace('[ ! -f "${IPLROM}" ] && IPLROM="${BIOSDIR}/IPL.n64"\n', "")
with self.assertRaises(LauncherPatternMissing):
records(el.mupen64plus, **{f"{EMU}/mupen64plussa/mupen64plussa-core/m64p.sh": changed})
def test_hatari_reads_the_tos_named_by_its_default_config(self):
(record,) = records(el.hatari)
self.assertEqual(record["names"], ["tos.img"])
self.assertEqual(
record["passes_to"], "Atari-ST-default.cfg [ROM] szTosImageFileName (--configfile)"
)
def test_advancemame_one_record_when_every_device_agrees(self):
roms, arcade, samples = records(el.advancemame)
self.assertEqual(samples["search_dir"], "/storage/roms/bios/advmame/samples")
self.assertNotIn("device", samples)
# advmame.sh rewrites /roms/mame to /roms/arcade for the arcade system.
self.assertEqual(
(roms["search_dir"], roms["passes_to"], roms["depends_on_option"]),
("/storage/roms/mame", "advmame.rc dir_rom", "system != arcade"),
)
self.assertNotIn("systems", roms)
self.assertEqual((arcade["search_dir"], arcade["systems"]), ("/storage/roms/arcade", ["arcade"]))
self.assertTrue(arcade["source_ref"].endswith("bin/advmame.sh:1-2, 6"))
def test_advancemame_devices_that_disagree_raise(self):
changed = ADVMAME_RC.replace("bios/advmame", "bios/other")
with self.assertRaises(LauncherPatternMissing):
records(el.advancemame, **{f"{EMU}/advancemame/config/advmame.rc_oga": changed})
def test_scummvm_extrapath_and_the_ini_copied_for_the_core(self):
extra, ini = records(el.scummvm)
self.assertEqual(extra["passes_to"], "scummvm --extrapath")
self.assertEqual(extra["depends_on_option"], "/storage/.config/scummvm/extra exists")
self.assertEqual((ini["emulator"], ini["build"]), ("scummvm", "libretro"))
self.assertEqual(ini["search_dir"], "/storage/.config/scummvm")
self.assertEqual(ini["passes_to"], "/storage/roms/bios/scummvm.ini")
def test_dolphin_data_home(self):
(record,) = records(el.dolphin)
self.assertEqual(record["passes_to"], "environment XDG_DATA_HOME")
self.assertEqual(record["search_dir"], "/storage/roms/dolphin")
def test_midi_sources_that_read_the_bios_folder(self):
found = records(el.midi)
self.assertEqual(
[(r["emulator"], r["kind"], r["names"], r["search_dir"]) for r in found],
[
("fluidsynth", "fixed_name", ["sc/SC-55.sf2"], "/storage/roms/bios"),
("mt32d", "passes_path", [], "/roms/bios"),
],
)
self.assertEqual(found[0]["depends_on_option"], "ra_midi_output = fluidsynth")
self.assertEqual(
found[1]["source_ref"], f"{ref(el.RUN_EMU, '7-9')}, {ref(el.UTILS, '1, 10-11')}"
)
def test_every_extractor_runs_on_the_fixtures(self):
found = el.extract_launchers(tree())
# The two mame.ini folders are no longer records.
self.assertEqual(len(found), 26)
class Revision(unittest.TestCase):
def test_the_revision_is_the_one_the_bios_list_is_read_at(self):
url = (
"https://raw.githubusercontent.com/EmuELEC/EmuELEC/v4.8/"
"packages/sx05re/emuelec/bin/batocera/batocera-systems"
)
self.assertEqual(launcher_revision(url), "v4.8")
def test_a_url_without_a_revision_raises(self):
with self.assertRaises(ValueError):
launcher_revision("https://example.org/batocera-systems")
class PlatformRecords(unittest.TestCase):
"""The launchers platforms/emuelec.yml carries, as the scraper wrote them."""
@classmethod
def setUpClass(cls) -> None:
cls.config = yaml.safe_load((REPO_ROOT / "platforms" / "emuelec.yml").read_text())
def test_the_platform_carries_launchers(self):
self.assertTrue(self.config.get("launchers"))
def test_every_reference_is_in_the_emuelec_packages(self):
for record in self.config["launchers"]:
for part in record["source_ref"].split(", "):
if ":" in part:
self.assertTrue(part.startswith(f"{el.SX05RE}/"), record)
if __name__ == "__main__":
unittest.main()