#!/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