From d329989f25df70465262d892c171fded661bfeaf Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Mon, 10 Aug 2026 20:51:38 +0300 Subject: [PATCH] test(e2e): guard the one-command disk-VM lifecycle A fresh Alpine disk VM must reach a clickable xfce desktop from a single stoat up: install, auto-restart after the poweroff, auto-apply the recipe, reboot once so eudev and libinput take effect. Each stage has regressed before. The script asserts the end state a user sees over stoat exec: xfce present, udevd is the device manager, Xorg drives input through libinput. Runs against a unique VM it deletes on exit. just e2e wraps it. --- justfile | 6 +++++ scripts/e2e.sh | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100755 scripts/e2e.sh diff --git a/justfile b/justfile index 15cbcbd..e76b777 100644 --- a/justfile +++ b/justfile @@ -68,6 +68,12 @@ test-pkg pkg: race: go test -race ./... +# full lifecycle check on a real disk VM: install -> apply -> reboot -> mouse +# works. Needs KVM and network, ~15 min. Set STOAT_HOME to isolate it. +[group('test')] +e2e: + ./scripts/e2e.sh + # coverage summary per package [group('test')] cover: diff --git a/scripts/e2e.sh b/scripts/e2e.sh new file mode 100755 index 0000000..1aa3a62 --- /dev/null +++ b/scripts/e2e.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env sh +# End-to-end lifecycle check: one `stoat up` must take a fresh Alpine disk VM +# from nothing to a desktop with a working mouse, with zero manual steps. +# +# The chain under test: install -> auto-restart after poweroff -> auto-apply +# the xfce recipe -> reboot once so eudev/libinput take effect. Each stage is +# code that has regressed before, so this asserts the OUTCOME a user sees: +# udev is the device manager and Xorg drives input through libinput. +# +# Runs against the real data root by default, under a unique VM name it deletes +# on exit. Set STOAT_HOME to isolate it from your VMs. Needs KVM and network; +# the xfce apk pull is ~1.4GB, so budget ~15 minutes. +set -eu + +IMAGE=alpine-standard +RECIPE=xfce +VM="e2e-$$" +UP_TIMEOUT=1200 # install + xfce pull + reboot-once +X_TIMEOUT=90 # Xorg restart after the reboot-once + +# Prefer the just-built binary over whatever is on PATH. +root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +STOAT="$root/stoat" +[ -x "$STOAT" ] || STOAT=stoat + +say() { printf '\n=== %s ===\n' "$*"; } +fail() { printf '\nFAIL: %s\n' "$*" >&2; exit 1; } + +cleanup() { + "$STOAT" down "$VM" >/dev/null 2>&1 || true + "$STOAT" rm "$VM" -y >/dev/null 2>&1 || true +} +trap cleanup EXIT INT TERM + +say "build" +( cd "$root" && go build -o stoat ./cmd/stoat ) + +say "image $IMAGE" +"$STOAT" images 2>/dev/null | grep -q "^$IMAGE .*downloaded" || "$STOAT" pull "$IMAGE" + +say "create $VM (alpine disk, recipe $RECIPE)" +"$STOAT" create "$VM" --image "$IMAGE" --mode disk --recipes "$RECIPE" + +say "up $VM (install -> restart -> apply -> reboot-once)" +timeout "$UP_TIMEOUT" "$STOAT" up "$VM" || fail "up did not finish in ${UP_TIMEOUT}s" + +say "assert: recipe applied (xfce present)" +"$STOAT" exec "$VM" -- sh -c 'command -v xfce4-session' \ + || fail "xfce4-session missing: the recipe did not apply" + +say "assert: udev is the device manager (not mdev)" +"$STOAT" exec "$VM" -- sh -c 'pgrep -x udevd >/dev/null' \ + || fail "udevd not running: setup-devd udev did not take effect" +"$STOAT" exec "$VM" -- sh -c '! pgrep -x mdev >/dev/null' \ + || fail "mdev still running: the device manager did not switch to udev" + +say "assert: Xorg drives input through libinput (mouse clickable)" +# X restarts on the reboot-once; poll until its log records a libinput device. +end=0 +while [ "$end" -lt "$X_TIMEOUT" ]; do + if "$STOAT" exec "$VM" -- sh -c \ + "grep -q \"Using input driver 'libinput'\" /var/log/Xorg.0.log 2>/dev/null"; then + printf '\nPASS: %s reached a clickable xfce desktop with no manual steps\n' "$VM" + exit 0 + fi + end=$((end + 5)) + sleep 5 +done +fail "no libinput device in Xorg.0.log after ${X_TIMEOUT}s"