diff options
Diffstat (limited to 'tests/boot')
| -rw-r--r-- | tests/boot | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/tests/boot b/tests/boot new file mode 100644 index 0000000..2f778eb --- /dev/null +++ b/tests/boot @@ -0,0 +1,250 @@ +# A real guest, booted for real. Skipped without a usable /dev/kvm, so the +# suite still exits 0 on a machine with no virtualisation. + +if [ ! -r /dev/kvm ] || [ ! -w /dev/kvm ]; then + skip 1 "no usable /dev/kvm, skipping the boot tests" + return 0 +fi + +"$VMM" create bt1 64M >/dev/null 2>&1 +cfg bt1 <<EOF +MEM=256 +CPUS=1 +EOF + +exits 0 "start boots a guest" start bt1 +PID=$(cat "$VMMDIR/bt1/pid" 2>/dev/null || echo) +if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then + ok "qemu is running" +else + notok "qemu is running" "$(tail -n 5 "$VMMDIR/bt1/stderr" 2>/dev/null)" +fi + +holds "qemu's stdout lands in a file" test -f "$VMMDIR/bt1/stdout" +holds "qemu's stderr lands in another" test -f "$VMMDIR/bt1/stderr" +outputs '==> .*stderr <==' "logs shows every stream" logs bt1 + +exits 0 "status of a running VM exits 0" status bt1 +outputs 'STATE=running' "status reports running" status bt1 +outputs 'running' "list reports running" list + +# The lock must be free after start, or every later verb reports busy. +holds "start releases its lock" flock -n "$VMMDIR/bt1" true + +exits 0 "start is idempotent" start bt1 +outputs 'already running' "a second start says so" start bt1 + +# restart swallows exit 5 from a stop that had to force the guest, and +# nothing else. A guest with no OS ignores ACPI, so this is that path, +# and it must come back as a different process. +old=$(cat "$VMMDIR/bt1/pid" 2>/dev/null || echo) +exits 0 "restart survives a stop that had to force" restart bt1 +new=$(cat "$VMMDIR/bt1/pid" 2>/dev/null || echo) +if [ -n "$new" ] && [ "$new" != "$old" ]; then + ok "restart comes back as a new process" +else + notok "restart comes back as a new process" "was [$old] now [$new]" +fi +outputs 'STATE=running' "the restarted guest is running" status bt1 + +# A restart that cannot start again must not report success: the config +# is broken under the guest, so stop works and start refuses. +cp "$VMMDIR/bt1/config" "$WORK/bt1.config" +cfg bt1 <<EOF +NOSUCHKEY=1 +EOF +refuses 'unknown key' "restart fails loudly when it cannot start again" restart bt1 +exits 3 "and leaves the guest stopped, not half restarted" status bt1 +cp "$WORK/bt1.config" "$VMMDIR/bt1/config" +exits 0 "start again for the tests below" start bt1 + +outputs 'running' "the monitor answers" monitor bt1 info status + +# Commands read from stdin: the count says every one was sent. +out=$(printf 'info status\ninfo version\n' | "$VMM" monitor bt1 2>&1) && got=0 || got=$? +n=$(printf '%s\n' "$out" | grep -c '"return"' || :) +if [ "$got" = 0 ] && [ "$n" = 2 ]; then + ok "monitor answers every command it reads from stdin" +else + notok "monitor answers every command it reads from stdin" \ + "exit $got, $n replies" "$out" +fi + +# A final line with no newline is a command too: dropped, it would be +# exit 0 and nothing done. +out=$(printf 'info status' | "$VMM" monitor bt1 2>&1) && got=0 || got=$? +if [ "$got" = 0 ] && printf '%s\n' "$out" | grep -q '"return"'; then + ok "monitor sends a final line with no newline" +else + notok "monitor sends a final line with no newline" "exit $got: $out" +fi + +# The command is interpolated into a JSON string. This payload closes +# that string and the object around it, so an unescaped one reaches +# QEMU's parser as a second complete command and stops the guest. An +# unescaped backslash instead makes the object unparseable, and QEMU +# refuses it with an error carrying no id, so no reply arrives at all. +outputs 'unknown command' "a quote cannot close the monitor's own object" \ + monitor bt1 'x"}},{"execute":"quit' +exits 0 "and the quit it carried never ran" status bt1 +outputs '"return"' "a backslash still leaves the monitor a parseable object" \ + monitor bt1 'info \ block' + +exits 5 "stop escalates and exits 5 on a guest with no OS" stop bt1 +holds "stop removes the pidfile once dead" test ! -e "$VMMDIR/bt1/pid" +exits 0 "stop is idempotent" stop bt1 + +# SNAPSHOT=yes must build a real overlay, never rely on -snapshot. +cp "$VMMDIR/bt1/disk.qcow2" "$WORK/bt1-before-snapshot.qcow2" +cfg bt1 <<EOF +MEM=256 +CPUS=1 +SNAPSHOT=yes +EOF +exits 0 "start with SNAPSHOT=yes" start bt1 +holds "the overlay is unlinked once qemu holds it" \ + test ! -e "$VMMDIR/bt1/ephemeral.qcow2" +# Unlinked, but open: qemu still serves the guest from it, and says so. +outputs 'ephemeral.qcow2' "the guest runs on the overlay, not the disk" \ + monitor bt1 info block +outputs '"return": ""' "a snapshot accepts guest disk writes" monitor bt1 \ + 'qemu-io -d /machine/peripheral/blk0/virtio-backend "write -P 0xa5 1048576 4096"' +exits 0 "the guest survives losing the overlay's name" status bt1 + +"$VMM" kill bt1 >/dev/null 2>&1 || : +holds "snapshot writes do not change the durable disk" qemu-img compare -q \ + "$WORK/bt1-before-snapshot.qcow2" "$VMMDIR/bt1/disk.qcow2" +exits 0 "kill is idempotent" kill bt1 + +# The same write without SNAPSHOT must survive a complete QEMU restart. +cfg bt1 <<EOF +MEM=256 +CPUS=1 +EOF +exits 0 "start without snapshot mode" start bt1 +outputs '"return": ""' "an ordinary guest accepts a disk write" monitor bt1 \ + 'qemu-io -d /machine/peripheral/blk0/virtio-backend "write -P 0x5a 2097152 4096"' +"$VMM" kill bt1 >/dev/null 2>&1 || : +exits 0 "restart after a durable disk write" start bt1 +outputs '"return": ""' "an ordinary disk write survives restart" monitor bt1 \ + 'qemu-io -d /machine/peripheral/blk0/virtio-backend "read -P 0x5a 2097152 4096"' +"$VMM" kill bt1 >/dev/null 2>&1 || : + +# A running VM must be undeletable and unclonable. +cfg bt1 <<EOF +MEM=256 +CPUS=1 +EOF +"$VMM" start bt1 >/dev/null 2>&1 || : +exits 4 "delete refuses a running VM" delete -f bt1 +exits 4 "clone refuses a running source" clone bt1 bt9 +"$VMM" kill bt1 >/dev/null 2>&1 || : +exits 0 "delete -f removes it once stopped" delete -f bt1 + +# status answers about the process, so an unparseable config must not stop +# it, and a config edited under a live guest must not make it lie. +"$VMM" create bt2 64M >/dev/null 2>&1 +cfg bt2 <<EOF +MEM=256 +CPUS=1 +EOF +"$VMM" start bt2 >/dev/null 2>&1 || : +cfg bt2 <<EOF +THIS IS NOT A CONFIG +EOF +outputs 'STATE=running' "status ignores a broken config" status bt2 +omits '^MEM=' "status reports no config values at all" status bt2 + +# A live guest must be visible under every spelling of VMMDIR, or the +# verbs that clean up delete the state of a running guest. +state=$(VMMDIR="$VMMDIR/" "$VMM" status bt2 2>/dev/null | sed -n 's/^STATE=//p') +if [ "$state" = running ]; then + ok "a running guest is visible through a trailing slash" +else + notok "a running guest is visible through a trailing slash" "got [$state]" +fi + +# vm_pid coming back empty is not proof of death. When some other live +# qemu owns the pidfile, the cleanup paths must leave the directory alone. +P2=$(cat "$VMMDIR/bt2/pid" 2>/dev/null || echo) +mkdir -p "$VMMDIR/bt3" +cp "$VMMDIR/bt2/config" "$VMMDIR/bt3/config" +printf '%s\n' "$P2" > "$VMMDIR/bt3/pid" +exits 4 "a different live qemu is reported as unknown state" stop bt3 +holds "state owned by an unrecognised live qemu is left alone" \ + test -f "$VMMDIR/bt3/pid" +state=$("$VMM" list | awk '$1 == "bt3" { print $2 }') +if [ "$state" = unknown ]; then + ok "list exposes unrecognised live qemu state" +else + notok "list exposes unrecognised live qemu state" "got [$state]" +fi +rm -rf "$VMMDIR/bt3" + +# A wedged monitor must be reported, not silently reported as success. +P=$(cat "$VMMDIR/bt2/pid" 2>/dev/null || echo) +if [ -n "$P" ]; then + kill -STOP "$P" 2>/dev/null || : + out=$(MONITOR_TIMEOUT=1 "$VMM" monitor bt2 info status 2>&1) && got=0 || got=$? + kill -CONT "$P" 2>/dev/null || : + if [ "$got" != 0 ] && printf '%s' "$out" | grep -q 'no reply'; then + ok "a silent monitor is an error, not exit 0" + else + notok "a silent monitor is an error, not exit 0" "exit $got: $out" + fi + # The shell reports the job timeout kills unless it is reaped where + # nobody can see it, and that report is not vmm's to print. + if printf '%s' "$out" | grep -q 'Terminated'; then + notok "a timed out call prints nothing but vmm's own error" "$out" + else + ok "a timed out call prints nothing but vmm's own error" + fi +else + skip 2 "bt2 did not start, cannot test the wedged monitor" +fi + +"$VMM" kill bt2 >/dev/null 2>&1 || : +exits 3 "status of the killed guest exits 3" status bt2 + +# GRAPHICS=vnc listens on a unix socket in the VM directory, and status +# reports the socket that is really there rather than what the config says. +"$VMM" create bt4 64M >/dev/null 2>&1 +cfg bt4 <<EOF +MEM=256 +CPUS=1 +GRAPHICS=vnc +EOF +if "$VMM" start bt4 >/dev/null 2>&1; then + holds "GRAPHICS=vnc creates the socket" test -S "$VMMDIR/bt4/vnc.sock" + outputs "VNC=$VMMDIR/bt4/vnc.sock" "status names the VNC socket" status bt4 + "$VMM" kill bt4 >/dev/null 2>&1 || : + holds "the socket is removed once stopped" test ! -e "$VMMDIR/bt4/vnc.sock" + out=$("$VMM" status bt4 2>&1) && got=0 || got=$? + if [ "$got" = 3 ] && ! printf '%s\n' "$out" | grep -q '^VNC='; then + ok "a stopped VM has no VNC line" + else + notok "a stopped VM has no VNC line" "exit $got: $out" + fi +else + skip 4 "this qemu cannot do GRAPHICS=vnc" +fi + +# One directory per VM means two guests are independent of each other: +# each names its own process, and killing one leaves the other alone. +"$VMM" create bt5 64M >/dev/null 2>&1 +"$VMM" create bt6 64M >/dev/null 2>&1 +for v in bt5 bt6; do + cfg "$v" <<CFG +MEM=256 +CPUS=1 +CFG + "$VMM" start "$v" >/dev/null 2>&1 || : +done +p5=$(cat "$VMMDIR/bt5/pid" 2>/dev/null || echo) +p6=$(cat "$VMMDIR/bt6/pid" 2>/dev/null || echo) +holds "two guests run at once, as two processes" \ + test -n "$p5" -a -n "$p6" -a "$p5" != "$p6" +"$VMM" kill bt5 >/dev/null 2>&1 || : +exits 3 "killing one stops that one" status bt5 +exits 0 "and leaves the other running" status bt6 +"$VMM" kill bt6 >/dev/null 2>&1 || : |