import contextlib import gzip import io import json import os import runpy import shutil import struct import tempfile import unittest from pathlib import Path from unittest import mock BIN = Path(__file__).resolve().parents[1] / "linuxfs/overlay/usr/local/bin" FEX = runpy.run_path(str(BIN / "droiddeck-fex")) G = FEX["main"].__globals__ def elf(machine, data=1, size=64): head = bytearray(size) head[0:4] = b"\x7fELF" head[4] = 2 head[5] = data struct.pack_into("H", head, 18, machine) return bytes(head) class FexTestCase(unittest.TestCase): def setUp(self): self.tmp = Path(tempfile.mkdtemp()) self.addCleanup(shutil.rmtree, self.tmp, True) self.home = self.tmp / "home" self.steam = self.home / ".local/share/Steam" (self.steam / "steamapps/common").mkdir(parents=True) state = self.home / ".local/share/droiddeck-fex" patches = { "HOME": str(self.home), "STEAM_ROOT": str(self.steam), "LIBRARIES": (str(self.tmp / "sd"),), "STATE": str(state), "CONFIG_DIR": str(state / "config"), "DATA_DIR": str(state / "data"), "CACHE_DIR": str(self.home / ".cache/droiddeck-fex"), "STATUS_FILE": str(state / "status.json"), "WANTED": str(self.home / ".config/droiddeck/fex-wanted"), "PRELOADS": str(self.tmp / "preloads"), "HOST_PRELOAD": str(self.tmp / "ld.so.preload"), } patcher = mock.patch.dict(G, patches) patcher.start() self.addCleanup(patcher.stop) def write(self, path, data=b"", mode=0o644): path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(data if isinstance(data, bytes) else data.encode()) path.chmod(mode) return path def fex_tool(self, library=None, server=True, thunks=True): tool = Path(library or self.steam) / "steamapps/common/FEX-Emu" self.write(tool / "usr/bin/FEX", elf(183), 0o755) if server: self.write(tool / "usr/bin/FEXServer", elf(183), 0o755) if thunks: (tool / "usr/lib/aarch64-linux-gnu/fex-emu/HostThunks").mkdir(parents=True) (tool / "usr/share/fex-emu/GuestThunks").mkdir(parents=True) self.write(tool / "usr/share/fex-emu/ThunksDB.json", "{}") self.write(Path(library or self.steam) / "steamapps/appmanifest_3127680.acf", '"AppState" { "installdir" "FEX-Emu" }') return tool def runtime(self, version="3.0.20260805.254768", lines=None): runtime = self.steam / "steamapps/common/SteamLinuxRuntime_sniper" platform = runtime / ("sniper_platform_" + version) files = platform / "files" self.write(files / "lib/x86_64-linux-gnu/libc.so.6", elf(62)) self.write(files / "ab/cdef-1.bin", b"blob") self.write(files / "share/ca/real.crt", b"cert") mtree = lines or [ "#mtree", ". type=dir", "./bin type=dir", "./lib64 type=dir", "./lib64/ld-linux-x86-64.so.2 type=link link=/usr/lib/x86_64-linux-gnu/ld-2.31.so", "./lib/x86_64-linux-gnu/libc.so.6 type=file mode=644 size=64", "./bin/\\133 type=file mode=755 size=4 contents=./ab/cdef-1.bin", "./share/ca/Na\\075me.crt type=link link=real.crt", "./etc/ssl/cert.pem type=link link=/etc/ssl/certs/ca.pem", ] with gzip.open(platform / "usr-mtree.txt.gz", "wt") as out: out.write("\n".join(mtree) + "\n") self.write(self.steam / "steamapps/appmanifest_1628350.acf", '"AppState" { "installdir" "SteamLinuxRuntime_sniper" }') return platform def quiet(self, function, *args): with contextlib.redirect_stderr(io.StringIO()) as err: return function(*args), err.getvalue() class ArchitectureTest(FexTestCase): def test_elf_machines(self): self.assertEqual(FEX["elf_arch"](self.write(self.tmp / "a", elf(62))), "x86_64") self.assertEqual(FEX["elf_arch"](self.write(self.tmp / "b", elf(3))), "i386") self.assertEqual(FEX["elf_arch"](self.write(self.tmp / "c", elf(183))), "arm64") self.assertEqual(FEX["elf_arch"](self.write(self.tmp / "d", elf(62, data=2))), "x86_64") self.assertEqual(FEX["elf_arch"](self.write(self.tmp / "e", elf(8))), "other") self.assertIsNone(FEX["elf_arch"](self.write(self.tmp / "f", "#!/bin/sh\n"))) self.assertIsNone(FEX["elf_arch"](self.tmp / "missing")) def test_a_script_follows_the_programs_beside_it(self): game = self.tmp / "game" script = self.write(game / "start.sh", "#!/bin/bash\n./bin/game\n") self.assertFalse(FEX["needs_fex"](str(script))) self.write(game / "bin/game.x86_64", elf(62), 0o755) self.assertTrue(FEX["needs_fex"](str(script))) self.assertTrue(FEX["needs_fex"](str(game))) self.write(game / "bin/game.arm64", elf(183), 0o755) self.assertFalse(FEX["needs_fex"](str(script))) self.assertTrue(FEX["needs_fex"](str(game / "bin/game.x86_64"))) self.assertFalse(FEX["needs_fex"](str(game / "bin/game.arm64"))) def test_the_scan_stops_at_its_depth(self): game = self.tmp / "deep" self.write(game / "a/b/c/d/e/game", elf(62), 0o755) self.assertFalse(FEX["needs_fex"](str(game))) self.write(game / "a/b/game", elf(62), 0o755) self.assertTrue(FEX["needs_fex"](str(game))) def test_appimage_offset_is_the_end_of_the_section_headers(self): head = bytearray(elf(62)) struct.pack_into("= 0x07564428 else b"") body += struct.pack("