Skip to content

Commit 1d5321d

Browse files
committed
test: add integration test suites for upgrade and rebuild flows
1 parent 3fbe591 commit 1d5321d

4 files changed

Lines changed: 733 additions & 0 deletions

File tree

‎Makefile‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ UNIT_TESTS = \
3838
tests/test_uki.sh \
3939
tests/test_home.sh \
4040
tests/test_upgrade.sh \
41+
tests/test_upgrade_flow.sh \
4142
tests/test_rebuild_uki.sh \
43+
tests/test_rebuild_uki_flow.sh \
4244
tests/test_atomic_gc.sh
4345

4446
test:

‎tests/test_atomic_gc.sh‎

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,31 @@ assert_eq "gc with text count → rc 1" "1" "$_rc"
183183
_run_gc
184184
assert_eq "gc default count → rc 0" "0" "$_rc"
185185

186+
# ── rm command: generation not found ─────────────────────
187+
188+
section "rm command: generation not found"
189+
190+
_run_gc rm --yes "20250101-000000"
191+
assert_eq "non-existent gen → rc 1" "1" "$_rc"
192+
assert_contains "not found message" "Generation not found" "$_out"
193+
194+
# ── rm command: dry-run with multiple gens ───────────────
195+
196+
section "rm command: dry-run with multiple gens"
197+
198+
_run_gc rm -n "20250604-100000" "20250603-080000"
199+
assert_eq "dry-run multi → rc 0" "0" "$_rc"
200+
assert_contains "dry-run echoes delete" "DELETE_GEN" "$_out"
201+
assert_contains "dry-run echoes dry flag" "dry=1" "$_out"
202+
203+
# ── rm command: delete tagged generation ─────────────────
204+
205+
section "rm command: delete tagged generation"
206+
207+
_run_gc rm --yes "20250615-120000-kde"
208+
assert_eq "delete tagged → rc 0" "0" "$_rc"
209+
assert_contains "tagged deleted" "DELETE_GEN 20250615-120000-kde" "$_out"
210+
186211
# ── gc command: dry-run ──────────────────────────────────
187212

188213
section "gc command: dry-run"
@@ -191,6 +216,89 @@ _run_gc -n
191216
assert_eq "gc dry-run → rc 0" "0" "$_rc"
192217
assert_contains "gc dry-run flag" "dry=1" "$_out"
193218

219+
# ── gc command: explicit keep count with dry-run ─────────
220+
221+
section "gc command: explicit count with dry-run"
222+
223+
_run_gc -n 5
224+
assert_eq "gc dry-run 5 → rc 0" "0" "$_rc"
225+
assert_contains "gc keep 5" "keep=5" "$_out"
226+
assert_contains "gc dry flag" "dry=1" "$_out"
227+
228+
# ── list command: empty output ───────────────────────────
229+
230+
section "list command: empty generations"
231+
232+
# Patch a separate copy to return empty list
233+
cat > "${TESTDIR}/gc-mocks-empty.txt" << PATCH
234+
235+
ESP="${_FAKE_ESP}"
236+
BTRFS_MOUNT="${_FAKE_BTRFS}"
237+
238+
acquire_lock() { echo "ACQUIRE_LOCK"; }
239+
ensure_btrfs_mounted(){ echo "ENSURE_BTRFS"; return 0; }
240+
get_current_subvol() { echo ""; }
241+
list_generations() {
242+
# empty
243+
return 0
244+
}
245+
delete_generation() { return 0; }
246+
warn_orphan_homes() { return 0; }
247+
garbage_collect() { return 0; }
248+
249+
PATCH
250+
251+
_TEST_SCRIPT_EMPTY="${TESTDIR}/atomic-gc-empty-test"
252+
sed \
253+
-e 's/\(\[\[ \$EUID -eq 0 \]\]\)/# \1/' \
254+
-e '/^validate_config || exit 1$/s/^/# /' \
255+
-e '/^_src "\${LIBDIR}\/common.sh"$/r '"${TESTDIR}/gc-mocks-empty.txt"'' \
256+
"$SCRIPT" > "$_TEST_SCRIPT_EMPTY"
257+
258+
chmod +x "$_TEST_SCRIPT_EMPTY"
259+
260+
run_cmd bash "$_TEST_SCRIPT_EMPTY" list
261+
assert_eq "empty list → exit 0" "0" "$_rc"
262+
assert_contains "no generations message" "No generations found" "$_out"
263+
264+
# ── list command: marks current correctly ────────────────
265+
266+
section "list command: current marking"
267+
268+
# Patch with known current
269+
cat > "${TESTDIR}/gc-mocks-current.txt" << PATCH
270+
271+
ESP="${_FAKE_ESP}"
272+
BTRFS_MOUNT="${_FAKE_BTRFS}"
273+
274+
acquire_lock() { :; }
275+
ensure_btrfs_mounted(){ return 0; }
276+
get_current_subvol() { echo "root-20250603-080000"; }
277+
list_generations() {
278+
echo "20250605-120000"
279+
echo "20250604-100000"
280+
echo "20250603-080000"
281+
}
282+
delete_generation() { return 0; }
283+
warn_orphan_homes() { return 0; }
284+
garbage_collect() { return 0; }
285+
286+
PATCH
287+
288+
_TEST_SCRIPT_CUR="${TESTDIR}/atomic-gc-cur-test"
289+
sed \
290+
-e 's/\(\[\[ \$EUID -eq 0 \]\]\)/# \1/' \
291+
-e '/^validate_config || exit 1$/s/^/# /' \
292+
-e '/^_src "\${LIBDIR}\/common.sh"$/r '"${TESTDIR}/gc-mocks-current.txt"'' \
293+
"$SCRIPT" > "$_TEST_SCRIPT_CUR"
294+
295+
chmod +x "$_TEST_SCRIPT_CUR"
296+
297+
run_cmd bash "$_TEST_SCRIPT_CUR" list
298+
assert_eq "list with current → exit 0" "0" "$_rc"
299+
assert_contains "third is current" "* 20250603-080000 (current)" "$_out"
300+
assert_not_contains "first not current" "* 20250605-120000" "$_out"
301+
194302
# ── Cleanup trap: verify in real script ───────────────────
195303

196304
section "Cleanup trap: verify in real script"

‎tests/test_rebuild_uki_flow.sh‎

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#!/usr/bin/env bash
2+
# tests/test_rebuild_uki_flow.sh — Integration tests for bin/atomic-rebuild-uki
3+
# Covers --list, overwrite prompt, mount/build/sign/unmount flow, cleanup
4+
# Run: bash tests/test_rebuild_uki_flow.sh
5+
6+
source "$(dirname "${BASH_SOURCE[0]}")/test_harness.sh"
7+
8+
SCRIPT="${PROJECT_ROOT}/bin/atomic-rebuild-uki"
9+
10+
# Mock verify-lib
11+
make_mock verify-lib "echo '${PROJECT_ROOT}/lib/atomic/common.sh'; exit 0"
12+
13+
# ── Setup ──────────────────────────────────────────────────────────────
14+
15+
_FAKE_ESP="${TESTDIR}/fake_esp"
16+
_FAKE_BTRFS="${TESTDIR}/fake_btrfs"
17+
_FAKE_LOCK="${TESTDIR}/lock"
18+
19+
mkdir -p "${_FAKE_ESP}/EFI/Linux"
20+
mkdir -p "${_FAKE_BTRFS}"
21+
mkdir -p "${_FAKE_LOCK}"
22+
23+
# Create a test generation
24+
GEN_ID="20250615-120000"
25+
mkdir -p "${_FAKE_BTRFS}/root-${GEN_ID}"
26+
mkdir -p "${_FAKE_BTRFS}/root-${GEN_ID}/boot"
27+
mkdir -p "${_FAKE_BTRFS}/root-${GEN_ID}/usr/lib/modules/6.14.0-arch1-1"
28+
echo "linux" > "${_FAKE_BTRFS}/root-${GEN_ID}/usr/lib/modules/6.14.0-arch1-1/pkgbase"
29+
touch "${_FAKE_BTRFS}/root-${GEN_ID}/boot/vmlinuz-linux"
30+
touch "${_FAKE_BTRFS}/root-${GEN_ID}/boot/initramfs-linux.img"
31+
mkdir -p "${_FAKE_BTRFS}/root-${GEN_ID}/etc"
32+
echo "PRETTY_NAME=\"Arch Linux\"" > "${_FAKE_BTRFS}/root-${GEN_ID}/etc/os-release"
33+
mkdir -p "${_FAKE_BTRFS}/root-${GEN_ID}/efi"
34+
35+
# Build mocks
36+
cat > "${TESTDIR}/rebuild-mocks.txt" << MOCKS
37+
38+
ESP="${_FAKE_ESP}"
39+
BTRFS_MOUNT="${_FAKE_BTRFS}"
40+
LOCK_DIR="${_FAKE_LOCK}"
41+
42+
acquire_lock() { echo "ACQUIRE_LOCK"; }
43+
validate_config() { return 0; }
44+
ensure_btrfs_mounted(){ echo "ENSURE_BTRFS"; return 0; }
45+
validate_subvolume() {
46+
local subvol="\$1"
47+
if [[ -d "\${BTRFS_MOUNT}/\${subvol}" ]]; then
48+
return 0
49+
else
50+
echo "ERROR: Subvolume not found" >&2
51+
return 1
52+
fi
53+
}
54+
55+
get_root_device() { echo "/dev/sda2"; }
56+
57+
build_uki() {
58+
local gen_id="\$1"
59+
echo "BUILD_UKI gen=\$gen_id"
60+
touch "\${ESP}/EFI/Linux/arch-\${gen_id}.efi"
61+
echo "\${ESP}/EFI/Linux/arch-\${gen_id}.efi"
62+
return 0
63+
}
64+
65+
sign_uki() { echo "SIGN_UKI"; return 0; }
66+
67+
mount() { echo "MOUNT: \$*"; return 0; }
68+
mountpoint() { return 1; }
69+
umount() { echo "UMOUNT: \$*"; return 0; }
70+
rmdir() { echo "RMDIR: \$*"; return 0; }
71+
mktemp() { echo "${_FAKE_LOCK}/rebuild-XXXXXX"; }
72+
73+
MOCKS
74+
75+
# Create patched test script
76+
_TEST_SCRIPT="${TESTDIR}/rebuild-uki-flow-test"
77+
78+
sed \
79+
-e 's/^\(\[\[ \$EUID -eq 0 \]\]\)/# \1/' \
80+
-e '/^validate_config || exit 1$/s/^/# /' \
81+
-e '/^_src "\${LIBDIR}\/common.sh"$/r '"${TESTDIR}/rebuild-mocks.txt"'' \
82+
"$SCRIPT" > "$_TEST_SCRIPT"
83+
84+
chmod +x "$_TEST_SCRIPT"
85+
86+
_run_rebuild() {
87+
run_cmd bash "$_TEST_SCRIPT" "$@"
88+
}
89+
90+
# ── Successful rebuild ───────────────────────────────────────────────
91+
92+
section "Successful rebuild flow"
93+
94+
# Remove UKI so no overwrite prompt
95+
rm -f "${_FAKE_ESP}/EFI/Linux/arch-${GEN_ID}.efi"
96+
97+
_run_rebuild "$GEN_ID"
98+
assert_eq "rebuild → exit 0" "0" "$_rc"
99+
assert_contains "lock acquired" "ACQUIRE_LOCK" "$_out"
100+
assert_contains "btrfs mounted" "ENSURE_BTRFS" "$_out"
101+
assert_contains "subvolume checked" "Checking subvolume" "$_out"
102+
assert_contains "mounting" "Mounting" "$_out"
103+
assert_contains "UKI built" "Building UKI" "$_out"
104+
assert_contains "BUILD_UKI called" "BUILD_UKI" "$_out"
105+
assert_contains "UKI signed" "SIGN_UKI" "$_out"
106+
assert_contains "unmounting" "Unmounting" "$_out"
107+
assert_contains "done message" "Done:" "$_out"
108+
109+
# ── Rebuild with existing UKI (overwrite) ────────────────────────────
110+
111+
section "Rebuild with existing UKI"
112+
113+
# Create UKI file so the prompt triggers
114+
touch "${_FAKE_ESP}/EFI/Linux/arch-${GEN_ID}.efi"
115+
116+
# The script asks for confirmation; we simulate "y" response
117+
run_cmd bash -c "echo 'y' | bash '${_TEST_SCRIPT}' '${GEN_ID}'"
118+
assert_eq "rebuild with overwrite → exit 0" "0" "$_rc"
119+
assert_contains "already exists" "already exists" "$_out"
120+
121+
# ── Rebuild with existing UKI (abort) ────────────────────────────────
122+
123+
section "Rebuild: user aborts overwrite"
124+
125+
touch "${_FAKE_ESP}/EFI/Linux/arch-${GEN_ID}.efi"
126+
127+
run_cmd bash -c "echo 'n' | bash '${_TEST_SCRIPT}' '${GEN_ID}'" || true
128+
assert_contains "aborted message" "Aborted" "$_out"
129+
130+
# ── --list: shows subvolumes ────────────────────────────────────────
131+
132+
section "List orphans"
133+
134+
# Create multiple subvolumes
135+
mkdir -p "${_FAKE_BTRFS}/root-20250614-110000"
136+
mkdir -p "${_FAKE_BTRFS}/root-20250613-100000-old"
137+
138+
# Create UKI for one of them
139+
touch "${_FAKE_ESP}/EFI/Linux/arch-20250615-120000.efi"
140+
touch "${_FAKE_ESP}/EFI/Linux/arch-20250614-110000.efi"
141+
# No UKI for 20250613-100000-old
142+
143+
run_cmd bash "$_TEST_SCRIPT" --list
144+
assert_eq "list → exit 0" "0" "$_rc"
145+
assert_contains "shows subvolumes" "Subvolumes" "$_out"
146+
assert_contains "UKI exists" "UKI exists" "$_out"
147+
assert_contains "UKI missing" "UKI missing" "$_out"
148+
149+
# ── Invalid GEN_ID ──────────────────────────────────────────────────
150+
151+
section "Invalid GEN_ID"
152+
153+
_run_rebuild "invalid"
154+
assert_eq "invalid → rc 1" "1" "$_rc"
155+
assert_contains "error mentions format" "Invalid GEN_ID" "$_out"
156+
157+
_run_rebuild "20250615"
158+
assert_eq "partial timestamp → rc 1" "1" "$_rc"
159+
160+
_run_rebuild ""
161+
assert_eq "empty → rc 1" "1" "$_rc"
162+
163+
_run_rebuild "../../etc/passwd"
164+
assert_eq "path traversal → rc 1" "1" "$_rc"
165+
166+
# ── Non-existent subvolume ──────────────────────────────────────────
167+
168+
section "Non-existent subvolume"
169+
170+
_run_rebuild "20250101-000000"
171+
assert_eq "missing subvol → rc 1" "1" "$_rc"
172+
assert_contains "error about subvolume" "Subvolume not found" "$_out"
173+
assert_contains "suggests --list" "atomic-rebuild-uki --list" "$_out"
174+
175+
# ── Tagged generation rebuild ────────────────────────────────────────
176+
177+
section "Tagged generation rebuild"
178+
179+
TAGGED_GEN="20250616-130000-kde"
180+
mkdir -p "${_FAKE_BTRFS}/root-${TAGGED_GEN}"
181+
mkdir -p "${_FAKE_BTRFS}/root-${TAGGED_GEN}/boot"
182+
mkdir -p "${_FAKE_BTRFS}/root-${TAGGED_GEN}/usr/lib/modules/6.14.0-arch1-1"
183+
echo "linux" > "${_FAKE_BTRFS}/root-${TAGGED_GEN}/usr/lib/modules/6.14.0-arch1-1/pkgbase"
184+
touch "${_FAKE_BTRFS}/root-${TAGGED_GEN}/boot/vmlinuz-linux"
185+
touch "${_FAKE_BTRFS}/root-${TAGGED_GEN}/boot/initramfs-linux.img"
186+
mkdir -p "${_FAKE_BTRFS}/root-${TAGGED_GEN}/etc"
187+
echo "PRETTY_NAME=\"Arch Linux\"" > "${_FAKE_BTRFS}/root-${TAGGED_GEN}/etc/os-release"
188+
mkdir -p "${_FAKE_BTRFS}/root-${TAGGED_GEN}/efi"
189+
190+
rm -f "${_FAKE_ESP}/EFI/Linux/arch-${TAGGED_GEN}.efi" 2>/dev/null || true
191+
192+
_run_rebuild "$TAGGED_GEN"
193+
assert_eq "tagged rebuild → exit 0" "0" "$_rc"
194+
assert_contains "tagged gen in output" "$TAGGED_GEN" "$_out"
195+
196+
# ── Cleanup trap structure ──────────────────────────────────────────
197+
198+
section "Cleanup trap: structure verification"
199+
200+
_script_content=$(cat "$SCRIPT")
201+
202+
assert_contains "cleanup function" "cleanup_rebuild()" "$_script_content"
203+
assert_contains "EXIT trap" "trap cleanup_rebuild EXIT" "$_script_content"
204+
assert_contains "unmounts MOUNT_DIR" 'MOUNT_DIR' "$_script_content"
205+
assert_contains "rmdir MOUNT_DIR" 'rmdir' "$_script_content"
206+
assert_contains "unmounts BTRFS" 'BTRFS_MOUNT' "$_script_content"
207+
assert_contains "closes lock FD" 'LOCK_FD' "$_script_content"
208+
209+
# ── Version and help ────────────────────────────────────────────────
210+
211+
section "Version and help"
212+
213+
run_cmd bash "$SCRIPT" --version
214+
assert_eq "version → exit 0" "0" "$_rc"
215+
assert_contains "version output" "atomic-rebuild-uki v" "$_out"
216+
217+
run_cmd bash "$SCRIPT" --help
218+
assert_eq "help → exit 0" "0" "$_rc"
219+
assert_contains "usage" "Usage" "$_out"
220+
assert_contains "list option" "list" "$_out"
221+
assert_contains "GEN_ID mentioned" "GEN_ID" "$_out"
222+
223+
# ── Unknown option (real script requires root, so GEN_ID validation
224+
# is tested in test_rebuild_uki.sh with patched script) ──────────
225+
226+
section "Unknown option behavior"
227+
228+
# --bogus is treated as GEN_ID — real script checks EUID first
229+
# so we verify the behavior with a quick patched test
230+
_TEST_UO="${TESTDIR}/rebuild-unknown-test"
231+
sed -e 's/^\(\[\[ \$EUID -eq 0 \]\]\)/# \1/' \
232+
-e '/^validate_config || exit 1$/s/^/# /' \
233+
"$SCRIPT" > "$_TEST_UO"
234+
chmod +x "$_TEST_UO"
235+
236+
run_cmd bash "$_TEST_UO" --bogus
237+
assert_eq "unknown option → rc 1" "1" "$_rc"
238+
assert_contains "invalid GEN_ID error" "Invalid GEN_ID" "$_out"
239+
240+
# ── list_orphans function structure ─────────────────────────────────
241+
242+
section "list_orphans function structure"
243+
244+
assert_contains "list_orphans defined" "list_orphans()" "$_script_content"
245+
assert_contains "subvolume glob" 'root-[0-9]' "$_script_content"
246+
assert_contains "sort -r for reverse" "sort -r" "$_script_content"
247+
assert_contains "empty message" "No generation subvolumes found" "$_script_content"
248+
249+
summary

0 commit comments

Comments
 (0)