aboutsummaryrefslogtreecommitdiff
path: root/tests/installer-steps/test_install_cpu_microcode.py
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-08-08 04:55:52 -0500
committerCraig Jennings <c@cjennings.net>2026-08-09 11:47:36 -0500
commitfca837883b0b79793ea81d223d6e08a8ee2b2248 (patch)
tree2c9deff0b93ad0b42842fbe7ba30a4f28732a894 /tests/installer-steps/test_install_cpu_microcode.py
parentfc3a8508561114177a67bbdb6802bdcf1df7683e (diff)
downloadarchsetup-fca837883b0b79793ea81d223d6e08a8ee2b2248.tar.gz
archsetup-fca837883b0b79793ea81d223d6e08a8ee2b2248.zip
feat: automate microcode, TLP radio enable, and ZFS tmp.mount mask
The 2026-04 velox setup left three manual fixes behind. All three now happen at install time. - The TLP config sets DEVICES_TO_ENABLE_ON_STARTUP="bluetooth wifi". systemd-rfkill is masked on laptops because it fights TLP, so TLP is the only thing left that can restore radio state. Without this a fresh install can boot with both radios soft-blocked. - mask_tmp_mount_for_zfs masks systemd's tmp.mount when the pool carries a dataset mounted at /tmp, so tmpfs can't shadow the dataset and break systemd-tmpfiles. - install_cpu_microcode installs intel-ucode or amd-ucode by vendor_id, first in boot_ux so grub-mkconfig and mkinitcpio's microcode hook both see the ucode image. New tests cover each function across normal, boundary, and error cases. The boot_ux and snapshot-dispatch sequence pins include the new calls.
Diffstat (limited to 'tests/installer-steps/test_install_cpu_microcode.py')
-rw-r--r--tests/installer-steps/test_install_cpu_microcode.py110
1 files changed, 110 insertions, 0 deletions
diff --git a/tests/installer-steps/test_install_cpu_microcode.py b/tests/installer-steps/test_install_cpu_microcode.py
new file mode 100644
index 0000000..20f1820
--- /dev/null
+++ b/tests/installer-steps/test_install_cpu_microcode.py
@@ -0,0 +1,110 @@
+"""Test install_cpu_microcode — vendor-detected microcode package install.
+
+archsetup never installed intel-ucode/amd-ucode (found on velox 2026-04-10:
+CPU running old microcode, hand-fixed). The step reads vendor_id from
+/proc/cpuinfo and installs the matching package. It must run before
+configure_grub in boot_ux: grub-mkconfig detects /boot/<vendor>-ucode.img for
+its initrd lines, and mkinitcpio's microcode hook embeds it, so the package
+has to exist before either generates.
+
+Method: sed-extract install_cpu_microcode from the real `archsetup`, point it
+at a fixture cpuinfo, fake pacman_install / display / error_warn.
+
+Run from repo root:
+ python3 -m unittest tests.installer-steps.test_install_cpu_microcode
+"""
+
+import os
+import re
+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")
+
+INTEL = "vendor_id\t: GenuineIntel\n"
+AMD = "vendor_id\t: AuthenticAMD\n"
+
+
+def run(cpuinfo_body, missing=False):
+ with tempfile.TemporaryDirectory() as d:
+ cpuinfo = os.path.join(d, "cpuinfo")
+ if not missing:
+ with open(cpuinfo, "w") as f:
+ f.write(cpuinfo_body)
+ script = textwrap.dedent(f"""\
+ logfile=/dev/null
+ action=""
+ display() {{ :; }}
+ pacman_install() {{ echo "INSTALL: $1"; }}
+ error_warn() {{ echo "WARN: $1"; return 1; }}
+ source <(sed -n '/^install_cpu_microcode() {{/,/^}}/p' "{ARCHSETUP}")
+ install_cpu_microcode "{cpuinfo}"
+ echo "RC=$?"
+ exit 0
+ """)
+ return subprocess.run(
+ ["bash", "-c", script], capture_output=True, text=True, timeout=10,
+ )
+
+
+def rc_of(r):
+ m = re.search(r"^RC=(\d+)$", r.stdout, re.M)
+ assert m, "no RC line in output: %r / %r" % (r.stdout, r.stderr)
+ return int(m.group(1))
+
+
+class InstallCpuMicrocode(unittest.TestCase):
+ # ------------------------------------------------------------ normal ----
+ def test_intel_vendor_installs_intel_ucode(self):
+ r = run(f"processor\t: 0\n{INTEL}model name\t: whatever\n")
+ self.assertIn("INSTALL: intel-ucode", r.stdout)
+ self.assertNotIn("amd-ucode", r.stdout)
+ self.assertEqual(rc_of(r), 0)
+
+ def test_amd_vendor_installs_amd_ucode(self):
+ r = run(f"processor\t: 0\n{AMD}")
+ self.assertIn("INSTALL: amd-ucode", r.stdout)
+ self.assertNotIn("intel-ucode", r.stdout)
+ self.assertEqual(rc_of(r), 0)
+
+ # ---------------------------------------------------------- boundary ----
+ def test_multicore_cpuinfo_installs_exactly_once(self):
+ # /proc/cpuinfo repeats vendor_id per logical CPU.
+ body = "".join(f"processor\t: {i}\n{AMD}\n" for i in range(16))
+ r = run(body)
+ self.assertEqual(r.stdout.count("INSTALL:"), 1,
+ "one package install, not one per core")
+
+ def test_space_separated_vendor_line_parses(self):
+ # Some kernels/arches pad with spaces rather than a tab.
+ r = run("vendor_id : GenuineIntel\n")
+ self.assertIn("INSTALL: intel-ucode", r.stdout)
+
+ def test_vendor_id_substring_elsewhere_does_not_confuse(self):
+ # A flags line or model name mentioning a vendor string must not win
+ # over the real vendor_id line.
+ body = "model name\t: AuthenticAMD emulator\n" + INTEL
+ r = run(body)
+ self.assertIn("INSTALL: intel-ucode", r.stdout)
+ self.assertNotIn("amd-ucode", r.stdout)
+
+ # ------------------------------------------------------------- error ----
+ def test_unknown_vendor_warns_and_installs_nothing(self):
+ r = run("vendor_id\t: CentaurHauls\n")
+ self.assertNotIn("INSTALL:", r.stdout)
+ self.assertIn("WARN:", r.stdout)
+ self.assertEqual(rc_of(r), 1)
+
+ def test_missing_cpuinfo_warns_and_installs_nothing(self):
+ r = run("", missing=True)
+ self.assertNotIn("INSTALL:", r.stdout)
+ self.assertIn("WARN:", r.stdout)
+ self.assertEqual(rc_of(r), 1)
+ self.assertEqual(r.returncode, 0)
+
+
+if __name__ == "__main__":
+ unittest.main()