#!/bin/bash # test-boot-arm64-disk.sh — Boot the ARM64 .arm64.img via UEFI + GRUB and # verify the init system reaches stage 90. # # This is the full-stack integration test: UEFI firmware -> GRUB -> kernel -> # initramfs -> staged init. Contrast with test-boot-arm64.sh which skips the # bootloader and loads kernel/initramfs directly. # # Exit 0 = PASS, Exit 1 = FAIL. # # Usage: ./test/qemu/test-boot-arm64-disk.sh [disk.img] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" VERSION="$(cat "$PROJECT_ROOT/VERSION")" DISK_IMAGE="${1:-$PROJECT_ROOT/output/kubesolo-os-${VERSION}.arm64.img}" TIMEOUT=180 echo "==> ARM64 UEFI Disk Boot Test" echo " Disk image: $DISK_IMAGE" echo " Timeout: ${TIMEOUT}s" if [ ! -f "$DISK_IMAGE" ]; then echo "ERROR: Disk image not found: $DISK_IMAGE" echo " Run 'make disk-image-arm64' to build it." exit 1 fi if ! command -v qemu-system-aarch64 >/dev/null 2>&1; then echo "ERROR: qemu-system-aarch64 not found." echo " apt install qemu-system-arm # Debian/Ubuntu" echo " dnf install qemu-system-aarch64 # Fedora/RHEL" exit 1 fi # --- Locate UEFI firmware --- UEFI_FW="" for candidate in \ /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \ /usr/share/AAVMF/AAVMF_CODE.fd \ /usr/share/edk2/aarch64/QEMU_EFI.fd \ /usr/share/qemu/edk2-aarch64-code.fd \ /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ /usr/local/share/qemu/edk2-aarch64-code.fd do if [ -f "$candidate" ]; then UEFI_FW="$candidate" break fi done if [ -z "$UEFI_FW" ]; then echo "ERROR: No ARM64 UEFI firmware found." echo " apt install qemu-efi-aarch64" exit 1 fi echo " UEFI fw: $UEFI_FW" # Copy disk image to a scratch file so the test doesn't mutate the source. # UEFI will write to grubenv on the EFI partition; we don't want to bake those # changes into the canonical build artifact. SCRATCH_DISK=$(mktemp /tmp/kubesolo-arm64-disk-test-XXXXXX.img) SERIAL_LOG=$(mktemp /tmp/kubesolo-arm64-disk-serial-XXXXXX.log) QEMU_PID="" cleanup() { [ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null || true rm -f "$SCRATCH_DISK" "$SERIAL_LOG" } trap cleanup EXIT cp --reflink=auto "$DISK_IMAGE" "$SCRATCH_DISK" 2>/dev/null || cp "$DISK_IMAGE" "$SCRATCH_DISK" # --- Launch QEMU --- qemu-system-aarch64 \ -machine virt \ -cpu cortex-a72 \ -m 2048 \ -smp 2 \ -nographic \ -bios "$UEFI_FW" \ -drive "file=$SCRATCH_DISK,format=raw,if=virtio,media=disk" \ -net nic,model=virtio \ -net user \ -serial "file:$SERIAL_LOG" & QEMU_PID=$! echo " Waiting for boot (PID $QEMU_PID)..." ELAPSED=0 SUCCESS=0 while [ "$ELAPSED" -lt "$TIMEOUT" ]; do if grep -q "\[kubesolo-init\] \[OK\] Stage 90-kubesolo.sh complete" "$SERIAL_LOG" 2>/dev/null; then SUCCESS=1 break fi if grep -q "KubeSolo is running" "$SERIAL_LOG" 2>/dev/null; then SUCCESS=1 break fi if ! kill -0 "$QEMU_PID" 2>/dev/null; then echo "" echo "==> FAIL: QEMU exited prematurely" echo " Last 30 lines of serial output:" tail -30 "$SERIAL_LOG" 2>/dev/null || echo " (no output)" exit 1 fi sleep 2 ELAPSED=$((ELAPSED + 2)) printf "\r Elapsed: %ds / %ds" "$ELAPSED" "$TIMEOUT" done echo "" kill "$QEMU_PID" 2>/dev/null || true wait "$QEMU_PID" 2>/dev/null || true QEMU_PID="" if [ "$SUCCESS" = "1" ]; then echo "==> ARM64 UEFI Disk Boot Test PASSED (${ELAPSED}s)" exit 0 fi echo "==> ARM64 UEFI Disk Boot Test FAILED (timeout ${TIMEOUT}s)" echo "" echo "==> Last 50 lines of serial output:" tail -50 "$SERIAL_LOG" 2>/dev/null || echo " (no output)" exit 1