aboutsummaryrefslogtreecommitdiff
path: root/tests/installer-steps/test_grub_cmdline.py
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-24 23:16:56 -0500
committerCraig Jennings <c@cjennings.net>2026-07-24 23:16:56 -0500
commita9391c90f07a564693bb1994f4f8fe6470efb3a2 (patch)
tree73ac6d588491314615ef50dbffc8fdd07842019d /tests/installer-steps/test_grub_cmdline.py
parent082d870d6c4c3a3cce85506505582b17ef19487b (diff)
downloadarchsetup-a9391c90f07a564693bb1994f4f8fe6470efb3a2.tar.gz
archsetup-a9391c90f07a564693bb1994f4f8fe6470efb3a2.zip
fix(installer): harden the lock path against the AMD-iGPU DPMS lockout
An idle lock on this Strix Halo box wedged the whole session: hyprlock died and the compositor stayed locked with no prompt, recoverable only from a console. It's a documented AMD-integrated-Radeon failure (hyprlock#953, Hyprland#5822) -- a display power cycle via DPMS invalidates the GPU resources the lock client holds, so hyprlock loses its surface and exits without unlocking. No coredump, no OOM; the GPU pulls the rug out. Two installer changes, both scoped and tested: update_grub_cmdline adds amdgpu.runpm=0 on AMD machines only. Disabling GPU runtime power management keeps those resources valid across a display cycle -- the root fix. A no-op on Intel/NVIDIA, and it rides the existing merge so no boot-critical token is touched. configure_hyprlock_pam writes a complete PAM stack. The hyprlock package ships only `auth include login`, leaving account and session uninitialised so pam_end() crashes on cleanup -- a separate documented lockout cause. All three phases now resolve through login, inheriting the keyring the graphical login uses. CALL_SITES pins both new wirings. 372 unit tests, exit 0; each addition proven by reverting it.
Diffstat (limited to 'tests/installer-steps/test_grub_cmdline.py')
-rw-r--r--tests/installer-steps/test_grub_cmdline.py42
1 files changed, 42 insertions, 0 deletions
diff --git a/tests/installer-steps/test_grub_cmdline.py b/tests/installer-steps/test_grub_cmdline.py
index 8c0b5b0..4c2d202 100644
--- a/tests/installer-steps/test_grub_cmdline.py
+++ b/tests/installer-steps/test_grub_cmdline.py
@@ -67,6 +67,48 @@ class MergeGrubCmdline(unittest.TestCase):
self.assertIn("zfs=zroot/ROOT/default", out)
+def run_with_gpu(body, gpu):
+ # detect_gpu_vendors is stubbed to a chosen vendor list so the AMD-only
+ # amdgpu.runpm=0 addition can be exercised without real /sys reads.
+ stub = 'detect_gpu_vendors() { printf "%s\\n" %s; }\n' % ("%s", "'" + gpu + "'")
+ stub = 'detect_gpu_vendors() { printf "%s" "$GPU"; [ -n "$GPU" ] && echo; }\n'
+ script = ('logfile=/dev/null\nerror_warn() { echo "WARN: $1"; return 1; }\n'
+ + stub + EXTRACT + "\n" + body + "\n")
+ return subprocess.run(["bash", "-c", script], capture_output=True, text=True,
+ timeout=10, env={**os.environ, "GPU": gpu})
+
+
+class AmdRunpmCmdline(unittest.TestCase):
+ """amdgpu.runpm=0 is added on AMD machines only: the AMD iGPU invalidates
+ hyprlock's GPU resources on a display power cycle, wedging the lock
+ (hyprlock#953). Disabling GPU runtime PM keeps them valid. A no-op on Intel
+ or NVIDIA."""
+
+ def _file(self, gpu):
+ import tempfile
+ fd, path = tempfile.mkstemp(prefix="grub-runpm-")
+ os.close(fd)
+ with open(path, "w") as f:
+ f.write('GRUB_CMDLINE_LINUX_DEFAULT="rw quiet"\n')
+ self.addCleanup(os.remove, path)
+ run_with_gpu(f'update_grub_cmdline "{path}"', gpu)
+ with open(path) as f:
+ return f.read()
+
+ def test_amd_gets_runpm_disabled(self):
+ self.assertIn("amdgpu.runpm=0", self._file("amd"))
+
+ def test_intel_does_not(self):
+ self.assertNotIn("amdgpu.runpm", self._file("intel"))
+
+ def test_no_gpu_detected_does_not(self):
+ self.assertNotIn("amdgpu.runpm", self._file(""))
+
+ def test_amd_with_nvidia_still_gets_it(self):
+ # A hybrid box with an AMD part present still wants the fix.
+ self.assertIn("amdgpu.runpm=0", self._file("amd\nnvidia"))
+
+
class UpdateGrubCmdline(unittest.TestCase):
def run_update(self, grub_body):
with tempfile.TemporaryDirectory() as d: