# 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 || :
