diff --git a/test/qemu/README.md b/test/qemu/README.md index b6d134c4..62a046f6 100644 --- a/test/qemu/README.md +++ b/test/qemu/README.md @@ -62,3 +62,43 @@ Track harness work in jhuntwork/merelinux#635, distro boot work in #634, and the overall dinit transition in #633. A Mere disk-backed guest without an initramfs is a separate next step. Extend guest scenarios for package-manager execution and service behavior rather than treating this environment probe as that evidence. + +## Disk-backed Mere/dinit smoke test + +`dinit-smoke` copies an existing named profile and its store into a disposable +ext4 image. It boots the profile's own `boot/vmlinux`, runs dinit directly as +PID 1, verifies the mounted root and PID 1 identity, then requests power-off +through dinit. It supplies a minimal test-only service graph. It does not install +or select the distro's production boot services, verify a bootloader, or test +package-manager service reconciliation. + +Prepare an alternate storage root with the official repository config and key, +then install the three test inputs: + +```sh +mere --root "$storage" install -p guest linux busybox dinit +QEMU_TIMEOUT=120 sh test/qemu/dinit-smoke "$storage" guest > dinit-guest.log 2>&1 +``` + +The prepared root must have a named profile with logical `/mere/store` links and +`boot/vmlinux`, `/bin/dinit`, `/bin/sh`, mount utilities, and dinit's `shutdown`. +Use a dedicated test profile. Its `etc-defaults` are deliberately not activated: +the harness supplies its own boot, probe, and shutdown services under `/etc`. + +Additional host dependencies are `readlink`, `truncate`, and `mke2fs` supporting +`-d` directory population. No host mounts or loop devices are used. The kernel +must have ext4, IDE/ATA disk support, serial console, and automatic devtmpfs +mounting built in. QEMU loads the kernel directly with `root=/dev/sda` and +`init=/bin/dinit`; no initramfs is passed. + +`QEMU` and `QEMU_TIMEOUT` work as above. `QEMU_DISK_SIZE` defaults to `384M` and +must accommodate the copied store. The launcher reports success only after the +boot marker, dinit's kernel shutdown message, and kernel power-off message appear +and QEMU exits successfully. The copied root and disk are removed afterward. + +The initial successful inputs were Mere linux 6.18.42-2, BusyBox 1.37.0-10, and +dinit 0.22.1-1 (x86_64), with QEMU 8.2.2 TCG. Dinit stopped the test services and +powered the guest off. The test does not establish complete filesystem shutdown +ordering or persistent-disk integrity; those need explicit boot/shutdown services +and further checks under #634. These input versions describe observed evidence, +not a frozen dependency set. diff --git a/test/qemu/dinit-smoke b/test/qemu/dinit-smoke new file mode 100755 index 00000000..0316791f --- /dev/null +++ b/test/qemu/dinit-smoke @@ -0,0 +1,94 @@ +#!/bin/sh +# Boot an existing Mere profile on an ext4 disk with dinit as PID 1. +set -eu +if [ "$#" -ne 2 ]; then + printf 'Usage: %s STORAGE_ROOT PROFILE_NAME\n' "$0" >&2 + exit 2 +fi +storage=$1 +profile=$storage/mere/profiles/$2/root +qemu=${QEMU:-qemu-system-x86_64} +for dir in "$storage/mere/store" "$profile"; do + if [ ! -d "$dir" ]; then + printf 'Missing directory: %s\n' "$dir" >&2 + exit 2 + fi +done +work=$(mktemp -d) +cleanup() { + # Copied store directories retain read-only modes. Change only directories + # in the disposable copy; never follow its absolute store symlinks. + find "$work" -type d -exec chmod u+w {} + + rm -rf "$work" +} +trap cleanup 0 +trap 'exit 130' INT +trap 'exit 143' TERM +root=$work/root +mkdir -p "$root/mere" "$root/etc/dinit.d" "$root/proc" "$root/sys" \ + "$root/dev" "$root/run" "$root/tmp" "$root/var/log" +cp -RPp "$storage/mere/store" "$root/mere/" +for dir in bin sbin usr lib boot; do + if [ -d "$profile/$dir" ]; then + cp -RPp "$profile/$dir" "$root/" + fi +done +# Resolve the kernel's logical symlink against this guest's store, not the host. +kernel_target=$(readlink "$root/boot/vmlinux") +case $kernel_target in + /mere/store/*) kernel=$root$kernel_target ;; + *) printf 'Expected a logical store link for boot/vmlinux\n' >&2; exit 2 ;; +esac +[ -r "$kernel" ] +: > "$root/etc/fstab" +cat > "$root/etc/dinit.d/boot" <<'SERVICE' +type = internal +depends-on = shutdown-probe +SERVICE +cat > "$root/etc/dinit.d/probe" <<'SERVICE' +type = scripted +command = /bin/sh /etc/guest-probe +options = starts-on-console +SERVICE +cat > "$root/etc/dinit.d/shutdown-probe" <<'SERVICE' +type = process +command = /bin/sh /etc/shutdown-probe +depends-on = probe +options = starts-on-console +restart = false +SERVICE +cat > "$root/etc/guest-probe" <<'GUEST' +#!/bin/sh +set -eu +export PATH=/bin:/sbin:/usr/bin:/usr/sbin +mount -t proc proc /proc +mount -t sysfs sysfs /sys +printf 'MERE_KERNEL: '; uname -r +printf 'PID1: '; cat /proc/1/comm +[ "$(cat /proc/1/comm)" = dinit ] +printf 'ROOT: '; mount | grep ' on / ' +printf 'DINIT_BOOT_PASS\n' +sync +GUEST +cat > "$root/etc/shutdown-probe" <<'GUEST' +#!/bin/sh +export PATH=/bin:/sbin:/usr/bin:/usr/sbin +printf 'SHUTDOWN_PROBE_START\n' +sleep 1 +exec shutdown -p +GUEST +truncate -s "${QEMU_DISK_SIZE:-384M}" "$work/root.ext4" +mke2fs -q -t ext4 -F -d "$root" "$work/root.ext4" +timeout "${QEMU_TIMEOUT:-120}" "$qemu" \ + -accel tcg,thread=single -m 256 -smp 1 -nodefaults \ + -display none -serial stdio -monitor none -no-reboot \ + -kernel "$kernel" -drive "file=$work/root.ext4,format=raw,if=ide" \ + -append 'console=ttyS0 root=/dev/sda rootfstype=ext4 rw init=/bin/dinit panic=-1' \ + > "$work/serial.log" 2>&1 || { + cat "$work/serial.log" + exit 1 + } +cat "$work/serial.log" +grep -q '^DINIT_BOOT_PASS' "$work/serial.log" +grep -q 'Issuing shutdown via kernel' "$work/serial.log" +grep -q 'reboot: Power down' "$work/serial.log"