Files
spoopyghosty0--frameport/tests/test_refresh_rate.py
T
Nick Leach a8d56567c8 Fix Quest refresh rate overrides through SteamVR
Apply saved refresh rates through SteamVR before Lepton starts, preserve launcher upgrade ordering, and cover the behavior with regression tests. Update the setting documentation and fix repository lint errors blocking CI.
2026-10-17 01:42:54 -04:00

127 lines
6.2 KiB
Python

"""Refresh overrides must reach SteamVR before Lepton: its OpenXR runtime cannot switch display modes."""
import ctypes
import json
from types import SimpleNamespace
import pytest
from test_agent import load_agent
def setup(monkeypatch, tmp_path, text="refresh_rate=90.0\n"):
a = load_agent(monkeypatch, tmp_path)
base = tmp_path / "game"
base.mkdir()
(base / "settings.conf").write_text(text)
dep = {"base": str(base), "appid": 4200460859, "kind": "quest"}
calls = []
vr = SimpleNamespace(refresh_rate=lambda appid, rate: calls.append((appid, rate)),
close=lambda: calls.append("close"))
monkeypatch.setattr(a.SteamVRSettings, "open", classmethod(lambda cls: vr))
monkeypatch.setattr(a, "container_running", lambda appid: False)
return a, base, dep, calls
def test_save_writes_both_adapter_configs_and_steamvr(monkeypatch, tmp_path):
a, base, dep, calls = setup(monkeypatch, tmp_path)
monkeypatch.setattr(a, "deployment", lambda pkg: dep)
monkeypatch.setattr(a, "install_controller_models", lambda *args: None)
result = a.cmd_set_settings({"package": "com.coatsink.alone", "settings": {"refresh_rate": 96.0}})
assert calls == [(4200460859, 96.0), "close"]
assert result["refresh_rate"] == {"refresh_rate": 96.0, "app_key": "steam.app.4200460859"}
assert (base / "settings.conf").read_text() == "refresh_rate=96.0\n"
assert (base / "lepton-data/external/Android/data/com.coatsink.alone/files/framebridge.conf").read_text() == \
"refresh_rate=96.0\n"
def test_unavailable_steamvr_retries_on_launch_and_running_game_waits(monkeypatch, tmp_path):
a, base, dep, calls = setup(monkeypatch, tmp_path)
open_vr = a.SteamVRSettings.open
monkeypatch.setattr(a.SteamVRSettings, "open", classmethod(lambda cls: None))
assert "unavailable" in a.sync_refresh_rate("com.x.y", dep)["skipped"]
assert not (base / a.REFRESH_RATE_MARKER).exists()
monkeypatch.setattr(a.SteamVRSettings, "open", open_vr)
monkeypatch.setattr(a, "container_running", lambda appid: True)
assert "running" in a.sync_refresh_rate("com.x.y", dep)["skipped"]
assert calls == []
monkeypatch.setattr(a, "deployment", lambda pkg: dep)
a.refresh_rate_worker("com.x.y")
assert calls == [(4200460859, 90.0), "close"]
assert (base / a.REFRESH_RATE_MARKER).read_text() == "90.0"
@pytest.mark.parametrize("reset", ["refresh_rate=0\n", "scale=1.0\n"])
def test_games_choice_and_rebuild_without_override_clear_owned_preference(monkeypatch, tmp_path, reset):
a, base, dep, calls = setup(monkeypatch, tmp_path)
a.sync_refresh_rate("com.x.y", dep)
(base / "settings.conf").write_text(reset)
assert a.sync_refresh_rate("com.x.y", dep)["refresh_rate"] == 0
assert calls == [(4200460859, 90.0), "close", (4200460859, 0.0), "close"]
assert not (base / a.REFRESH_RATE_MARKER).exists()
@pytest.mark.parametrize("text", ["scale=1.0\n", "refresh_rate=bad\n", "refresh_rate=nan\n",
"refresh_rate=200\n", "refresh_rate=-90\n"])
def test_unset_or_invalid_rate_does_not_change_steamvr(monkeypatch, tmp_path, text):
a, base, dep, calls = setup(monkeypatch, tmp_path, text)
a.sync_refresh_rate("com.x.y", dep)
assert calls == []
assert not (base / a.REFRESH_RATE_MARKER).exists()
def test_steamvr_error_keeps_override_pending_and_closes(monkeypatch, tmp_path):
a, base, dep, calls = setup(monkeypatch, tmp_path)
def fail(*args):
raise a.AgentError("write failed")
vr = SimpleNamespace(refresh_rate=fail, close=lambda: calls.append("close"))
monkeypatch.setattr(a.SteamVRSettings, "open", classmethod(lambda cls: vr))
assert a.sync_refresh_rate("com.x.y", dep) == {"skipped": "write failed"}
assert calls == ["close"] and not (base / a.REFRESH_RATE_MARKER).exists()
@pytest.mark.parametrize("config_sync", [False, True])
@pytest.mark.parametrize("missing_play_log", [False, True])
def test_new_and_upgraded_launchers_select_rate_before_lepton(monkeypatch, tmp_path, config_sync, missing_play_log):
a, base, dep, calls = setup(monkeypatch, tmp_path)
anchors = tmp_path / "anchors"
anchor = anchors / "com.x.y"
anchor.mkdir(parents=True)
monkeypatch.setattr(a, "ANCHORS", str(anchors))
dep["config_sync"] = {"config.xml": {"width": "eye_width"}} if config_sync else {}
a.write_launcher(str(anchor), str(base), "com.x.y", "Game", dep["appid"], "/lepton", {}, config_sync=config_sync)
path = anchor / "launch.sh"
text = path.read_text()
line = next(line for line in text.splitlines(True) if " _refresh_rate " in line)
assert "timeout 10s" in line and "9>&- || true" in line
assert text.index(line) < text.index("\nsetsid ")
assert a.set_refresh_rate_line(text, "com.x.y") == text
old = text.replace(line, "")
if missing_play_log:
old = "".join(line for line in old.splitlines(True) if "plays.log" not in line)
path.write_text(old)
(anchor / "deployment.json").write_text(json.dumps(dep))
assert a.upgrade_launchers() == ["com.x.y"]
assert path.read_text().count(" _refresh_rate ") == 1
assert a.upgrade_launchers() == []
assert a.set_refresh_rate_line("exec proton run game.exe\n", "com.x.y") == "exec proton run game.exe\n"
def test_settings_function_table_uses_per_app_float_and_removes_only_its_key(monkeypatch, tmp_path):
a = load_agent(monkeypatch, tmp_path)
calls = []
set_float = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_float,
ctypes.POINTER(ctypes.c_int))(
lambda section, key, rate, err: calls.append((section, key, rate)))
remove_key = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int))(
lambda section, key, err: calls.append((section, key, "remove")))
vr = a.SteamVRSettings()
vr.ct = ctypes
vr.table = (ctypes.c_void_p * 11)()
vr.table[3] = ctypes.cast(set_float, ctypes.c_void_p)
vr.table[10] = ctypes.cast(remove_key, ctypes.c_void_p)
vr.refresh_rate(4200460859, 90)
vr.refresh_rate(4200460859, 0)
assert calls == [(b"steam.app.4200460859", b"preferredRefreshRate", 90.0),
(b"steam.app.4200460859", b"preferredRefreshRate", "remove")]