# 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 </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 <&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 </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 </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 </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 </dev/null 2>&1 || : cfg bt2 </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 </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" </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 || :