From 88e8411ec36a6bc4767d69030c597497f0849207 Mon Sep 17 00:00:00 2001 From: Danny Date: Fri, 20 Mar 2026 07:32:37 +0100 Subject: [PATCH] Added more tests --- configs/rc.d/pipewire | 35 ++- docs/CHANGELOG.md | 51 +++++ src/install/modules/disk.sh | 2 +- src/iso/build-iso-arch.sh | 7 +- tests/run-tests.sh | 445 ++++++++++++++++++++++++++++++------ 5 files changed, 463 insertions(+), 77 deletions(-) diff --git a/configs/rc.d/pipewire b/configs/rc.d/pipewire index efc13a9..ad3e27c 100755 --- a/configs/rc.d/pipewire +++ b/configs/rc.d/pipewire @@ -4,18 +4,37 @@ # ============================================================================ # PipeWire audio server + WirePlumber session manager. # NOTE: PipeWire is designed to run as a user service, not system-wide. -# This script starts it for the auto-login user (danny) on tty1. -# For the system-level boot, we just ensure the prerequisites are ready. -# The actual PipeWire startup is handled in the user's shell profile. +# This script prepares the runtime directory for the auto-login user. +# The actual PipeWire startup is handled in the user's shell profile +# (~/.zprofile) which starts pipewire, pipewire-pulse, and wireplumber. +# +# The auto-login user is detected from /etc/inittab (--autologin ). # ============================================================================ +# Source system configuration +[ -f /etc/rc.conf ] && . /etc/rc.conf + +# Detect the auto-login user from inittab +get_autologin_user() { + local user + user=$(grep -m1 -- '--autologin' /etc/inittab 2>/dev/null \ + | sed 's/.*--autologin \([^ ]*\).*/\1/') + echo "${user:-root}" +} + case "$1" in start) - echo " PipeWire: ready (will start with user session)" - # Ensure runtime directory exists for the user - mkdir -p /run/user/1000 - chown danny:danny /run/user/1000 - chmod 700 /run/user/1000 + AUTOLOGIN_USER=$(get_autologin_user) + AUTOLOGIN_UID=$(id -u "$AUTOLOGIN_USER" 2>/dev/null || echo 1000) + + echo " PipeWire: preparing runtime dir for ${AUTOLOGIN_USER} (uid ${AUTOLOGIN_UID})" + + # Ensure XDG_RUNTIME_DIR exists for the user session + mkdir -p "/run/user/${AUTOLOGIN_UID}" + chown "${AUTOLOGIN_USER}:${AUTOLOGIN_USER}" "/run/user/${AUTOLOGIN_UID}" + chmod 700 "/run/user/${AUTOLOGIN_UID}" + + echo " PipeWire: ready (will start with user session on tty1)" ;; stop) echo " Stopping PipeWire..." diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 744ff2f..1805b61 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,57 @@ --- +## V27 2026-03-20 07:00:00 + +**Add ISO build, boot chain verification, and fix installer bugs** + +### Changes: +- Fixed `configs/rc.d/pipewire`: removed hardcoded `danny`/UID `1000` + - Now auto-detects the autologin user from `/etc/inittab` via `get_autologin_user()` + - Creates XDG_RUNTIME_DIR using the actual user's UID from `id -u` + - Works correctly for any username set during installation +- Fixed `src/iso/build-iso-arch.sh`: `mkdir -p install/configs` now runs BEFORE + copying zprofile into it (was after — silent failure, zprofile never reached the ISO) + - Also now copies inittab, rc.conf, and full rc.d/ directory into ISO's install/configs/ +- Fixed `src/install/modules/disk.sh`: in `configure_boot()`, `mkdir -p EFI/Linux/` + now runs BEFORE `cp vmlinuz vmlinuz.efi` (was after — would fail on clean ESP) +- Added Test Suite 7 (Boot Chain Verification) — 20+ static checks that verify the + complete EFISTUB → init → autologin → zsh → dwl chain is correctly wired: + - `chain.efistub` — CONFIG_EFI_STUB=y in kernel config + - `chain.autologin` — --autologin in inittab + - `chain.inittab_sysinit/multi` — rc.sysinit and rc.multi referenced + - `chain.rc.sysinit/multi/shutdown` — scripts exist and are executable + - `chain.daemon_listed.*` — eudev/dbus/dhcpcd/pipewire in DAEMONS array + - `chain.zprofile_dwl` — zprofile contains `exec dwl` + - `chain.zprofile_tty1_guard` — only runs on /dev/tty1 + - `chain.zprofile_wayland_guard` — won't double-launch + - `chain.zprofile_pipewire` — starts audio stack + - `chain.zprofile_nvidia_env` — GBM_BACKEND set for RTX 5090 + - `chain.zprofile_xdg_runtime` — XDG_RUNTIME_DIR created + - `chain.pipewire_dynamic_user` — no hardcoded username + - `chain.installer_copies_zprofile` — installer deploys zprofile + - `chain.installer_updates_inittab` — installer updates autologin user + - `chain.boot_mkdir_before_cp` — mkdir before cp in configure_boot + - `chain.efibootmgr` — UEFI boot entry created + - `chain.nvidia_modules` — NVIDIA in MODULES array + - `chain.nvidia_modeset` — nvidia-drm modeset=1 set +- Added Test Suite 9 (ISO Build) — actually builds the ISO via `build-iso-arch.sh`: + - Checks prerequisites (mksquashfs, xorriso, mkfs.fat, mcopy) + - Builds ISO and verifies it was produced + - Mounts ISO and squashfs to verify all critical files are inside: + rc.conf, rc.d scripts, installer modules, zprofile, dpack binary, package repos + - Verifies the zprofile inside the ISO has `exec dwl` +- Renumbered QEMU boot test to Suite 10 + +### Plan deviation/changes: +- None + +### What is missing/needs polish: +- ISO build requires sudo (test runner needs root for mount operations) +- QEMU boot test still depends on a bootable kernel being present + +--- + ## V26 2026-03-20 06:30:00 **Fix test runner bugs and add missing test coverage** diff --git a/src/install/modules/disk.sh b/src/install/modules/disk.sh index bb3ea16..989786e 100755 --- a/src/install/modules/disk.sh +++ b/src/install/modules/disk.sh @@ -151,8 +151,8 @@ configure_boot() { # Copy kernel to ESP if [ -f "${MOUNT_POINT}/boot/vmlinuz" ]; then - cp "${MOUNT_POINT}/boot/vmlinuz" "${MOUNT_POINT}/boot/efi/EFI/Linux/vmlinuz.efi" mkdir -p "${MOUNT_POINT}/boot/efi/EFI/Linux" + cp "${MOUNT_POINT}/boot/vmlinuz" "${MOUNT_POINT}/boot/efi/EFI/Linux/vmlinuz.efi" ok "Kernel copied to ESP" else warn "No kernel found — you'll need to install one before booting" diff --git a/src/iso/build-iso-arch.sh b/src/iso/build-iso-arch.sh index 8e1bcee..9bcc80b 100755 --- a/src/iso/build-iso-arch.sh +++ b/src/iso/build-iso-arch.sh @@ -132,10 +132,13 @@ l3:3:wait:/etc/rc.d/rc.multi ca::ctrlaltdel:/sbin/shutdown -r now EOF -# Installer scripts +# Installer scripts and configs +mkdir -p "${ROOTFS}/install/configs" cp -a "${PROJECT_ROOT}/src/install/"* "${ROOTFS}/install/" 2>/dev/null || true cp "${PROJECT_ROOT}/configs/zprofile" "${ROOTFS}/install/configs/zprofile" 2>/dev/null || true -mkdir -p "${ROOTFS}/install/configs" +cp "${PROJECT_ROOT}/configs/inittab" "${ROOTFS}/install/configs/inittab" 2>/dev/null || true +cp "${PROJECT_ROOT}/configs/rc.conf" "${ROOTFS}/install/configs/rc.conf" 2>/dev/null || true +cp -a "${PROJECT_ROOT}/configs/rc.d" "${ROOTFS}/install/configs/rc.d" 2>/dev/null || true # Live shell profile with installer prompt cat > "${ROOTFS}/root/.bash_profile" << 'PROFILE' diff --git a/tests/run-tests.sh b/tests/run-tests.sh index 3e66d8a..cf6c7ce 100755 --- a/tests/run-tests.sh +++ b/tests/run-tests.sh @@ -498,10 +498,193 @@ for daemon in eudev syslog dbus dhcpcd pipewire; do done # ============================================================================ -# TEST SUITE 7: Package Signing (network test) +# TEST SUITE 7: Boot Chain Verification +# ============================================================================ +# Verify that the complete boot-to-desktop chain is correctly wired: +# EFISTUB → init → rc.sysinit → rc.multi → agetty --autologin → zsh → dwl +# These are static checks — no running system required. +echo -e "\n${BOLD}=== Test Suite 7: Boot Chain Verification ===${NC}\n" + +# 7.1 — Kernel has EFISTUB +if [ -f "$KCONFIG" ] && grep -q "^CONFIG_EFI_STUB=y" "$KCONFIG"; then + record_test "chain.efistub" "pass" +else + record_test "chain.efistub" "fail" "CONFIG_EFI_STUB not set — kernel won't boot as EFI binary" +fi + +# 7.2 — inittab has auto-login +INITTAB="${PROJECT_ROOT}/configs/inittab" +if [ -f "$INITTAB" ] && grep -q -- '--autologin' "$INITTAB"; then + AUTOLOGIN_USER=$(grep -- '--autologin' "$INITTAB" | sed 's/.*--autologin \([^ ]*\).*/\1/' | head -1) + record_test "chain.autologin" "pass" "User: ${AUTOLOGIN_USER}" +else + record_test "chain.autologin" "fail" "No --autologin in inittab — user won't be logged in automatically" +fi + +# 7.3 — inittab runs rc.sysinit and rc.multi +if [ -f "$INITTAB" ]; then + grep -q "rc.sysinit" "$INITTAB" && record_test "chain.inittab_sysinit" "pass" \ + || record_test "chain.inittab_sysinit" "fail" "inittab missing rc.sysinit" + grep -q "rc.multi" "$INITTAB" && record_test "chain.inittab_multi" "pass" \ + || record_test "chain.inittab_multi" "fail" "inittab missing rc.multi" +fi + +# 7.4 — rc.sysinit and rc.multi exist and are executable +for rcscript in rc.sysinit rc.multi rc.shutdown; do + path="${PROJECT_ROOT}/configs/rc.d/${rcscript}" + if [ -x "$path" ]; then + record_test "chain.${rcscript}" "pass" + else + record_test "chain.${rcscript}" "fail" "Missing or not executable" + fi +done + +# 7.5 — rc.conf has DAEMONS array with required services +RC_CONF="${PROJECT_ROOT}/configs/rc.conf" +if [ -f "$RC_CONF" ]; then + for svc in eudev dbus dhcpcd pipewire; do + if grep -q "DAEMONS=.*${svc}" "$RC_CONF"; then + record_test "chain.daemon_listed.${svc}" "pass" + else + record_test "chain.daemon_listed.${svc}" "fail" "${svc} not in DAEMONS array — won't start at boot" + fi + done +fi + +# 7.6 — zprofile contains dwl auto-start on tty1 +ZPROFILE="${PROJECT_ROOT}/configs/zprofile" +if [ -f "$ZPROFILE" ]; then + if grep -q 'exec dwl' "$ZPROFILE"; then + record_test "chain.zprofile_dwl" "pass" + else + record_test "chain.zprofile_dwl" "fail" "zprofile missing 'exec dwl' — desktop won't launch" + fi + + if grep -q '/dev/tty1' "$ZPROFILE"; then + record_test "chain.zprofile_tty1_guard" "pass" + else + record_test "chain.zprofile_tty1_guard" "fail" "zprofile missing tty1 check — dwl might launch on wrong tty" + fi + + if grep -q 'WAYLAND_DISPLAY' "$ZPROFILE"; then + record_test "chain.zprofile_wayland_guard" "pass" + else + record_test "chain.zprofile_wayland_guard" "fail" "zprofile missing WAYLAND_DISPLAY check — might double-launch" + fi + + if grep -q 'pipewire' "$ZPROFILE"; then + record_test "chain.zprofile_pipewire" "pass" + else + record_test "chain.zprofile_pipewire" "fail" "zprofile missing pipewire startup — no audio" + fi + + if grep -q 'GBM_BACKEND' "$ZPROFILE"; then + record_test "chain.zprofile_nvidia_env" "pass" + else + record_test "chain.zprofile_nvidia_env" "fail" "zprofile missing NVIDIA env vars — Wayland may fail on RTX 5090" + fi + + if grep -q 'XDG_RUNTIME_DIR' "$ZPROFILE"; then + record_test "chain.zprofile_xdg_runtime" "pass" + else + record_test "chain.zprofile_xdg_runtime" "fail" "zprofile missing XDG_RUNTIME_DIR setup" + fi +else + record_test "chain.zprofile_dwl" "fail" "zprofile file missing entirely" +fi + +# 7.7 — rc.d/pipewire does NOT hardcode a username (should auto-detect) +PW_SCRIPT="${PROJECT_ROOT}/configs/rc.d/pipewire" +if [ -f "$PW_SCRIPT" ]; then + if grep -q 'get_autologin_user\|--autologin' "$PW_SCRIPT"; then + record_test "chain.pipewire_dynamic_user" "pass" + else + # Check if it still hardcodes 'danny' + if grep -q 'chown danny' "$PW_SCRIPT"; then + record_test "chain.pipewire_dynamic_user" "fail" "rc.d/pipewire hardcodes username 'danny'" + else + record_test "chain.pipewire_dynamic_user" "pass" + fi + fi +fi + +# 7.8 — Installer copies zprofile to target user home +INSTALLER_USER="${PROJECT_ROOT}/src/install/modules/user.sh" +if [ -f "$INSTALLER_USER" ]; then + if grep -q 'zprofile' "$INSTALLER_USER"; then + record_test "chain.installer_copies_zprofile" "pass" + else + record_test "chain.installer_copies_zprofile" "fail" "Installer doesn't copy zprofile — target user won't auto-start dwl" + fi + + if grep -q -- '--autologin' "$INSTALLER_USER"; then + record_test "chain.installer_updates_inittab" "pass" + else + record_test "chain.installer_updates_inittab" "fail" "Installer doesn't update inittab autologin user" + fi +fi + +# 7.9 — Installer boot config: mkdir before cp, efibootmgr call +INSTALLER_DISK="${PROJECT_ROOT}/src/install/modules/disk.sh" +if [ -f "$INSTALLER_DISK" ]; then + # Check that mkdir comes before cp in configure_boot + if python3 -c " +import re, sys +with open('${INSTALLER_DISK}') as f: + content = f.read() +# Find configure_boot function +m = re.search(r'configure_boot\(\)\s*\{(.*?)\n\}', content, re.DOTALL) +if not m: + sys.exit(1) +body = m.group(1) +mkdir_pos = body.find('mkdir -p') +cp_pos = body.find('cp.*vmlinuz.*vmlinuz.efi') if 'cp' in body else body.find('cp ') +# Just check mkdir exists before the cp of vmlinuz +lines = body.split('\n') +mkdir_line = cp_line = -1 +for i, line in enumerate(lines): + if 'mkdir -p' in line and 'EFI/Linux' in line: + mkdir_line = i + if 'vmlinuz.efi' in line and 'cp ' in line: + cp_line = i +if mkdir_line >= 0 and cp_line >= 0 and mkdir_line < cp_line: + sys.exit(0) +else: + sys.exit(1) +" 2>/dev/null; then + record_test "chain.boot_mkdir_before_cp" "pass" + else + record_test "chain.boot_mkdir_before_cp" "fail" "configure_boot: mkdir must come before cp to EFI/Linux/" + fi + + if grep -q 'efibootmgr' "$INSTALLER_DISK"; then + record_test "chain.efibootmgr" "pass" + else + record_test "chain.efibootmgr" "fail" "Installer doesn't create UEFI boot entry" + fi +fi + +# 7.10 — NVIDIA kernel modules in rc.conf MODULES array +if [ -f "$RC_CONF" ]; then + if grep -q 'MODULES=.*nvidia' "$RC_CONF"; then + record_test "chain.nvidia_modules" "pass" + else + record_test "chain.nvidia_modules" "fail" "NVIDIA modules not in MODULES array — GPU won't work" + fi + + if grep -q 'nvidia-drm.*modeset=1\|modeset=1.*nvidia-drm' "$RC_CONF" || \ + grep -q 'MODULE_PARAMS.*nvidia-drm.*modeset' "$RC_CONF"; then + record_test "chain.nvidia_modeset" "pass" + else + record_test "chain.nvidia_modeset" "fail" "nvidia-drm modeset=1 not set — Wayland DRM/KMS won't work" + fi +fi + +# ============================================================================ +# TEST SUITE 8: Package Signing (network test) # ============================================================================ if [ "$QUICK_MODE" = false ] && [ -x "$DPACK" ]; then - echo -e "\n${BOLD}=== Test Suite 7: Package Signing ===${NC}\n" + echo -e "\n${BOLD}=== Test Suite 8: Package Signing ===${NC}\n" ZLIB_TOML="${PROJECT_ROOT}/src/repos/core/zlib/zlib.toml" if [ -f "$ZLIB_TOML" ]; then @@ -517,81 +700,211 @@ if [ "$QUICK_MODE" = false ] && [ -x "$DPACK" ]; then fi else if [ "$QUICK_MODE" = true ]; then - echo -e "\n${BOLD}=== Test Suite 7: Package Signing (SKIPPED) ===${NC}\n" + echo -e "\n${BOLD}=== Test Suite 8: Package Signing (SKIPPED) ===${NC}\n" record_test "sign.zlib" "skip" "Quick mode" fi fi # ============================================================================ -# TEST SUITE 8: QEMU Boot Test (skipped in quick mode) +# TEST SUITE 9: ISO Build # ============================================================================ -if [ "$QUICK_MODE" = false ] && [ -n "$OVMF_PATH" ]; then - echo -e "\n${BOLD}=== Test Suite 8: QEMU Boot Test ===${NC}\n" +echo -e "\n${BOLD}=== Test Suite 9: ISO Build ===${NC}\n" - ISO="${PROJECT_ROOT}/darkforge-live.iso" - if [ -f "$ISO" ]; then - echo " Testing ISO boot in QEMU (60s timeout)..." - QEMU_DISK=$(mktemp /tmp/darkforge-qemu-XXXXX.qcow2) - qemu-img create -f qcow2 "$QEMU_DISK" 20G >/dev/null 2>&1 +ISO="${PROJECT_ROOT}/darkforge-live.iso" - KVM_FLAG="" - [ -c /dev/kvm ] && KVM_FLAG="-enable-kvm" - - # Build OVMF flags — split CODE/VARS files need -drive, single .fd uses -bios - OVMF_FLAGS="" - if echo "$OVMF_PATH" | grep -q "OVMF_CODE"; then - OVMF_VARS_TEMPLATE="$(dirname "$OVMF_PATH")/OVMF_VARS.fd" - # Try 4m variant first - if [ ! -f "$OVMF_VARS_TEMPLATE" ]; then - OVMF_VARS_TEMPLATE="$(dirname "$OVMF_PATH")/OVMF_VARS.4m.fd" - fi - OVMF_VARS_COPY="/tmp/darkforge-ovmf-vars.fd" - cp "$OVMF_VARS_TEMPLATE" "$OVMF_VARS_COPY" 2>/dev/null || dd if=/dev/zero of="$OVMF_VARS_COPY" bs=256K count=1 2>/dev/null - OVMF_FLAGS="-drive if=pflash,format=raw,readonly=on,file=${OVMF_PATH} -drive if=pflash,format=raw,file=${OVMF_VARS_COPY}" - else - OVMF_FLAGS="-bios ${OVMF_PATH}" - fi - - timeout 60 qemu-system-x86_64 \ - ${KVM_FLAG} \ - -m 2G \ - -smp 2 \ - ${OVMF_FLAGS} \ - -cdrom "$ISO" \ - -drive file="$QEMU_DISK",format=qcow2,if=virtio \ - -nographic \ - -serial mon:stdio \ - -no-reboot \ - 2>"${LOG_DIR}/qemu-stderr.log" | head -200 > "${LOG_DIR}/qemu-output.log" & - QEMU_PID=$! - - sleep 60 - kill $QEMU_PID 2>/dev/null - wait $QEMU_PID 2>/dev/null - - # Check if we got kernel boot messages - if grep -qi "linux version\|darkforge\|kernel" "${LOG_DIR}/qemu-output.log" 2>/dev/null; then - record_test "qemu.kernel_boots" "pass" - else - record_test "qemu.kernel_boots" "fail" "No kernel boot messages in serial output" - fi - - # Check if we got to userspace - if grep -qi "login:\|installer\|welcome\|darkforge" "${LOG_DIR}/qemu-output.log" 2>/dev/null; then - record_test "qemu.reaches_userspace" "pass" - else - record_test "qemu.reaches_userspace" "fail" "Did not reach login prompt" - fi - - rm -f "$QEMU_DISK" +# Check ISO build prerequisites +ISO_PREREQS_OK=true +for tool in mksquashfs xorriso mkfs.fat mcopy; do + if command -v "$tool" >/dev/null 2>&1; then + record_test "iso.prereq.${tool}" "pass" else - record_test "qemu.iso_exists" "fail" "No ISO found — build it first with src/iso/build-iso.sh" - record_test "qemu.kernel_boots" "skip" "No ISO" - record_test "qemu.reaches_userspace" "skip" "No ISO" + record_test "iso.prereq.${tool}" "fail" "Not installed — needed for ISO build" + ISO_PREREQS_OK=false + fi +done + +if [ "$QUICK_MODE" = false ] && [ "$ISO_PREREQS_OK" = true ]; then + # Build the ISO + echo -e " ${CYAN}Building ISO (this may take a few minutes)...${NC}" + t_start=$(date +%s) + if sudo bash "${PROJECT_ROOT}/src/iso/build-iso-arch.sh" > "${LOG_DIR}/iso-build.log" 2>&1; then + t_end=$(date +%s) + record_test "iso.build" "pass" "" "$((t_end - t_start))" + else + t_end=$(date +%s) + err=$(tail -10 "${LOG_DIR}/iso-build.log" | tr '\n' ' ' | tr '"' "'") + record_test "iso.build" "fail" "${err}" "$((t_end - t_start))" + fi + + # Check ISO was produced + if [ -f "$ISO" ]; then + ISO_SIZE=$(du -sh "$ISO" | cut -f1) + record_test "iso.exists" "pass" "Size: ${ISO_SIZE}" + + # Verify ISO has EFI boot structure + if xorriso -indev "$ISO" -find / -name "BOOTX64.EFI" 2>/dev/null | grep -q "BOOTX64"; then + record_test "iso.has_efi_binary" "pass" + else + record_test "iso.has_efi_binary" "fail" "ISO missing EFI/BOOT/BOOTX64.EFI — won't UEFI boot" + fi + + # Verify ISO has squashfs rootfs + if xorriso -indev "$ISO" -find / -name "rootfs.img" 2>/dev/null | grep -q "rootfs"; then + record_test "iso.has_rootfs" "pass" + else + record_test "iso.has_rootfs" "fail" "ISO missing LiveOS/rootfs.img" + fi + + # Mount the squashfs and verify critical files are inside + SQFS_MNT=$(mktemp -d /tmp/darkforge-sqfs-XXXXX) + SQFS_EXTRACTED=false + + # Extract squashfs from ISO to check contents + ISO_MNT=$(mktemp -d /tmp/darkforge-iso-XXXXX) + if sudo mount -o loop,ro "$ISO" "$ISO_MNT" 2>/dev/null; then + if [ -f "$ISO_MNT/LiveOS/rootfs.img" ]; then + if sudo mount -o loop,ro "$ISO_MNT/LiveOS/rootfs.img" "$SQFS_MNT" 2>/dev/null; then + SQFS_EXTRACTED=true + fi + fi + fi + + if [ "$SQFS_EXTRACTED" = true ]; then + # Check that the live rootfs has all the critical files for the install chain + for check_file in \ + "etc/rc.conf:rc.conf in live rootfs" \ + "etc/rc.d/rc.sysinit:rc.sysinit in live rootfs" \ + "etc/rc.d/rc.multi:rc.multi in live rootfs" \ + "etc/rc.d/eudev:eudev daemon in live rootfs" \ + "etc/rc.d/dbus:dbus daemon in live rootfs" \ + "etc/rc.d/dhcpcd:dhcpcd daemon in live rootfs" \ + "etc/rc.d/pipewire:pipewire daemon in live rootfs" \ + "install/install.sh:installer script in live rootfs" \ + "install/modules/disk.sh:disk module in live rootfs" \ + "install/modules/user.sh:user module in live rootfs" \ + "install/modules/locale.sh:locale module in live rootfs" \ + "install/modules/packages.sh:packages module in live rootfs" \ + "install/configs/zprofile:zprofile for target user in live rootfs"; do + fpath="${check_file%%:*}" + fdesc="${check_file##*:}" + if [ -f "$SQFS_MNT/$fpath" ]; then + record_test "iso.rootfs.${fpath##*/}" "pass" + else + record_test "iso.rootfs.${fpath##*/}" "fail" "Missing: ${fdesc}" + fi + done + + # Check that the zprofile in the ISO has dwl auto-start + if [ -f "$SQFS_MNT/install/configs/zprofile" ]; then + if grep -q 'exec dwl' "$SQFS_MNT/install/configs/zprofile"; then + record_test "iso.rootfs.zprofile_has_dwl" "pass" + else + record_test "iso.rootfs.zprofile_has_dwl" "fail" "zprofile in ISO missing 'exec dwl'" + fi + fi + + # Check that dpack binary is in the ISO + if [ -f "$SQFS_MNT/usr/bin/dpack" ]; then + record_test "iso.rootfs.dpack_binary" "pass" + else + record_test "iso.rootfs.dpack_binary" "fail" "dpack binary missing from ISO — installer can't use dpack" + fi + + # Check that package repos are in the ISO + if [ -d "$SQFS_MNT/var/lib/dpack/repos/core" ]; then + record_test "iso.rootfs.repos" "pass" + else + record_test "iso.rootfs.repos" "fail" "Package repos missing from ISO" + fi + + sudo umount "$SQFS_MNT" 2>/dev/null + else + record_test "iso.rootfs_mount" "skip" "Could not mount squashfs for inspection" + fi + + sudo umount "$ISO_MNT" 2>/dev/null + rmdir "$SQFS_MNT" "$ISO_MNT" 2>/dev/null + else + record_test "iso.exists" "fail" "ISO not produced by build script" fi else - echo -e "\n${BOLD}=== Test Suite 8: QEMU Boot Test (SKIPPED) ===${NC}\n" - record_test "qemu.kernel_boots" "skip" "Quick mode or no OVMF" + if [ "$QUICK_MODE" = true ]; then + record_test "iso.build" "skip" "Quick mode" + else + record_test "iso.build" "skip" "Missing ISO build prerequisites" + fi +fi + +# ============================================================================ +# TEST SUITE 10: QEMU Boot Test (skipped in quick mode) +# ============================================================================ +if [ "$QUICK_MODE" = false ] && [ -n "${OVMF_PATH:-}" ] && [ -f "${ISO}" ]; then + echo -e "\n${BOLD}=== Test Suite 10: QEMU Boot Test ===${NC}\n" + + echo -e " ${CYAN}Testing ISO boot in QEMU (60s timeout)...${NC}" + QEMU_DISK=$(mktemp /tmp/darkforge-qemu-XXXXX.qcow2) + qemu-img create -f qcow2 "$QEMU_DISK" 20G >/dev/null 2>&1 + + KVM_FLAG="" + [ -c /dev/kvm ] && KVM_FLAG="-enable-kvm" + + # Build OVMF flags — split CODE/VARS files need -drive, single .fd uses -bios + OVMF_FLAGS="" + if echo "$OVMF_PATH" | grep -q "OVMF_CODE"; then + OVMF_VARS_TEMPLATE="$(dirname "$OVMF_PATH")/OVMF_VARS.fd" + # Try 4m variant if regular not found + if [ ! -f "$OVMF_VARS_TEMPLATE" ]; then + OVMF_VARS_TEMPLATE="$(dirname "$OVMF_PATH")/OVMF_VARS.4m.fd" + fi + OVMF_VARS_COPY="/tmp/darkforge-ovmf-vars.fd" + cp "$OVMF_VARS_TEMPLATE" "$OVMF_VARS_COPY" 2>/dev/null || \ + dd if=/dev/zero of="$OVMF_VARS_COPY" bs=256K count=1 2>/dev/null + OVMF_FLAGS="-drive if=pflash,format=raw,readonly=on,file=${OVMF_PATH} -drive if=pflash,format=raw,file=${OVMF_VARS_COPY}" + else + OVMF_FLAGS="-bios ${OVMF_PATH}" + fi + + timeout 60 qemu-system-x86_64 \ + ${KVM_FLAG} \ + -m 2G \ + -smp 2 \ + ${OVMF_FLAGS} \ + -cdrom "$ISO" \ + -drive file="$QEMU_DISK",format=qcow2,if=virtio \ + -nographic \ + -serial mon:stdio \ + -no-reboot \ + 2>"${LOG_DIR}/qemu-stderr.log" | head -200 > "${LOG_DIR}/qemu-output.log" & + QEMU_PID=$! + + sleep 60 + kill $QEMU_PID 2>/dev/null + wait $QEMU_PID 2>/dev/null + + # Check if we got kernel boot messages + if grep -qi "linux version\|darkforge\|kernel" "${LOG_DIR}/qemu-output.log" 2>/dev/null; then + record_test "qemu.kernel_boots" "pass" + else + record_test "qemu.kernel_boots" "fail" "No kernel boot messages in serial output" + fi + + # Check if we got to userspace + if grep -qi "login:\|installer\|welcome\|darkforge" "${LOG_DIR}/qemu-output.log" 2>/dev/null; then + record_test "qemu.reaches_userspace" "pass" + else + record_test "qemu.reaches_userspace" "fail" "Did not reach login prompt" + fi + + rm -f "$QEMU_DISK" +else + echo -e "\n${BOLD}=== Test Suite 10: QEMU Boot Test (SKIPPED) ===${NC}\n" + if [ "$QUICK_MODE" = true ]; then + record_test "qemu.kernel_boots" "skip" "Quick mode" + elif [ -z "${OVMF_PATH:-}" ]; then + record_test "qemu.kernel_boots" "skip" "No OVMF firmware" + else + record_test "qemu.kernel_boots" "skip" "No ISO built" + fi fi # ============================================================================