Files
DroidDeck/tools/tests/test_steam_libraries.py
T
maxjivi05 f89dc04072 fix: Proton's EasyAntiCheat and BattlEye runtimes are not listed as games
Steam installs Proton EasyAntiCheat Runtime (1826330) and Proton BattlEye Runtime (1161040) as tool depots. The launcher's Games tab and the desktop's game entries now skip them like the other runtimes.
2026-10-24 00:47:45 -04:00

168 lines
7.9 KiB
Python

import importlib.machinery
import importlib.util
import json
import os
import re
import runpy
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
BIN = Path(__file__).resolve().parents[1] / "linuxfs/overlay/usr/local/bin"
def load(name):
loader = importlib.machinery.SourceFileLoader(name.replace("-", "_"), str(BIN / name))
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
library = load("droiddeck-steam-library")
desktop = load("droiddeck-desktop-games")
COMPAT = runpy.run_path(str(BIN / "steam-compatibility"))
def entry(index, path, label=""):
return '\t"%d"\n\t{\n\t\t"path"\t\t"%s"\n\t\t"label"\t\t"%s"\n\t}\n' % (index, path, label)
def manifest(steamapps, appid, name="Game", installdir="Game", flags="4"):
steamapps.mkdir(parents=True, exist_ok=True)
(steamapps / ("appmanifest_%s.acf" % appid)).write_text(
'"AppState"\n{\n\t"appid"\t\t"%s"\n\t"name"\t\t"%s"\n\t"StateFlags"\t\t"%s"\n\t"installdir"\t\t"%s"\n}\n'
% (appid, name, flags, installdir))
class LibraryListTest(unittest.TestCase):
def setUp(self):
temp = tempfile.TemporaryDirectory()
self.addCleanup(temp.cleanup)
self.root = Path(temp.name)
self.steam = self.root / "Steam"
self.vdf = self.steam / "steamapps/libraryfolders.vdf"
self.vdf.parent.mkdir(parents=True)
self.games = self.root / "Games"
self.card = self.root / "card"
self.pc = self.games / "PC"
self.old = self.games / "Old"
self.user = self.root / "user-library"
for folder in (self.pc, self.old, self.user, self.card):
(folder / "steamapps").mkdir(parents=True)
manifest(self.pc / "steamapps", 10)
self.vdf.write_text('"libraryfolders"\n{\n' + entry(0, self.steam) + entry(1, self.old, "Old")
+ entry(2, self.user, "Mine") + entry(3, self.card, "SD Card") + "}\n")
self.listing = self.root / "steam-libraries.json"
for target in (patch.object(library, "GAMES_DIR", str(self.games) + "/"),
patch.object(library, "LIBRARIES", ((str(self.card), "SD Card"),)),
patch("builtins.print")):
target.start()
self.addCleanup(target.stop)
def run_main(self, listing=True):
with patch.dict(os.environ), patch("sys.argv", ["droiddeck-steam-library", str(self.steam)]):
os.environ.pop("BL_STEAM_LIBRARIES", None)
if listing:
os.environ["BL_STEAM_LIBRARIES"] = str(self.listing)
library.main()
return self.vdf.read_text()
def paths(self, text):
return [Path(p) for p in re.findall(r'"path"\s+"([^"]*)"', text)]
def test_listed_games_folder_is_registered_and_the_rest_are_forgotten(self):
self.listing.write_text(json.dumps([{"path": str(self.pc), "label": "PC (PNY USB drive)"}]))
text = self.run_main()
self.assertEqual([self.steam, self.user, self.pc], self.paths(text))
self.assertIn('"label"\t\t"PC (PNY USB drive)"', text)
self.assertIn('"10"\t\t"0"', text)
self.assertIn('"label"\t\t"PC (PNY USB drive)"', (self.pc / "libraryfolder.vdf").read_text())
def test_absent_listed_folder_is_forgotten_and_comes_back(self):
self.listing.write_text(json.dumps([{"path": str(self.pc), "label": "PC"}, {"path": str(self.card), "label": "SD Card"}]))
self.run_main()
moved = self.root / "unplugged"
self.pc.rename(moved)
self.assertNotIn(self.pc, self.paths(self.run_main()))
moved.rename(self.pc)
self.assertIn(self.pc, self.paths(self.run_main()))
self.assertIn(self.card, self.paths(self.vdf.read_text()))
def test_without_a_list_only_the_card_is_managed(self):
text = self.run_main(listing=False)
self.assertEqual([self.steam, self.old, self.user, self.card], self.paths(text))
def test_unreadable_list_keeps_games_folder_entries(self):
self.listing.write_text("{not json")
text = self.run_main()
self.assertEqual([self.steam, self.old, self.user, self.card], self.paths(text))
def test_labels_cannot_break_the_vdf(self):
self.listing.write_text(json.dumps([{"path": str(self.pc), "label": 'P"C\\'}]))
text = self.run_main()
self.assertIn('"label"\t\t"P C "', text)
class CompatLibrariesTest(unittest.TestCase):
def setUp(self):
temp = tempfile.TemporaryDirectory()
self.addCleanup(temp.cleanup)
self.root = Path(temp.name)
self.steam = self.root / "Steam"
self.pc = self.root / "Games/My PC"
(self.steam / "steamapps").mkdir(parents=True)
(self.steam / "steamapps/libraryfolders.vdf").write_text(
'"libraryfolders"\n{\n' + entry(0, self.steam) + entry(1, self.pc, "PC") + "}\n")
def test_titles_and_tools_in_a_registered_library_are_found(self):
manifest(self.steam / "steamapps", 20)
manifest(self.pc / "steamapps", 30)
manifest(self.pc / "steamapps", 3127680, installdir="FEX")
self.assertEqual(["20", "30"], COMPAT["installed_apps"](str(self.steam)))
self.assertEqual(["1628350", "4183110"], COMPAT["fex_missing"](str(self.steam)))
def test_newest_proton_across_libraries_wins(self):
for root, build in ((self.steam, "100"), (self.pc, "200")):
depot = root / "steamapps/common/Proton Experimental (ARM64)"
(depot / "files/bin-arm64").mkdir(parents=True)
(depot / "version").write_text(build + " x\n")
self.assertEqual(str(self.pc / "steamapps/common/Proton Experimental (ARM64)"), COMPAT["find_source"](str(self.steam)))
def test_missing_list_still_reads_the_client_library(self):
(self.steam / "steamapps/libraryfolders.vdf").unlink()
self.assertEqual(str(self.steam), COMPAT["libraries"](str(self.steam))[0])
class DesktopGamesTest(unittest.TestCase):
def test_games_in_registered_libraries_get_entries(self):
with tempfile.TemporaryDirectory() as temp:
root = Path(temp)
steam, pc = root / "Steam", root / "Games/PC"
manifest(steam / "steamapps", 40, name="Internal")
manifest(pc / "steamapps", 50, name="Outside")
(steam / "steamapps/libraryfolders.vdf").write_text('"libraryfolders"\n{\n' + entry(0, steam) + entry(1, pc) + "}\n")
with patch.object(desktop, "STEAM_ROOT", str(steam)), patch.object(desktop, "APPS_DIR", str(root / "apps")), \
patch.object(desktop, "LIBRARIES", ((str(steam), "Steam Games"), (str(root / "card"), "SD Games"))):
self.assertEqual({40: "Internal", 50: "Outside"}, desktop.write_entries())
def test_runtimes_and_compatibility_tools_get_none(self):
with tempfile.TemporaryDirectory() as temp:
root = Path(temp)
steam = root / "Steam"
manifest(steam / "steamapps", 40, name="Game")
manifest(steam / "steamapps", 1628350, name="Steam Linux Runtime 3.0 (sniper)", installdir="SteamLinuxRuntime_sniper")
manifest(steam / "steamapps", 1826330, name="Proton EasyAntiCheat Runtime", installdir="Proton EasyAntiCheat Runtime")
manifest(steam / "steamapps", 1161040, name="Proton BattlEye Runtime", installdir="Proton BattlEye Runtime")
manifest(steam / "steamapps", 5000, name="Some Proton", installdir="Some Proton")
(steam / "steamapps/common/Some Proton").mkdir(parents=True)
(steam / "steamapps/common/Some Proton/toolmanifest.vdf").write_text('"manifest"\n{\n}\n')
with patch.object(desktop, "STEAM_ROOT", str(steam)), patch.object(desktop, "APPS_DIR", str(root / "apps")), \
patch.object(desktop, "LIBRARIES", ((str(steam), "Steam Games"),)):
self.assertEqual({40: "Game"}, desktop.write_entries())
if __name__ == "__main__":
unittest.main()