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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
#!/usr/bin/env bats
# Unit tests for installer/lib/disk.sh
#
# Coverage scope: pure partition-path helpers (get_efi_partition,
# get_root_partition) plus the partition_disks orchestration shape
# (which globals get populated, which destructive tools get invoked,
# how often). The destructive tools themselves (sgdisk, wipefs,
# partprobe, mkfs.fat) and the interactive select_disks (fzf) are
# validated by VM integration per the project's testing-strategy.org
# policy.
setup() {
# shellcheck disable=SC1091
source "${BATS_TEST_DIRNAME}/../../installer/lib/common.sh"
# shellcheck disable=SC1091
source "${BATS_TEST_DIRNAME}/../../installer/lib/disk.sh"
}
#############################
# get_efi_partition
#############################
@test "get_efi_partition: SATA disk gets numeric suffix 1" {
run get_efi_partition /dev/sda
[ "$status" -eq 0 ]
[ "$output" = "/dev/sda1" ]
}
@test "get_efi_partition: virtio disk gets numeric suffix 1" {
run get_efi_partition /dev/vda
[ "$status" -eq 0 ]
[ "$output" = "/dev/vda1" ]
}
@test "get_efi_partition: NVMe disk gets p1 suffix" {
run get_efi_partition /dev/nvme0n1
[ "$status" -eq 0 ]
[ "$output" = "/dev/nvme0n1p1" ]
}
@test "get_efi_partition: NVMe with multi-namespace disk gets p1 suffix" {
run get_efi_partition /dev/nvme1n2
[ "$status" -eq 0 ]
[ "$output" = "/dev/nvme1n2p1" ]
}
@test "get_efi_partition: empty input documents current behavior" {
# Empty input misses the nvme regex so the bare-suffix branch fires,
# producing just "1". This pins the existing behavior; the function
# is never called with empty in production but pinning catches a
# change in suffix-rule logic.
run get_efi_partition ""
[ "$status" -eq 0 ]
[ "$output" = "1" ]
}
#############################
# get_root_partition
#############################
@test "get_root_partition: SATA disk gets numeric suffix 2" {
run get_root_partition /dev/sda
[ "$status" -eq 0 ]
[ "$output" = "/dev/sda2" ]
}
@test "get_root_partition: NVMe disk gets p2 suffix" {
run get_root_partition /dev/nvme0n1
[ "$status" -eq 0 ]
[ "$output" = "/dev/nvme0n1p2" ]
}
@test "get_root_partition: virtio disk gets numeric suffix 2" {
run get_root_partition /dev/vdb
[ "$status" -eq 0 ]
[ "$output" = "/dev/vdb2" ]
}
#############################
# partition_disks (orchestration)
#############################
# partition_disks reads SELECTED_DISKS, dispatches the per-disk
# layout (sgdisk + wipefs + mkfs.fat) on FILESYSTEM, and populates
# EFI_PARTS + ROOT_PARTS for downstream callers (create_zfs_pool,
# btrfs_open_encryption, sync_efi_partitions, fstab generation).
#
# The destructive system tools (sgdisk, wipefs, partprobe, mkfs.fat)
# are mocked to capture invocation shape only — actual partition
# behavior is validated by VM integration per testing-strategy.org.
partition_disks_setup() {
SELECTED_DISKS=()
EFI_PARTS=()
ROOT_PARTS=()
CALLS=()
sgdisk() { CALLS+=("sgdisk $*"); return 0; }
wipefs() { CALLS+=("wipefs $*"); return 0; }
partprobe() { CALLS+=("partprobe $*"); return 0; }
mkfs.fat() { CALLS+=("mkfs.fat $*"); return 0; }
sleep() { :; }
info() { :; }
step() { :; }
error() { CALLS+=("error $*"); return 1; }
}
@test "partition_disks: populates EFI_PARTS and ROOT_PARTS for single SATA disk" {
partition_disks_setup
SELECTED_DISKS=(/dev/sda)
FILESYSTEM=zfs
partition_disks
[ "${#EFI_PARTS[@]}" -eq 1 ]
[ "${#ROOT_PARTS[@]}" -eq 1 ]
[ "${EFI_PARTS[0]}" = "/dev/sda1" ]
[ "${ROOT_PARTS[0]}" = "/dev/sda2" ]
}
@test "partition_disks: NVMe disk gets p1/p2 suffixes in globals" {
partition_disks_setup
SELECTED_DISKS=(/dev/nvme0n1)
FILESYSTEM=zfs
partition_disks
[ "${EFI_PARTS[0]}" = "/dev/nvme0n1p1" ]
[ "${ROOT_PARTS[0]}" = "/dev/nvme0n1p2" ]
}
@test "partition_disks: multi-disk SATA populates both arrays in order" {
partition_disks_setup
SELECTED_DISKS=(/dev/sda /dev/sdb /dev/sdc)
FILESYSTEM=zfs
partition_disks
[ "${#EFI_PARTS[@]}" -eq 3 ]
[ "${#ROOT_PARTS[@]}" -eq 3 ]
[ "${EFI_PARTS[0]}" = "/dev/sda1" ]
[ "${EFI_PARTS[1]}" = "/dev/sdb1" ]
[ "${EFI_PARTS[2]}" = "/dev/sdc1" ]
[ "${ROOT_PARTS[0]}" = "/dev/sda2" ]
[ "${ROOT_PARTS[1]}" = "/dev/sdb2" ]
[ "${ROOT_PARTS[2]}" = "/dev/sdc2" ]
}
@test "partition_disks: mixed SATA + NVMe applies correct suffix per disk" {
partition_disks_setup
SELECTED_DISKS=(/dev/sda /dev/nvme0n1)
FILESYSTEM=zfs
partition_disks
[ "${EFI_PARTS[0]}" = "/dev/sda1" ]
[ "${EFI_PARTS[1]}" = "/dev/nvme0n1p1" ]
[ "${ROOT_PARTS[0]}" = "/dev/sda2" ]
[ "${ROOT_PARTS[1]}" = "/dev/nvme0n1p2" ]
}
@test "partition_disks: ZFS dispatch passes BF00 root type to sgdisk" {
partition_disks_setup
SELECTED_DISKS=(/dev/sda)
FILESYSTEM=zfs
partition_disks
[[ " ${CALLS[*]} " == *"sgdisk -n 2:0:0 -t 2:BF00"* ]]
}
@test "partition_disks: Btrfs dispatch passes 8300 root type to sgdisk" {
partition_disks_setup
SELECTED_DISKS=(/dev/sda)
FILESYSTEM=btrfs
partition_disks
[[ " ${CALLS[*]} " == *"sgdisk -n 2:0:0 -t 2:8300"* ]]
}
@test "partition_disks: invokes wipefs -af on each disk" {
partition_disks_setup
SELECTED_DISKS=(/dev/sda /dev/sdb)
FILESYSTEM=zfs
partition_disks
local count
count=$(printf '%s\n' "${CALLS[@]}" | grep -c '^wipefs -af ')
[ "$count" -eq 2 ]
[[ " ${CALLS[*]} " == *"wipefs -af /dev/sda"* ]]
[[ " ${CALLS[*]} " == *"wipefs -af /dev/sdb"* ]]
}
@test "partition_disks: invokes mkfs.fat once per EFI partition with EFI<i> label" {
partition_disks_setup
SELECTED_DISKS=(/dev/sda /dev/sdb)
FILESYSTEM=zfs
partition_disks
local count
count=$(printf '%s\n' "${CALLS[@]}" | grep -c '^mkfs.fat ')
[ "$count" -eq 2 ]
[[ " ${CALLS[*]} " == *"mkfs.fat -F32 -n EFI0 /dev/sda1"* ]]
[[ " ${CALLS[*]} " == *"mkfs.fat -F32 -n EFI1 /dev/sdb1"* ]]
}
@test "partition_disks: re-initializes globals on second call" {
partition_disks_setup
SELECTED_DISKS=(/dev/sda /dev/sdb)
FILESYSTEM=zfs
partition_disks
[ "${#EFI_PARTS[@]}" -eq 2 ]
SELECTED_DISKS=(/dev/sdc)
partition_disks
[ "${#EFI_PARTS[@]}" -eq 1 ]
[ "${#ROOT_PARTS[@]}" -eq 1 ]
[ "${EFI_PARTS[0]}" = "/dev/sdc1" ]
}
@test "partition_disks: empty SELECTED_DISKS calls error and skips work" {
partition_disks_setup
SELECTED_DISKS=()
FILESYSTEM=zfs
partition_disks || true
[[ " ${CALLS[*]} " == *"error partition_disks: SELECTED_DISKS is empty"* ]]
[[ " ${CALLS[*]} " != *"sgdisk"* ]]
[[ " ${CALLS[*]} " != *"wipefs"* ]]
}
#############################
# disk_meets_min_size / MIN_DISK_BYTES
#############################
@test "MIN_DISK_BYTES is 20 GB (decimal)" {
[ "$MIN_DISK_BYTES" -eq 20000000000 ]
}
@test "disk_meets_min_size: exactly the minimum passes" {
run disk_meets_min_size 20000000000
[ "$status" -eq 0 ]
}
@test "disk_meets_min_size: one byte under the minimum fails" {
run disk_meets_min_size 19999999999
[ "$status" -eq 1 ]
}
@test "disk_meets_min_size: a 20 GiB disk image clears the 20 GB floor" {
run disk_meets_min_size 21474836480
[ "$status" -eq 0 ]
}
@test "disk_meets_min_size: a large disk passes" {
run disk_meets_min_size 500107862016
[ "$status" -eq 0 ]
}
@test "disk_meets_min_size: zero fails" {
run disk_meets_min_size 0
[ "$status" -eq 1 ]
}
@test "disk_meets_min_size: non-numeric input fails" {
run disk_meets_min_size notanumber
[ "$status" -eq 1 ]
}
@test "disk_meets_min_size: empty input fails" {
run disk_meets_min_size ""
[ "$status" -eq 1 ]
}
# disk_in_use
#
# The safety gate validate_install_targets leans on to refuse wiping a disk
# that's already in use. Each branch (mount, swap, imported zpool, idle) is
# driven by mocking the system-boundary commands it queries — lsblk, swapon,
# command_exists, zpool. The /proc/mdstat branch's positive case can't be
# unit-driven without writing to /proc, so it stays VM-harness territory; the
# idle case below pins the negative for a name guaranteed absent from mdstat.
# This is the error path for the "installing onto an already-mounted disk"
# case: in_use returns 0, and the orchestrator turns that into a hard fail.
@test "disk_in_use: returns 0 when a mountpoint is present" {
lsblk() { echo "/boot"; }
run disk_in_use /dev/sda
[ "$status" -eq 0 ]
}
@test "disk_in_use: returns 0 when active swap sits on the disk" {
lsblk() { :; }
swapon() { echo "/dev/sda2"; }
run disk_in_use /dev/sda
[ "$status" -eq 0 ]
}
@test "disk_in_use: returns 0 when the disk is a member of an imported zpool" {
lsblk() { :; }
swapon() { :; }
command_exists() { return 0; }
# -P prints full paths; a partition member (/dev/sda2) must still match
# the bare disk path via fixed-string search.
zpool() { echo " /dev/sda2 ONLINE 0 0 0"; }
run disk_in_use /dev/sda
[ "$status" -eq 0 ]
}
@test "disk_in_use: returns 1 when the disk is idle" {
lsblk() { :; }
swapon() { :; }
command_exists() { return 1; }
# A name that won't appear in the host's /proc/mdstat keeps the final
# branch deterministic.
run disk_in_use /dev/zzz999
[ "$status" -eq 1 ]
}
|