testsAndMisc/python_pkg/brightness_controller/tests/test_brightness_controller.py

474 lines
18 KiB
Python
Raw Normal View History

"""Tests for brightness_controller module."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from python_pkg.brightness_controller import brightness_controller
# ── _find_als_device ─────────────────────────────────────────────────────
class TestFindAlsDevice:
"""Tests for _find_als_device."""
@patch.object(
Path,
"glob",
return_value=[Path("/sys/bus/iio/devices/iio0/in_illuminance_raw")],
)
def test_found(self, _mock_glob: MagicMock) -> None:
result = brightness_controller._find_als_device()
assert result == Path("/sys/bus/iio/devices/iio0")
@patch.object(Path, "glob", return_value=[])
def test_not_found(self, _mock_glob: MagicMock) -> None:
assert brightness_controller._find_als_device() is None
# ── _read_lux ────────────────────────────────────────────────────────────
class TestReadLux:
"""Tests for _read_lux."""
def test_all_files_present(self, tmp_path: Path) -> None:
(tmp_path / "in_illuminance_raw").write_text("100\n")
(tmp_path / "in_illuminance_scale").write_text("2.0\n")
(tmp_path / "in_illuminance_offset").write_text("5.0\n")
assert brightness_controller._read_lux(tmp_path) == pytest.approx(210.0)
def test_missing_scale(self, tmp_path: Path) -> None:
(tmp_path / "in_illuminance_raw").write_text("50\n")
(tmp_path / "in_illuminance_offset").write_text("0\n")
assert brightness_controller._read_lux(tmp_path) == pytest.approx(50.0)
def test_missing_offset(self, tmp_path: Path) -> None:
(tmp_path / "in_illuminance_raw").write_text("50\n")
(tmp_path / "in_illuminance_scale").write_text("1.0\n")
assert brightness_controller._read_lux(tmp_path) == pytest.approx(50.0)
def test_invalid_scale(self, tmp_path: Path) -> None:
(tmp_path / "in_illuminance_raw").write_text("50\n")
(tmp_path / "in_illuminance_scale").write_text("bad\n")
(tmp_path / "in_illuminance_offset").write_text("0\n")
assert brightness_controller._read_lux(tmp_path) == pytest.approx(50.0)
def test_invalid_offset(self, tmp_path: Path) -> None:
(tmp_path / "in_illuminance_raw").write_text("50\n")
(tmp_path / "in_illuminance_scale").write_text("1.0\n")
(tmp_path / "in_illuminance_offset").write_text("bad\n")
assert brightness_controller._read_lux(tmp_path) == pytest.approx(50.0)
# ── _lux_to_brightness ──────────────────────────────────────────────────
class TestLuxToBrightness:
"""Tests for _lux_to_brightness."""
def test_below_minimum(self) -> None:
assert brightness_controller._lux_to_brightness(-1.0) == 10
def test_at_minimum(self) -> None:
assert brightness_controller._lux_to_brightness(0.0) == 10
def test_above_maximum(self) -> None:
assert brightness_controller._lux_to_brightness(10000.0) == 100
def test_at_maximum(self) -> None:
assert brightness_controller._lux_to_brightness(5000.0) == 100
def test_interpolation(self) -> None:
# Between (5.0, 40) and (50.0, 75), at lux=27.5
assert brightness_controller._lux_to_brightness(27.5) == 57
def test_fallback_return(self) -> None:
"""Exercise the post-loop fallback (unreachable with monotonic curves)."""
nan = float("nan")
with patch.object(
brightness_controller,
"LUX_CURVE",
[(nan, 10), (nan, 99)],
):
assert brightness_controller._lux_to_brightness(50.0) == 99
# ── _run_brightnessctl ───────────────────────────────────────────────────
class TestRunBrightnessctl:
"""Tests for _run_brightnessctl."""
@patch("python_pkg.brightness_controller.brightness_controller.subprocess.run")
def test_captures_stdout(self, mock_run: MagicMock) -> None:
mock_run.return_value = MagicMock(stdout=" some output ")
result = brightness_controller._run_brightnessctl("-l", "-m")
assert result == "some output"
mock_run.assert_called_once_with(
[brightness_controller._BRIGHTNESSCTL, "-l", "-m"],
capture_output=True,
text=True,
check=False,
)
# ── _get_devices ─────────────────────────────────────────────────────────
class TestGetDevices:
"""Tests for _get_devices."""
@patch("python_pkg.brightness_controller.brightness_controller._run_brightnessctl")
def test_returns_backlight_devices(self, mock_run: MagicMock) -> None:
mock_run.return_value = (
"intel_backlight,backlight,50,42%,120000\nkbd_backlight,leds,0,0%,3"
)
devices = brightness_controller._get_devices()
assert len(devices) == 1
assert devices[0].name == "intel_backlight"
assert devices[0].device_class == "backlight"
assert devices[0].current == 42
assert devices[0].percent == "42%"
assert devices[0].max_brightness == 120000
@patch("python_pkg.brightness_controller.brightness_controller._run_brightnessctl")
def test_empty_output(self, mock_run: MagicMock) -> None:
mock_run.return_value = ""
assert brightness_controller._get_devices() == []
@patch("python_pkg.brightness_controller.brightness_controller._run_brightnessctl")
def test_too_few_fields(self, mock_run: MagicMock) -> None:
mock_run.return_value = "a,b,c"
assert brightness_controller._get_devices() == []
# ── _get_brightness ──────────────────────────────────────────────────────
class TestGetBrightness:
"""Tests for _get_brightness."""
@patch("python_pkg.brightness_controller.brightness_controller._run_brightnessctl")
def test_valid(self, mock_run: MagicMock) -> None:
mock_run.side_effect = ["123", "intel_backlight,backlight,50,42%,120000"]
assert brightness_controller._get_brightness("intel_backlight") == 42
@patch("python_pkg.brightness_controller.brightness_controller._run_brightnessctl")
def test_empty_get_output(self, mock_run: MagicMock) -> None:
mock_run.return_value = ""
assert brightness_controller._get_brightness("intel_backlight") == -1
@patch("python_pkg.brightness_controller.brightness_controller._run_brightnessctl")
def test_info_no_valid_fields(self, mock_run: MagicMock) -> None:
mock_run.side_effect = ["123", "a,b,c"]
assert brightness_controller._get_brightness("intel_backlight") == -1
# ── _set_brightness ──────────────────────────────────────────────────────
class TestSetBrightness:
"""Tests for _set_brightness."""
@patch("python_pkg.brightness_controller.brightness_controller._run_brightnessctl")
def test_calls_brightnessctl(self, mock_run: MagicMock) -> None:
brightness_controller._set_brightness("intel_backlight", 75)
mock_run.assert_called_once_with("-d", "intel_backlight", "set", "75%")
# ── Device NamedTuple ────────────────────────────────────────────────────
class TestDevice:
"""Tests for Device NamedTuple."""
def test_create(self) -> None:
d = brightness_controller.Device("test", "backlight", 50, "50%", 1000)
assert d.name == "test"
assert d.max_brightness == 1000
# ── BrightnessController ────────────────────────────────────────────────
def _make_controller(
devices: list[brightness_controller.Device] | None = None,
als_path: Path | None = None,
*,
daemon_state: bool = False,
) -> brightness_controller.BrightnessController:
"""Create a BrightnessController with all Tk operations mocked."""
if devices is None:
devices = [
brightness_controller.Device(
"intel_backlight", "backlight", 50, "50%", 120000
)
]
with (
patch(
"python_pkg.brightness_controller.brightness_controller._get_devices",
return_value=devices,
),
patch(
"python_pkg.brightness_controller.brightness_controller._find_als_device",
return_value=als_path,
),
patch.object(
brightness_controller.BrightnessController,
"_read_daemon_state",
return_value=daemon_state,
),
patch(
"python_pkg.brightness_controller.brightness_controller.tk.Tk"
) as mock_tk,
patch(
"python_pkg.brightness_controller.brightness_controller.tk.StringVar"
) as mock_str_var,
patch(
"python_pkg.brightness_controller.brightness_controller.tk.IntVar"
) as mock_int_var,
patch("python_pkg.brightness_controller.brightness_controller.ttk"),
patch(
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=50,
),
):
mock_root = MagicMock()
mock_tk.return_value = mock_root
mock_root.after = MagicMock()
mock_str_var.return_value = MagicMock()
mock_int_var.return_value = MagicMock()
return brightness_controller.BrightnessController()
class TestBrightnessControllerInit:
"""Tests for BrightnessController.__init__."""
def test_single_device(self) -> None:
ctrl = _make_controller()
assert ctrl.current_device == "intel_backlight"
def test_no_devices(self) -> None:
ctrl = _make_controller(devices=[])
assert ctrl.current_device == ""
def test_multiple_devices(self) -> None:
devices = [
brightness_controller.Device("led0", "leds", 0, "0%", 3),
brightness_controller.Device("intel_bl", "backlight", 50, "50%", 120000),
]
ctrl = _make_controller(devices=devices)
# Should prefer backlight device
assert ctrl.current_device == "intel_bl"
def test_with_als(self, tmp_path: Path) -> None:
ctrl = _make_controller(als_path=tmp_path)
assert ctrl.als_path == tmp_path
def test_auto_mode_enabled(self) -> None:
ctrl = _make_controller(daemon_state=True)
assert ctrl.auto_mode is True
class TestSelectDefaultDevice:
"""Tests for _select_default_device."""
def test_no_devices_sets_message(self) -> None:
ctrl = _make_controller(devices=[])
ctrl.pct_var = MagicMock()
ctrl._select_default_device()
ctrl.pct_var.set.assert_called_with("No devices")
def test_prefers_backlight(self) -> None:
devices = [
brightness_controller.Device("led0", "leds", 0, "0%", 3),
brightness_controller.Device("bl", "backlight", 50, "50%", 120000),
]
ctrl = _make_controller(devices=devices)
ctrl._refresh_brightness = MagicMock()
ctrl._select_default_device()
assert ctrl.current_device == "bl"
def test_no_backlight_device(self) -> None:
"""When no backlight device exists, uses the first device."""
devices = [
brightness_controller.Device("led0", "leds", 0, "0%", 3),
brightness_controller.Device("led1", "leds", 0, "0%", 5),
]
ctrl = _make_controller(devices=devices)
ctrl._refresh_brightness = MagicMock()
ctrl._select_default_device()
assert ctrl.current_device == "led0"
class TestOnDeviceChange:
"""Tests for _on_device_change."""
def test_updates_current_device(self) -> None:
ctrl = _make_controller()
ctrl.device_var = MagicMock()
ctrl.device_var.get.return_value = "new_device"
ctrl._refresh_brightness = MagicMock()
ctrl._on_device_change(MagicMock())
assert ctrl.current_device == "new_device"
ctrl._refresh_brightness.assert_called_once()
class TestRefreshBrightness:
"""Tests for _refresh_brightness."""
@patch(
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=75,
)
def test_updates_ui(self, _mock_get: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
ctrl._refresh_brightness()
ctrl.pct_var.set.assert_called_with("75%")
ctrl.slider_var.set.assert_called_with(75)
@patch(
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=-1,
)
def test_error(self, _mock_get: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl._refresh_brightness()
ctrl.pct_var.set.assert_called_with("Error")
def test_no_current_device(self) -> None:
ctrl = _make_controller(devices=[])
ctrl.pct_var = MagicMock()
ctrl._refresh_brightness()
ctrl.pct_var.set.assert_not_called()
class TestOnSliderMove:
"""Tests for _on_slider_move."""
@patch("python_pkg.brightness_controller.brightness_controller._set_brightness")
def test_sets_brightness(self, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl._updating_slider = False
ctrl._on_slider_move("75.0")
mock_set.assert_called_once_with("intel_backlight", 75)
ctrl.pct_var.set.assert_called_with("75%")
def test_skips_during_update(self) -> None:
ctrl = _make_controller()
ctrl._updating_slider = True
ctrl.pct_var = MagicMock()
ctrl._on_slider_move("75.0")
ctrl.pct_var.set.assert_not_called()
def test_no_device(self) -> None:
ctrl = _make_controller(devices=[])
ctrl.pct_var = MagicMock()
ctrl._on_slider_move("75.0")
ctrl.pct_var.set.assert_not_called()
@patch("python_pkg.brightness_controller.brightness_controller._set_brightness")
def test_disables_auto_mode(self, _mock_set: MagicMock) -> None:
ctrl = _make_controller(daemon_state=True)
ctrl.auto_mode = True
ctrl.pct_var = MagicMock()
ctrl._set_auto = MagicMock()
ctrl._updating_slider = False
ctrl._on_slider_move("50.0")
ctrl._set_auto.assert_called_once_with(enabled=False)
class TestSetPct:
"""Tests for _set_pct."""
@patch("python_pkg.brightness_controller.brightness_controller._set_brightness")
@patch(
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=25,
)
def test_sets_brightness(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
ctrl._set_pct(25)
mock_set.assert_called_once_with("intel_backlight", 25)
def test_no_device(self) -> None:
ctrl = _make_controller(devices=[])
# Should not raise
ctrl._set_pct(50)
class TestDecrease:
"""Tests for _decrease."""
@patch("python_pkg.brightness_controller.brightness_controller._set_brightness")
@patch(
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=50,
)
def test_decrease(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
ctrl._decrease()
mock_set.assert_called_once_with("intel_backlight", 45)
@patch("python_pkg.brightness_controller.brightness_controller._set_brightness")
@patch(
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=2,
)
def test_clamps_to_zero(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
ctrl._decrease()
mock_set.assert_called_once_with("intel_backlight", 0)
def test_no_device(self) -> None:
ctrl = _make_controller(devices=[])
ctrl._decrease()
class TestIncrease:
"""Tests for _increase."""
@patch("python_pkg.brightness_controller.brightness_controller._set_brightness")
@patch(
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=50,
)
def test_increase(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
ctrl._increase()
mock_set.assert_called_once_with("intel_backlight", 55)
@patch("python_pkg.brightness_controller.brightness_controller._set_brightness")
@patch(
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=98,
)
def test_clamps_to_100(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
ctrl._increase()
mock_set.assert_called_once_with("intel_backlight", 100)
def test_no_device(self) -> None:
ctrl = _make_controller(devices=[])
ctrl._increase()