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
|
"""Test the nvme early-module step.
Two bugs the extracted ensure_nvme_early_module fixes:
1. The MODULES=(... nvme) edit was only compiled into the initramfs by a
mkinitcpio -P that ran behind `if ! is_zfs_root`, so on ZFS-root machines
the early-load hardening never landed. The step now rebuilds whenever it
changed the file, unconditionally.
2. The already-present check grepped the whole file for "nvme", so a comment
or an unrelated token (nvme_tcp) anywhere in mkinitcpio.conf skipped the
edit. The check now looks for the nvme word on the MODULES line only.
Method: sed-extract ensure_nvme_early_module from the real `archsetup`, point
it at a temp mkinitcpio.conf, and fake has_nvme_drives/backup_system_file/
mkinitcpio/display/error_warn.
Run from repo root:
python3 -m unittest tests.installer-steps.test_ensure_nvme_early_module
"""
import os
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(conf_body, has_nvme=True):
with tempfile.TemporaryDirectory() as d:
conf = os.path.join(d, "mkinitcpio.conf")
with open(conf, "w") as f:
f.write(conf_body)
script = textwrap.dedent(f"""\
logfile=/dev/null
action=""
display() {{ :; }}
backup_system_file() {{ :; }}
error_warn() {{ echo "WARN: $1"; return 1; }}
has_nvme_drives() {{ return {0 if has_nvme else 1}; }}
# stdout is redirected into $logfile at the call site, so the fake
# records its invocation in a side file instead.
mkinitcpio() {{ echo "MKINITCPIO $*" >> "{d}/mk.log"; }}
source <(sed -n '/^ensure_nvme_early_module() {{/,/^}}/p' "{ARCHSETUP}")
ensure_nvme_early_module "{conf}"
echo "RC=$?"
echo "CONF:[$(cat "{conf}")]"
[ -f "{d}/mk.log" ] && cat "{d}/mk.log"
""")
return subprocess.run(
["bash", "-c", script], capture_output=True, text=True, timeout=10,
)
class EnsureNvmeEarlyModule(unittest.TestCase):
def test_empty_modules_gets_nvme_and_rebuilds(self):
r = run("MODULES=()\nHOOKS=(base udev)\n")
self.assertIn("MODULES=(nvme)", r.stdout)
self.assertIn("MKINITCPIO -P", r.stdout,
"the initramfs must rebuild after the MODULES edit")
def test_populated_modules_appends_nvme_and_rebuilds(self):
r = run("MODULES=(btrfs)\n")
self.assertIn("MODULES=(btrfs nvme)", r.stdout)
self.assertIn("MKINITCPIO -P", r.stdout)
def test_nvme_already_present_no_edit_no_rebuild(self):
r = run("MODULES=(nvme)\n")
self.assertIn("MODULES=(nvme)", r.stdout)
self.assertNotIn("MODULES=(nvme nvme)", r.stdout)
self.assertNotIn("MKINITCPIO", r.stdout,
"an unchanged conf must not trigger a rebuild (resume idempotence)")
def test_nvme_mention_elsewhere_does_not_skip_the_edit(self):
# The old whole-file grep skipped the edit when any line mentioned
# nvme; a comment must not mask a missing MODULES entry.
r = run("# early nvme notes: nvme_load=YES\nMODULES=(btrfs)\n")
self.assertIn("MODULES=(btrfs nvme)", r.stdout)
self.assertIn("MKINITCPIO -P", r.stdout)
def test_nvme_tcp_module_does_not_count_as_nvme(self):
r = run("MODULES=(nvme_tcp)\n")
self.assertIn("MODULES=(nvme_tcp nvme)", r.stdout)
def test_no_nvme_drives_is_a_noop(self):
r = run("MODULES=()\n", has_nvme=False)
self.assertIn("RC=0", r.stdout)
self.assertIn("CONF:[MODULES=()", r.stdout)
self.assertNotIn("MKINITCPIO", r.stdout)
if __name__ == "__main__":
unittest.main()
|