1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
|
"""Test configure_tlp_power's radio-enable line, daemon masking, and laptop gating.
systemd-rfkill is masked on laptops because it fights TLP's radio handling —
which means nothing restores radio state at boot unless TLP is told to. The
velox 2026-04-10 setup found wifi and bluetooth soft-blocked on first boot for
exactly this reason. The conf written here must carry
DEVICES_TO_ENABLE_ON_STARTUP so a fresh install comes up with radios on.
power-profiles-daemon is masked and stopped on laptops for the same class of
reason. power-profiles-daemon.service declares "Conflicts=tuned.service
tlp.service auto-cpufreq.service ..." — the line is in ppd's unit, not tlp's —
so systemd TERMs TLP the instant ppd starts. Leaving ppd merely disabled does
not prevent that: ppd ships D-Bus activation files, and the desktop-settings
panel's own powerprofilesctl call activates it on demand. Velox ran that way
from its 2026-08-13 rebuild until 2026-08-16, with TLP failing at every boot and
none of its battery policy applied, while the machine looked correctly
configured. Masking blocks D-Bus activation too, which both keeps TLP alive and
makes the panel's power control read as unavailable, the behavior the
package-install site in `archsetup` already documents as intended. The stop is
what makes a repair re-run take effect on a booted machine, where a mask alone
would leave a running ppd running.
The bluetooth resume hook installed here must land in
/usr/lib/systemd/system-sleep/. That is the only directory systemd-sleep scans
(systemd 262's binary and its man page both name just the one), so a hook under
/etc/systemd/system-sleep/ is silently dead. Velox ran about fifteen hibernate
cycles between 2026-08-21 and 2026-10-05 with the hook installed there and it
never fired once; bluetooth came back rfkilled every time, and TLP's own hook
(which lives in /usr/lib and does run) carried the block across every later
cycle.
Method: sed-extract configure_tlp_power from the real `archsetup`, point it at
a temp tlp.d dir and a temp power-supply dir, and fake pacman_install /
run_task / display / error_warn / systemctl.
Run from repo root:
python3 -m unittest tests.installer-steps.test_configure_tlp_power
"""
import os
import stat
import subprocess
import tempfile
import textwrap
import unittest
REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
ARCHSETUP = os.path.join(REPO_ROOT, "archsetup")
def run(battery=True, bat_name="BAT0", unwritable_tlpd=False, systemctl_fails=False,
install_fails=False):
with tempfile.TemporaryDirectory() as d:
psdir = os.path.join(d, "power_supply")
os.makedirs(psdir)
if battery:
open(os.path.join(psdir, bat_name), "w").close()
tlpd = os.path.join(d, "tlp.d")
if unwritable_tlpd:
os.makedirs(tlpd)
os.chmod(tlpd, stat.S_IRUSR | stat.S_IXUSR)
# The real mask call redirects stdout into $logfile, so the fake
# systemctl records to a side file the test reads back instead.
sysrc = 1 if systemctl_fails else 0
# The real install_executable warns on its own; the fake only reports
# the return code so the test can see whether the step keeps going.
instrc = 1 if install_fails else 0
script = textwrap.dedent(f"""\
logfile=/dev/null
action=""
display() {{ :; }}
pacman_install() {{ echo "INSTALL: $1"; }}
run_task() {{ echo "TASK: $1"; }}
systemctl() {{ echo "SYSTEMCTL: $*" >> "{d}/systemctl.log"; return {sysrc}; }}
error_warn() {{ echo "WARN: $1"; return 1; }}
user_archsetup_dir=/src
install_executable() {{ echo "INSTALL_EXEC: $1 -> $2"; return {instrc}; }}
source <(sed -n '/^configure_tlp_power() {{/,/^}}/p' "{ARCHSETUP}")
configure_tlp_power "{tlpd}" "{psdir}"
echo "RC=$?"
echo "CONF:[$(cat "{tlpd}/01-custom.conf" 2>/dev/null)]"
[ -f "{d}/systemctl.log" ] && cat "{d}/systemctl.log"
exit 0
""")
r = subprocess.run(
["bash", "-c", script], capture_output=True, text=True, timeout=10,
)
if unwritable_tlpd:
os.chmod(tlpd, stat.S_IRWXU)
return r
class ConfigureTlpPower(unittest.TestCase):
# ------------------------------------------------------------ normal ----
def test_laptop_gets_tlp_with_radio_enable_line(self):
r = run(battery=True)
self.assertIn("INSTALL: tlp", r.stdout)
conf = r.stdout.split("CONF:[")[1]
self.assertIn('DEVICES_TO_ENABLE_ON_STARTUP="bluetooth wifi"', conf,
"radios must be re-enabled at boot: systemd-rfkill is "
"masked, so TLP is the only thing that can do it")
self.assertIn("CPU_ENERGY_PERF_POLICY_ON_AC", conf)
self.assertIn("SYSTEMCTL: mask systemd-rfkill.service systemd-rfkill.socket",
r.stdout)
self.assertIn("TASK: enabling TLP service", r.stdout)
def test_laptop_masks_power_profiles_daemon(self):
r = run(battery=True)
self.assertIn("SYSTEMCTL: mask power-profiles-daemon.service", r.stdout,
"ppd's unit declares Conflicts=...tlp.service..., so ppd "
"must be masked or it TERMs TLP whenever it is activated")
def test_laptop_stops_running_power_profiles_daemon(self):
"""Masking alone leaves an already-running ppd running.
The installer runs on a booted system, so a repair re-run would
otherwise mask ppd, leave it live, and let it keep TLP dead until the
next reboot with nothing reporting it.
"""
r = run(battery=True)
self.assertIn("SYSTEMCTL: stop power-profiles-daemon.service", r.stdout)
def test_ppd_is_masked_before_it_is_stopped(self):
"""Order matters: stopping first leaves a window to re-activate in."""
calls = [line for line in run(battery=True).stdout.splitlines()
if line.startswith("SYSTEMCTL:") and "power-profiles-daemon" in line]
verbs = [line.split()[1] for line in calls]
self.assertEqual(verbs, ["mask", "stop"])
def test_power_profiles_daemon_is_masked_not_merely_disabled(self):
"""Disabling ppd is not enough — D-Bus activation ignores it.
This is the whole point of the mask, so assert the verb directly. A
`disable` here would pass a naive "ppd is handled" check while leaving
the panel's powerprofilesctl call free to start ppd and kill TLP.
"""
r = run(battery=True)
ppd_calls = [line for line in r.stdout.splitlines()
if line.startswith("SYSTEMCTL:") and "power-profiles-daemon" in line]
self.assertTrue(ppd_calls, "configure_tlp_power must act on ppd at all")
for line in ppd_calls:
self.assertNotIn(" disable ", line,
"disable leaves D-Bus activation live; only mask blocks it")
def test_radio_line_is_active_not_commented(self):
r = run(battery=True)
conf = r.stdout.split("CONF:[")[1].split("]")[0]
for line in conf.splitlines():
if "DEVICES_TO_ENABLE_ON_STARTUP" in line:
self.assertFalse(line.lstrip().startswith("#"),
"the radio-enable line must not be commented out")
break
else:
self.fail("DEVICES_TO_ENABLE_ON_STARTUP line missing from conf")
def test_laptop_installs_the_resume_hook_where_systemd_sleep_scans(self):
"""The hook goes to /usr/lib/systemd/system-sleep/, the only scanned dir.
Assert the full install list, not a substring: a second copy under
/etc/ would pass an assertIn and still be the dead hook this test
exists to catch.
"""
r = run(battery=True)
installs = [line for line in r.stdout.splitlines()
if line.startswith("INSTALL_EXEC:")]
self.assertEqual(
installs,
["INSTALL_EXEC: /src/scripts/zz-bluetooth-resume -> "
"/usr/lib/systemd/system-sleep/zz-bluetooth-resume"],
"systemd-sleep never scans /etc/systemd/system-sleep/; a hook "
"there installs cleanly and never runs")
# ---------------------------------------------------------- boundary ----
def test_desktop_installs_no_resume_hook(self):
"""No TLP on a desktop means no masked rfkill and no gap to fill."""
r = run(battery=False)
self.assertNotIn("INSTALL_EXEC:", r.stdout)
def test_desktop_without_battery_is_a_no_op(self):
r = run(battery=False)
self.assertNotIn("INSTALL:", r.stdout)
self.assertNotIn("SYSTEMCTL:", r.stdout)
self.assertIn("CONF:[]", r.stdout, "no conf may be written on a desktop")
def test_second_battery_index_still_counts_as_laptop(self):
r = run(battery=True, bat_name="BAT1")
self.assertIn("INSTALL: tlp", r.stdout)
self.assertIn('DEVICES_TO_ENABLE_ON_STARTUP', r.stdout)
def test_desktop_keeps_power_profiles_daemon(self):
"""A batteryless machine must NOT get ppd masked.
There is no TLP on a desktop to conflict with it, and the package-install
site enables ppd precisely so the settings panel's three-way power
control works there. Masking it here would break that control for no gain.
"""
r = run(battery=False)
self.assertNotIn("power-profiles-daemon", r.stdout)
# ------------------------------------------------------------- error ----
def test_failed_ppd_mask_warns_and_does_not_crash(self):
"""A masking failure must surface, not pass silently.
Silence is the exact failure mode being fixed: velox looked configured
while TLP was dead. If the mask cannot be applied, say so.
Assert on the harness's own RC= line, not on r.returncode. The harness
script ends in a literal `exit 0`, so r.returncode is 0 no matter what
configure_tlp_power does — asserting it can never fail, which would make
this test the same silent no-op it exists to catch.
"""
r = run(battery=True, systemctl_fails=True)
self.assertIn("WARN: masking power-profiles-daemon for TLP", r.stdout)
self.assertIn("WARN: stopping power-profiles-daemon for TLP", r.stdout)
self.assertIn("RC=", r.stdout,
"the function must return so the install continues, "
"not exit and take the script down with it")
def test_failed_hook_install_does_not_stop_the_ppd_mask(self):
"""install_executable warns for itself, so the step must carry on.
The mask that follows is what keeps TLP alive; a hook copy that fails
(source missing on a partial checkout) must not take it with it.
"""
r = run(battery=True, install_fails=True)
self.assertIn("INSTALL_EXEC:", r.stdout)
self.assertIn("SYSTEMCTL: mask power-profiles-daemon.service", r.stdout)
self.assertIn("RC=", r.stdout)
@unittest.skipUnless(os.geteuid() != 0, "root ignores directory write bits")
def test_unwritable_tlpd_warns_and_does_not_crash(self):
r = run(battery=True, unwritable_tlpd=True)
self.assertIn("WARN:", r.stdout,
"a failed conf write must surface through error_warn")
self.assertEqual(r.returncode, 0)
if __name__ == "__main__":
unittest.main()
|