personal memory agent
1# SPDX-License-Identifier: AGPL-3.0-only
2# Copyright (c) 2026 sol pbc
3
4from __future__ import annotations
5
6import subprocess
7from pathlib import Path
8from unittest.mock import MagicMock
9
10import pytest
11
12from solstone.think import display_powersave as dp
13from solstone.think.processing import DISPLAY_POWERSAVE_UNAVAILABLE
14
15
16@pytest.fixture(autouse=True)
17def reset_display_monitor():
18 dp.reset_display_powersave_monitor()
19 yield
20 dp.reset_display_powersave_monitor()
21
22
23def _connector(
24 root: Path,
25 name: str,
26 *,
27 status: str | None,
28 enabled: str | None,
29 dpms: str | None,
30) -> Path:
31 path = root / name
32 path.mkdir(parents=True)
33 for attr_name, value in {
34 "status": status,
35 "enabled": enabled,
36 "dpms": dpms,
37 }.items():
38 if value is not None:
39 (path / attr_name).write_text(value, encoding="utf-8")
40 return path
41
42
43def _read_linux(root: Path):
44 return dp.read_display_power(platform="linux", sysfs_root=root)
45
46
47def _reader(state: str, reason: str | None = None):
48 return lambda: dp.DisplayReading(state, reason)
49
50
51def test_read_linux_returns_awake_when_any_considered_connector_is_on(
52 tmp_path: Path,
53) -> None:
54 _connector(
55 tmp_path,
56 "card1-HDMI-A-2",
57 status="connected",
58 enabled="enabled",
59 dpms="On",
60 )
61
62 assert _read_linux(tmp_path) == dp.DisplayReading(dp.DISPLAY_AWAKE, None)
63
64
65@pytest.mark.parametrize("dpms", ["Off", "Standby", "Suspend"])
66def test_read_linux_returns_asleep_when_all_considered_connectors_are_off_states(
67 tmp_path: Path,
68 dpms: str,
69) -> None:
70 _connector(
71 tmp_path,
72 "card1-HDMI-A-2",
73 status="connected",
74 enabled="enabled",
75 dpms=dpms,
76 )
77
78 assert _read_linux(tmp_path) == dp.DisplayReading(dp.DISPLAY_ASLEEP, None)
79
80
81def test_read_linux_ignores_disconnected_and_disabled_stale_dpms(
82 tmp_path: Path,
83) -> None:
84 _connector(
85 tmp_path,
86 "card1-HDMI-A-2",
87 status="disconnected",
88 enabled="enabled",
89 dpms="On",
90 )
91 _connector(
92 tmp_path,
93 "card1-DP-1",
94 status="connected",
95 enabled="disabled",
96 dpms="On",
97 )
98
99 assert _read_linux(tmp_path) == dp.DisplayReading(
100 dp.DISPLAY_UNDETECTABLE,
101 dp.REASON_HEADLESS,
102 )
103
104
105def test_read_linux_mixed_on_and_off_returns_awake(tmp_path: Path) -> None:
106 _connector(
107 tmp_path,
108 "card1-HDMI-A-2",
109 status="connected",
110 enabled="enabled",
111 dpms="Off",
112 )
113 _connector(
114 tmp_path,
115 "card1-DP-1",
116 status="connected",
117 enabled="enabled",
118 dpms="On",
119 )
120
121 assert _read_linux(tmp_path) == dp.DisplayReading(dp.DISPLAY_AWAKE, None)
122
123
124def test_read_linux_returns_unreadable_for_missing_or_unknown_considered_dpms(
125 tmp_path: Path,
126) -> None:
127 _connector(
128 tmp_path,
129 "card1-HDMI-A-2",
130 status="connected",
131 enabled="enabled",
132 dpms="Off",
133 )
134 _connector(
135 tmp_path,
136 "card1-DP-1",
137 status="connected",
138 enabled="enabled",
139 dpms=None,
140 )
141
142 assert _read_linux(tmp_path) == dp.DisplayReading(
143 dp.DISPLAY_UNDETECTABLE,
144 dp.REASON_UNREADABLE,
145 )
146
147
148def test_read_linux_on_wins_over_unreadable_sibling(tmp_path: Path) -> None:
149 _connector(
150 tmp_path,
151 "card1-HDMI-A-2",
152 status="connected",
153 enabled="enabled",
154 dpms=None,
155 )
156 _connector(
157 tmp_path,
158 "card1-DP-1",
159 status="connected",
160 enabled="enabled",
161 dpms="On",
162 )
163
164 assert _read_linux(tmp_path) == dp.DisplayReading(dp.DISPLAY_AWAKE, None)
165
166
167def test_read_linux_returns_headless_when_no_connected_enabled_connectors(
168 tmp_path: Path,
169) -> None:
170 _connector(
171 tmp_path,
172 "card1-HDMI-A-2",
173 status="disconnected",
174 enabled="enabled",
175 dpms="Off",
176 )
177
178 assert _read_linux(tmp_path) == dp.DisplayReading(
179 dp.DISPLAY_UNDETECTABLE,
180 dp.REASON_HEADLESS,
181 )
182
183
184def test_read_linux_returns_unreadable_when_no_connectors(tmp_path: Path) -> None:
185 assert _read_linux(tmp_path) == dp.DisplayReading(
186 dp.DISPLAY_UNDETECTABLE,
187 dp.REASON_UNREADABLE,
188 )
189
190
191def test_read_linux_returns_unreadable_when_root_missing(tmp_path: Path) -> None:
192 assert _read_linux(tmp_path / "missing") == dp.DisplayReading(
193 dp.DISPLAY_UNDETECTABLE,
194 dp.REASON_UNREADABLE,
195 )
196
197
198def test_read_linux_returns_unreadable_when_glob_raises() -> None:
199 class BrokenRoot:
200 def glob(self, _pattern):
201 raise OSError("no drm")
202
203 assert dp.read_display_power(platform="linux", sysfs_root=BrokenRoot()) == (
204 dp.DisplayReading(dp.DISPLAY_UNDETECTABLE, dp.REASON_UNREADABLE)
205 )
206
207
208def test_read_display_power_wraps_detector_exception_as_unreadable(
209 tmp_path: Path,
210 monkeypatch: pytest.MonkeyPatch,
211) -> None:
212 _connector(
213 tmp_path,
214 "card1-HDMI-A-2",
215 status="connected",
216 enabled="enabled",
217 dpms="On",
218 )
219
220 def fail_read_attr(_path: Path, _name: str) -> str:
221 raise RuntimeError("boom")
222
223 monkeypatch.setattr(dp, "_read_attr", fail_read_attr)
224
225 assert _read_linux(tmp_path) == dp.DisplayReading(
226 dp.DISPLAY_UNDETECTABLE,
227 dp.REASON_UNREADABLE,
228 )
229
230
231def test_read_display_power_dispatches_linux_and_darwin(
232 tmp_path: Path,
233 monkeypatch: pytest.MonkeyPatch,
234) -> None:
235 monkeypatch.setattr(
236 dp,
237 "_read_linux",
238 lambda root: dp.DisplayReading(dp.DISPLAY_AWAKE, None),
239 )
240 monkeypatch.setattr(
241 dp,
242 "_read_macos",
243 lambda: dp.DisplayReading(dp.DISPLAY_ASLEEP, None),
244 )
245
246 assert dp.read_display_power(platform="linux", sysfs_root=tmp_path).state == (
247 dp.DISPLAY_AWAKE
248 )
249 assert dp.read_display_power(platform="darwin", sysfs_root=tmp_path).state == (
250 dp.DISPLAY_ASLEEP
251 )
252
253
254def test_read_display_power_returns_unsupported_platform(tmp_path: Path) -> None:
255 assert dp.read_display_power(platform="win32", sysfs_root=tmp_path) == (
256 dp.DisplayReading(dp.DISPLAY_UNDETECTABLE, dp.REASON_UNSUPPORTED_PLATFORM)
257 )
258
259
260def test_parse_macos_log_uses_last_display_marker() -> None:
261 text = "\n".join(
262 [
263 "2026-01-01 Display is turned off",
264 "2026-01-01 Display is turned on",
265 ]
266 )
267
268 assert dp._parse_macos_log(text) == dp.DisplayReading(dp.DISPLAY_AWAKE, None)
269
270
271def test_parse_macos_log_without_marker_returns_no_window_server() -> None:
272 assert dp._parse_macos_log("unrelated line") == dp.DisplayReading(
273 dp.DISPLAY_UNDETECTABLE,
274 dp.REASON_NO_WINDOW_SERVER,
275 )
276
277
278@pytest.mark.parametrize(
279 ("asleep", "expected_state"),
280 [(True, dp.DISPLAY_ASLEEP), (False, dp.DISPLAY_AWAKE)],
281)
282def test_read_macos_uses_coregraphics_sleep_state_when_main_display_exists(
283 monkeypatch: pytest.MonkeyPatch,
284 asleep: bool,
285 expected_state: str,
286) -> None:
287 monkeypatch.setattr(dp, "_macos_main_display_id", lambda: 1)
288 monkeypatch.setattr(dp, "_macos_display_is_asleep", lambda display_id: asleep)
289
290 assert dp._read_macos() == dp.DisplayReading(expected_state, None)
291
292
293def test_read_macos_falls_back_to_pmset_log_when_main_display_is_zero(
294 monkeypatch: pytest.MonkeyPatch,
295) -> None:
296 monkeypatch.setattr(dp, "_macos_main_display_id", lambda: 0)
297 monkeypatch.setattr(dp, "_macos_pmset_log", lambda: "Display is turned off")
298
299 assert dp._read_macos() == dp.DisplayReading(dp.DISPLAY_ASLEEP, None)
300
301
302def test_macos_pmset_log_contains_timeout_and_oserror(
303 monkeypatch: pytest.MonkeyPatch,
304) -> None:
305 def timeout(*_args, **_kwargs):
306 raise subprocess.TimeoutExpired(["pmset", "-g", "log"], timeout=1)
307
308 monkeypatch.setattr(dp.subprocess, "run", timeout)
309 assert dp._macos_pmset_log() == ""
310
311 def oserror(*_args, **_kwargs):
312 raise OSError("pmset missing")
313
314 monkeypatch.setattr(dp.subprocess, "run", oserror)
315 assert dp._macos_pmset_log() == ""
316
317
318def test_monitor_debounces_only_after_threshold_from_injected_now() -> None:
319 monitor = dp.DisplayPowersaveMonitor(debounce_s=120.0)
320
321 first = monitor.poll(now=0.0, read=_reader(dp.DISPLAY_ASLEEP))
322 before_threshold = monitor.poll(now=119.0, read=_reader(dp.DISPLAY_ASLEEP))
323 after_threshold = monitor.poll(now=240.0, read=_reader(dp.DISPLAY_ASLEEP))
324
325 assert first.debounced is False
326 assert before_threshold.debounced is False
327 assert after_threshold.debounced is True
328
329
330def test_monitor_wake_closes_immediately_and_resets_since() -> None:
331 monitor = dp.DisplayPowersaveMonitor(debounce_s=120.0)
332 monitor.poll(now=0.0, read=_reader(dp.DISPLAY_ASLEEP))
333 monitor.poll(now=240.0, read=_reader(dp.DISPLAY_ASLEEP))
334
335 awake = monitor.poll(now=241.0, read=_reader(dp.DISPLAY_AWAKE))
336 next_asleep = monitor.poll(now=242.0, read=_reader(dp.DISPLAY_ASLEEP))
337
338 assert awake.available is True
339 assert awake.asleep is False
340 assert awake.debounced is False
341 assert next_asleep.debounced is False
342
343
344def test_monitor_undetectable_resets_snapshot_and_debounce() -> None:
345 monitor = dp.DisplayPowersaveMonitor(debounce_s=120.0)
346 monitor.poll(now=0.0, read=_reader(dp.DISPLAY_ASLEEP))
347 undetectable = monitor.poll(
348 now=60.0,
349 read=_reader(dp.DISPLAY_UNDETECTABLE, dp.REASON_UNREADABLE),
350 )
351 next_asleep = monitor.poll(now=180.0, read=_reader(dp.DISPLAY_ASLEEP))
352
353 assert undetectable == DISPLAY_POWERSAVE_UNAVAILABLE
354 assert next_asleep.debounced is False
355
356
357def test_monitor_capability_known_is_sticky_until_reset() -> None:
358 monitor = dp.DisplayPowersaveMonitor(debounce_s=120.0)
359
360 assert monitor.capability_known() is False
361 monitor.poll(now=0.0, read=_reader(dp.DISPLAY_AWAKE))
362 assert monitor.capability_known() is True
363 monitor.poll(
364 now=1.0,
365 read=_reader(dp.DISPLAY_UNDETECTABLE, dp.REASON_UNREADABLE),
366 )
367 assert monitor.capability_known() is True
368 monitor.reset()
369 assert monitor.capability_known() is False
370
371
372def test_singleton_last_and_reset_are_non_polling(
373 monkeypatch: pytest.MonkeyPatch,
374) -> None:
375 snapshot = dp._MONITOR.poll(now=0.0, read=_reader(dp.DISPLAY_AWAKE))
376 read = MagicMock(side_effect=AssertionError("last should not poll"))
377 monkeypatch.setattr(dp, "read_display_power", read)
378
379 assert dp.last_display_powersave() == snapshot
380 read.assert_not_called()
381 dp.reset_display_powersave_monitor()
382 assert dp.last_display_powersave() == DISPLAY_POWERSAVE_UNAVAILABLE