Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions test/qemu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
94 changes: 94 additions & 0 deletions test/qemu/dinit-smoke
Original file line number Diff line number Diff line change
@@ -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"