diff options
| author | Lena <lena@omega> | 2026-08-01 00:00:00 +0000 |
|---|---|---|
| committer | Lena <lena@omega> | 2026-08-01 00:00:00 +0000 |
| commit | c235cb62beefd03087e7cfc7f7f55f0c7c6601c9 (patch) | |
| tree | 2776f86d9226d55c78e3d735764fcd9ecbe5e08e /test | |
| download | vmm-master.tar.gz | |
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-x | test | 184 |
1 files changed, 184 insertions, 0 deletions
@@ -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 |