aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorLena <lena@omega>2026-08-01 00:00:00 +0000
committerLena <lena@omega>2026-08-01 00:00:00 +0000
commitc235cb62beefd03087e7cfc7f7f55f0c7c6601c9 (patch)
tree2776f86d9226d55c78e3d735764fcd9ecbe5e08e /test
downloadvmm-c235cb62beefd03087e7cfc7f7f55f0c7c6601c9.tar.gz
Enter vmmHEADmaster
A QEMU/KVM virtual machine manager in one POSIX shell script. Each VM is a directory of plain files under $VMMDIR, configured by a hand-edited KEY=VALUE file that is parsed rather than sourced, so nothing in it can inject a raw QEMU argument. Control is a QMP FIFO pair inside that directory, guarded by its permissions alone; nothing binds a TCP port. The tests are TAP without a framework and boot real guests where the host allows.
Diffstat (limited to 'test')
-rwxr-xr-xtest184
1 files changed, 184 insertions, 0 deletions
diff --git a/test b/test
new file mode 100755
index 0000000..4a28df5
--- /dev/null
+++ b/test
@@ -0,0 +1,184 @@
+#!/bin/sh
+#
+# vmm tests. TAP output, no framework.
+#
+# ./test run every file in tests/
+# ./test config run tests/config only
+#
+# Every test runs against a throwaway VMMDIR under $TMPDIR. Tests that
+# need to boot a guest are skipped when /dev/kvm is not usable, so this
+# exits 0 on a machine without virtualisation.
+
+# shellcheck disable=SC2329 # the helpers below are called from tests/*
+set -eu
+umask 077
+
+ROOT=$(cd "$(dirname "$0")" && pwd)
+VMM=$ROOT/vmm
+[ -x "$VMM" ] || { printf 'Bail out! not executable: %s\n' "$VMM"; exit 1; }
+
+WORK=$(mktemp -d "${TMPDIR:-/tmp}/vmm-tests.XXXXXX")
+trap 'cleanup' EXIT
+trap 'exit 129' HUP
+trap 'exit 130' INT
+trap 'exit 143' TERM
+
+cleanup() {
+ trap - EXIT HUP INT TERM
+ for cmdline in /proc/[0-9]*/cmdline; do
+ p=${cmdline#/proc/}
+ p=${p%/cmdline}
+ line=
+ IFS= read -r line 2>/dev/null < "$cmdline" || :
+ case $line in
+ qemu-system-*"-pidfile$WORK/vms/"*"/pid" | \
+ /*/qemu-system-*"-pidfile$WORK/vms/"*"/pid")
+ kill -9 "$p" 2>/dev/null || :
+ ;;
+ esac
+ done
+ rm -rf "$WORK"
+}
+
+VMMDIR=$WORK/vms
+export VMMDIR
+SHUTDOWN_TIMEOUT=3
+export SHUTDOWN_TIMEOUT
+EDITOR=true
+export EDITOR
+
+N=0
+FAILED=0
+
+ok() {
+ N=$((N + 1))
+ printf 'ok %s - %s\n' "$N" "$1"
+}
+
+notok() {
+ N=$((N + 1))
+ FAILED=$((FAILED + 1))
+ printf 'not ok %s - %s\n' "$N" "$1"
+ shift
+ # Line by line: a diagnostic carrying vmm's output is usually several
+ # lines, and a bare one is not a comment. It is read as a result, so an
+ # error message mentioning 'ok' would invent a test that never ran.
+ for l in "$@"; do
+ printf '%s\n' "$l" | sed 's/^/# /'
+ done
+}
+
+# skip N REASON - N assertions this host cannot make
+skip() {
+ n=$1
+ shift
+ while [ "$n" -gt 0 ]; do
+ N=$((N + 1))
+ printf 'ok %s - # SKIP %s\n' "$N" "$*"
+ n=$((n - 1))
+ done
+}
+
+# holds DESC CMD... - require a command to succeed. 'test ! -e x' says
+# the negative, so there is only the one.
+holds() {
+ desc=$1
+ shift
+ if "$@" >/dev/null 2>&1; then
+ ok "$desc"
+ else
+ notok "$desc" "failed: $*"
+ fi
+}
+
+# exits CODE DESC ARGS... - run vmm and require an exact exit code
+exits() {
+ want=$1
+ desc=$2
+ shift 2
+ out=$("$VMM" "$@" 2>&1) && got=0 || got=$?
+ if [ "$got" = "$want" ]; then
+ ok "$desc"
+ else
+ notok "$desc" "wanted exit $want, got $got" "argv: $*" "output: $out"
+ fi
+}
+
+# outputs PATTERN DESC ARGS... - run vmm and require a matching line
+outputs() {
+ pat=$1
+ desc=$2
+ shift 2
+ out=$("$VMM" "$@" 2>&1) && got=0 || got=$?
+ if [ "$got" = 0 ] && printf '%s\n' "$out" | grep -q -- "$pat"; then
+ ok "$desc"
+ else
+ notok "$desc" "exit $got, wanted 0 and a match for: $pat" \
+ "argv: $*" "output: $out"
+ fi
+}
+
+# omits PATTERN DESC ARGS... - run vmm and require no matching line
+omits() {
+ pat=$1
+ desc=$2
+ shift 2
+ out=$("$VMM" "$@" 2>&1) && got=0 || got=$?
+ if [ "$got" != 0 ]; then
+ notok "$desc" "exit $got, wanted 0" "argv: $*" "output: $out"
+ elif printf '%s\n' "$out" | grep -q -- "$pat"; then
+ notok "$desc" "unwanted match for: $pat" "argv: $*" "output: $out"
+ else
+ ok "$desc"
+ fi
+}
+
+# refuses PATTERN DESC ARGS... - require a NON-ZERO exit AND a matching
+# message. The message alone is not enough: an error downgraded to a
+# warning prints the same words and still exits 0.
+refuses() {
+ pat=$1
+ desc=$2
+ shift 2
+ out=$("$VMM" "$@" 2>&1) && got=0 || got=$?
+ if [ "$got" = 0 ]; then
+ notok "$desc" "expected a refusal, got exit 0" "argv: $*" "output: $out"
+ return 0
+ fi
+ if printf '%s\n' "$out" | grep -q -- "$pat"; then
+ ok "$desc"
+ else
+ notok "$desc" "exit $got but no match for: $pat" "argv: $*" "output: $out"
+ fi
+}
+
+cfg() {
+ cat > "$VMMDIR/$1/config"
+}
+
+# The list is an order, cheapest checks first. The loop refuses a file
+# it does not name, which would otherwise never run and never be missed.
+if [ $# -eq 0 ]; then
+ set -- static usage create config argv arch list clone lock env \
+ boot net console viewer
+ for f in "$ROOT"/tests/*; do
+ case " $* " in
+ *" ${f##*/} "*) continue ;;
+ esac
+ printf 'Bail out! tests/%s is in no run list\n' "${f##*/}"
+ exit 1
+ done
+fi
+
+for t in "$@"; do
+ if [ ! -f "$ROOT/tests/$t" ]; then
+ printf 'Bail out! no such test: %s\n' "$t"
+ exit 1
+ fi
+ # shellcheck source=/dev/null
+ . "$ROOT/tests/$t"
+done
+
+printf '1..%s\n' "$N"
+[ "$FAILED" = 0 ] || exit 1
+exit 0