testsAndMisc/linux_configuration/tests/test_monitor_autoconfig.py
Krzysztof kuhy Rudnicki 53da9b5a12 Monitors: best mode automatically on hotplug
systemd user service watches RandR output events and puts every
connected monitor at native resolution + max refresh, DP-0 primary
on the left, unplugged outputs switched off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWXa4ytX9adN7qpMAop
2026-11-11 15:41:26 +01:00

190 lines
6.7 KiB
Python

"""Tests for linux_configuration/features/monitor_autoconfig.py (I/O shell)."""
from __future__ import annotations
import subprocess
from types import SimpleNamespace
from typing import TYPE_CHECKING
import monitor_autoconfig
from monitor_autoconfig import apply_once, main, watch
import pytest
if TYPE_CHECKING:
from collections.abc import Callable
OFF = """\
DP-0 connected primary (normal left inverted right x axis y axis)
3840x2160 60.00 + 144.00
HDMI-0 connected 2560x1440+3840+0 (normal left inverted right x axis y axis)
2560x1440 59.95 + 143.98*
"""
BEST = """\
DP-0 connected primary 3840x2160+0+0 (normal left inverted right x axis y axis)
3840x2160 60.00 + 144.00*
HDMI-0 connected 2560x1440+3840+0 (normal left inverted right x axis y axis)
2560x1440 59.95 + 143.98*
"""
WANT = [
"xrandr", "--output", "DP-0", "--mode", "3840x2160", "--rate", "144.00",
"--pos", "0x0", "--primary", "--output", "HDMI-0", "--mode", "2560x1440",
"--rate", "143.98", "--pos", "3840x0",
] # fmt: skip
NO_MODES_YET = """\
DP-0 connected primary (normal left inverted right x axis y axis)
HDMI-0 connected 2560x1440+3840+0 (normal left inverted right x axis y axis)
2560x1440 59.95 + 143.98*
"""
class FakeXrandr:
"""Serves queued ``xrandr --query`` texts and records apply calls."""
def __init__(self, *queries: str, fail_apply: bool = False) -> None:
self.queries = list(queries)
self.applied: list[list[str]] = []
self.fail_apply = fail_apply
def __call__(self, argv: list[str]) -> str:
if argv == ["xrandr", "--query"]:
return self.queries.pop(0)
self.applied.append(argv)
if self.fail_apply:
raise subprocess.CalledProcessError(1, argv, stderr="crtc failed")
return ""
class FakeDisplay:
"""Xlib display double: ``events`` batches, then the X server goes away."""
def __init__(self, batches: list[int]) -> None:
self.batches = batches
self.pending = 0
self.selected: list[int] = []
self.handshakes = 0
root = type("Root", (), {"xrandr_select_input": self.selected.append})()
self.screen: Callable[[], object] = type("S", (), {"root": root})
def next_event(self) -> None:
if self.pending:
self.pending -= 1
return
if not self.batches:
raise ConnectionError
self.pending = self.batches.pop(0) - 1
def pending_events(self) -> int:
return self.pending
def xrandr_query_version(self) -> None:
"""X only sends RandR 1.2+ output events after this handshake."""
self.handshakes += 1
def test_apply_once_runs_planned_xrandr() -> None:
"""An off monitor gets switched on with one xrandr call."""
fake = FakeXrandr(OFF)
assert apply_once("DP-0", run=fake) is True
assert fake.applied == [WANT]
def test_apply_once_noop_when_best() -> None:
"""Already-best layout means no xrandr call (no flicker on stray events)."""
fake = FakeXrandr(BEST)
assert apply_once("DP-0", run=fake) is False
assert fake.applied == []
def test_watch_applies_at_start_and_once_per_burst() -> None:
"""A burst of 3 hotplug events is drained and handled by one apply."""
fake = FakeXrandr(OFF, BEST)
display = FakeDisplay([3])
with pytest.raises(ConnectionError):
watch("DP-0", display=display, run=fake, settle=lambda: None)
assert display.handshakes == 1
assert display.selected == [monitor_autoconfig.OUTPUT_CHANGE_MASK]
assert fake.applied == [WANT]
assert fake.queries == []
def test_main_dry_run_prints_command(capsys: pytest.CaptureFixture[str]) -> None:
"""``--dry-run`` prints the xrandr command and changes nothing."""
fake = FakeXrandr(OFF)
assert main(["--dry-run"], run=fake) == 0
assert capsys.readouterr().out.split() == WANT
assert fake.applied == []
def test_main_once_applies(monkeypatch: pytest.MonkeyPatch) -> None:
"""``--once`` applies the layout and exits without watching."""
fake = FakeXrandr(OFF)
monkeypatch.setattr(monitor_autoconfig, "watch", pytest.fail)
assert main(["--once", "--primary", "DP-0"], run=fake) == 0
assert fake.applied == [WANT]
def test_main_default_watches(monkeypatch: pytest.MonkeyPatch) -> None:
"""With no mode flag, ``main`` hands off to ``watch``."""
seen: list[str] = []
monkeypatch.setattr(monitor_autoconfig, "watch", seen.append)
assert main([]) == 0
assert seen == ["DP-0"]
def test_apply_once_waits_for_late_edid_modes() -> None:
"""A just-plugged monitor with no modes yet is re-queried, then applied."""
fake = FakeXrandr(NO_MODES_YET, OFF)
settles: list[None] = []
assert apply_once("DP-0", run=fake, settle=lambda: settles.append(None))
assert settles == [None]
assert fake.applied == [WANT]
def test_apply_once_gives_up_waiting_after_tries() -> None:
"""Modes that never appear stop the retry; the rest is still laid out."""
fake = FakeXrandr(NO_MODES_YET, NO_MODES_YET)
assert apply_once("DP-0", run=fake, settle=lambda: None, tries=2) is True
assert fake.applied[0][:3] == ["xrandr", "--output", "HDMI-0"]
def test_watch_survives_failed_xrandr(capsys: pytest.CaptureFixture[str]) -> None:
"""A failing apply is logged and the watcher keeps handling events."""
fake = FakeXrandr(OFF, OFF, fail_apply=True)
with pytest.raises(ConnectionError):
watch("DP-0", display=FakeDisplay([1]), run=fake, settle=lambda: None)
assert fake.applied == [WANT, WANT]
assert "crtc failed" in capsys.readouterr().err
def test_run_returns_stdout(monkeypatch: pytest.MonkeyPatch) -> None:
"""``_run`` is a checked subprocess call returning stdout."""
calls: list[tuple[list[str], dict[str, object]]] = []
def fake_run(argv: list[str], **kwargs: object) -> SimpleNamespace:
calls.append((argv, kwargs))
return SimpleNamespace(stdout="out")
monkeypatch.setattr(monitor_autoconfig.subprocess, "run", fake_run)
assert monitor_autoconfig._run(["xrandr"]) == "out"
assert calls[0][1]["check"] is True
def test_open_display_uses_xlib(monkeypatch: pytest.MonkeyPatch) -> None:
"""The real display comes from ``Xlib.display.Display()``."""
sentinel = object()
fake_module = SimpleNamespace(Display=lambda: sentinel)
monkeypatch.setattr(
monitor_autoconfig.importlib, "import_module", lambda name: fake_module
)
assert monitor_autoconfig._open_display() is sentinel
def test_settle_sleeps_settle_seconds(monkeypatch: pytest.MonkeyPatch) -> None:
"""The real settle waits ``SETTLE_SECONDS`` for EDID to arrive."""
slept: list[float] = []
monkeypatch.setattr(monitor_autoconfig.time, "sleep", slept.append)
monitor_autoconfig._settle()
assert slept == [monitor_autoconfig.SETTLE_SECONDS]