@@ -1,0 +1,502 @@
#!/usr/bin/env bash
# Self-test of the Linux test guest (not of Sunshine): boots it the way the E2E harness does and
# checks the ready marker, the evdev log for input injected through QEMU's D-Bus display and QMP,
# the framebuffer console, and the guest's sine tone.
#
# Usage: selftest.sh
#
# Cases (SELFTEST_CASES, space separated, default: all of them):
# harness run.sh: -display dbus on a private bus, -audiodev dbus, intel-hda + hda-output,
# virtio-vga, virtio-tablet-pci, virtio-keyboard-pci. Checks D-Bus Keyboard/Mouse
# (absolute) input, QMP input-send-event, a QMP screendump of the console, and
# the D-Bus audio tone if run_vm.sh exports the Audio object.
# ps2 no virtio input devices and vmport=off, so the guest mouse is a relative PS/2
# mouse: D-Bus Keyboard and Mouse RelMotion/Press. Serial through -chardev.
# dbus-audio the harness devices with -display dbus,audiodev=snd0, which exports
# /org/qemu/Display1/Audio: the tone through a D-Bus AudioOutListener (python3-gi).
# audio-hda intel-hda + hda-output into -audiodev wav: dominant frequency, level, no gaps.
# audio-virtio the same with virtio-sound-pci.
#
# Environment:
# QEMU QEMU binary (default: qemu-system-x86_64)
# VM_ACCEL kvm or tcg (default: kvm when /dev/kvm is usable)
# READY_TIMEOUT seconds to wait for SQGUEST READY (default: 30 with KVM, 300 with TCG)
# GUEST_DIR prebuilt build.sh output (default: build or reuse the cached one)
# SELFTEST_KEEP 1 keeps the work directory
#
# Host tools: dbus-daemon, gdbus, python3 (python3-gi for the D-Bus audio check).
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
qemu="${QEMU:-qemu-system-x86_64}"
if [[ -z "${VM_ACCEL:-}" ]]; then
if [[ -r /dev/kvm && -w /dev/kvm ]]; then
VM_ACCEL=kvm
else
VM_ACCEL=tcg
fi
fi
export VM_ACCEL QEMU="${qemu}"
if [[ "${VM_ACCEL}" == kvm ]]; then
ready_timeout="${READY_TIMEOUT:-30}"
event_timeout=5
max_gaps=0
else
ready_timeout="${READY_TIMEOUT:-300}"
event_timeout=30
# a TCG guest on a busy host can underrun; still require the frequency and level
max_gaps=1000000
fi
cases="${SELFTEST_CASES:-harness ps2 dbus-audio audio-hda audio-virtio}"
GUEST_DIR="${GUEST_DIR:-$("${script_dir}/build.sh")}"
export GUEST_DIR
cmdline="$(cat "${GUEST_DIR}/cmdline")"
work="$(mktemp -d "${TMPDIR:-/tmp}/sq-guest-selftest.XXXXXX")"
failures=0
stop_vm() {
local dir="$1" pidfile pid
for pidfile in "${dir}/qemu.pid" "${dir}/dbus.pid"; do
[[ -f "${pidfile}" ]] || continue
pid="$(cat "${pidfile}")"
kill "${pid}" 2> /dev/null || true
for _ in $(seq 1 50); do
kill -0 "${pid}" 2> /dev/null || break
sleep 0.1
done
kill -9 "${pid}" 2> /dev/null || true
rm -f "${pidfile}"
done
}
cleanup() {
for dir in "${work}"/*/; do
[[ -d "${dir}" ]] && stop_vm "${dir%/}"
done
if [[ ${failures} == 0 && "${SELFTEST_KEEP:-0}" != 1 ]]; then
rm -rf "${work}"
else
echo "selftest: work directory kept at ${work}" >&2
fi
}
trap cleanup EXIT
pass() {
echo "selftest: PASS ${current}: $*"
}
fail() {
echo "selftest: FAIL ${current}: $*" >&2
failures=$((failures + 1))
}
# expect <log> <extended regex> <description>: wait for a matching serial line
expect() {
local log="$1" regex="$2" what="$3" line deadline=$((SECONDS + event_timeout))
while ((SECONDS < deadline)); do
if line="$(tr -d '\r' < "${log}" | grep -a -m1 -E "${regex}")"; then
pass "${what}: ${line}"
return 0
fi
sleep 0.1
done
fail "${what}: no line matching /${regex}/"
}
wait_ready() {
local log="$1" start="$2" line
if line="$("${script_dir}/wait_ready.sh" "${log}" "${ready_timeout}")"; then
pass "ready after $((SECONDS - start)) s (host) / guest ${line##*uptime=} s: ${line}"
return 0
fi
fail "no ready marker within ${ready_timeout} s"
return 1
}
# console <bus> <method> <args...>: call a method on /org/qemu/Display1/Console_0
console() {
local bus="$1" method="$2"
shift 2
gdbus call --address "${bus}" --dest org.qemu --object-path /org/qemu/Display1/Console_0 \
--method "${method}" "$@" > /dev/null || fail "D-Bus ${method} $*"
}
console_prop() {
local bus="$1" iface="$2" prop="$3"
gdbus call --address "${bus}" --dest org.qemu --object-path /org/qemu/Display1/Console_0 \
--method org.freedesktop.DBus.Properties.Get "${iface}" "${prop}" | sed -E 's/^\(<(uint32 |)(.*)>,\)$/\2/'
}
# qmp <socket> <json command>...: run QMP commands, print their replies
qmp() {
python3 - "$@" << 'EOF'
import json, socket, sys
sock = socket.socket(socket.AF_UNIX)
sock.connect(sys.argv[1])
f = sock.makefile("rw")
json.loads(f.readline())
for command in ['{"execute": "qmp_capabilities"}'] + sys.argv[2:]:
f.write(command + "\n")
f.flush()
while True:
reply = json.loads(f.readline())
if "return" in reply or "error" in reply:
break
if "error" in reply:
sys.exit("qmp error: %s for %s" % (reply["error"], command))
print(json.dumps(reply["return"]))
EOF
}
qmp_key() {
echo "{\"execute\": \"input-send-event\", \"arguments\": {\"events\": [{\"type\": \"key\", \"data\": {\"down\": $2, \"key\": {\"type\": \"qcode\", \"data\": \"$1\"}}}]}}"
}
# tone_check <wav|dbus> <file|bus> <seconds> <expected Hz>: dominant frequency, level and gaps
tone_check() {
python3 - "$@" "${max_gaps}" << 'EOF'
import math, struct, sys, time
mode, source, seconds, expected, max_gaps = sys.argv[1], sys.argv[2], float(sys.argv[3]), float(sys.argv[4]), int(sys.argv[5])
def analyse(pcm, rate, channels, what):
frames = len(pcm) // (2 * channels)
samples = struct.unpack("<%dh" % (frames * channels), pcm[: frames * 2 * channels])
left = samples[0::channels]
block = rate // 100
rms = [math.sqrt(sum(x * x for x in left[i : i + block]) / block) for i in range(0, frames - block + 1, block)]
start = next((i for i, v in enumerate(rms) if v > 1000), None)
if start is None:
sys.exit("%s: no tone in %.2f s of audio" % (what, frames / rate))
active = rms[start:]
gaps = sum(1 for v in active if v < 1000)
window = left[start * block : start * block + rate]
def goertzel(freq):
k = 2 * math.cos(2 * math.pi * freq / rate)
s1 = s2 = 0.0
for x in window:
s1, s2 = x + k * s1 - s2, s1
return math.sqrt(max(s1 * s1 + s2 * s2 - k * s1 * s2, 0))
dominant = max(range(50, rate // 2, 10), key=goertzel)
level = sorted(active)[len(active) // 2]
print("%s: %d Hz stereo=%d, %.2f s, tone from %.2f s, dominant %d Hz, median rms %.0f, silent 10 ms blocks after start: %d"
% (what, rate, channels, frames / rate, start / 100, dominant, level, gaps))
if abs(dominant - expected) > 10 or gaps > max_gaps or level < 3000:
sys.exit("%s: tone check failed" % what)
if mode == "wav":
data = open(source, "rb").read()
channels, rate, bits = struct.unpack_from("<H", data, 22)[0], struct.unpack_from("<I", data, 24)[0], struct.unpack_from("<H", data, 34)[0]
if bits != 16:
sys.exit("unexpected wav format: %d bits" % bits)
# QEMU fixes the RIFF sizes only on a clean exit; read everything after the 44-byte header
analyse(data[44:], rate, channels, "wav")
sys.exit(0)
import gi
gi.require_version("Gio", "2.0")
from gi.repository import Gio, GLib
XML = """<node><interface name="org.qemu.Display1.AudioOutListener">
<method name="Init"><arg name="id" type="t" direction="in"/><arg name="bits" type="y" direction="in"/>
<arg name="is_signed" type="b" direction="in"/><arg name="is_float" type="b" direction="in"/>
<arg name="freq" type="u" direction="in"/><arg name="nchannels" type="y" direction="in"/>
<arg name="bytes_per_frame" type="u" direction="in"/><arg name="bytes_per_second" type="u" direction="in"/>
<arg name="be" type="b" direction="in"/></method>
<method name="Fini"><arg name="id" type="t" direction="in"/></method>
<method name="SetEnabled"><arg name="id" type="t" direction="in"/><arg name="enabled" type="b" direction="in"/></method>
<method name="SetVolume"><arg name="id" type="t" direction="in"/><arg name="mute" type="b" direction="in"/><arg name="volume" type="ay" direction="in"/></method>
<method name="Write"><arg name="id" type="t" direction="in"/><arg name="data" type="ay" direction="in"/></method>
<property name="Interfaces" type="as" access="read"/>
</interface></node>"""
import socket
bus = Gio.DBusConnection.new_for_address_sync(
source, Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT | Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION, None, None)
ours, theirs = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
fds = Gio.UnixFDList.new()
index = fds.append(theirs.fileno())
bus.call_with_unix_fd_list_sync("org.qemu", "/org/qemu/Display1/Audio", "org.qemu.Display1.Audio", "RegisterOutListener",
GLib.Variant("(h)", (index,)), None, Gio.DBusCallFlags.NONE, 5000, fds, None)
theirs.close()
stream = Gio.Socket.new_from_fd(ours.detach()).connection_factory_create_connection()
peer = Gio.DBusConnection.new_sync(
stream, None, Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT | Gio.DBusConnectionFlags.DELAY_MESSAGE_PROCESSING, None, None)
streams = {}
first_write = [None]
def on_call(connection, sender, path, interface, method, params, invocation):
stream_id = params.get_child_value(0).get_uint64()
if method == "Init":
_, bits, signed, is_float, freq, nchannels, bpf, bps, be = params.unpack()
streams[stream_id] = {"bits": bits, "signed": signed, "float": is_float, "freq": freq, "channels": nchannels,
"be": be, "enabled": False, "data": bytearray()}
print("dbus: Init id=%d bits=%d signed=%s float=%s freq=%d channels=%d bytes_per_second=%d be=%s"
% (stream_id, bits, signed, is_float, freq, nchannels, bps, be))
elif method == "SetEnabled":
streams.setdefault(stream_id, {"data": bytearray()})["enabled"] = params.get_child_value(1).get_boolean()
elif method == "Write":
if first_write[0] is None:
first_write[0] = time.monotonic()
streams.setdefault(stream_id, {"data": bytearray()})["data"] += params.get_child_value(1).get_data_as_bytes().get_data()
invocation.return_value(None)
def on_get(connection, sender, path, interface, name):
return GLib.Variant("as", [])
peer.register_object(
"/org/qemu/Display1/AudioOutListener", Gio.DBusNodeInfo.new_for_xml(XML).interfaces[0], on_call, on_get, None)
peer.start_message_processing()
loop = GLib.MainLoop()
GLib.timeout_add(int(seconds * 1000), loop.quit)
loop.run()
active = [s for s in streams.values() if len(s["data"])]
if not active:
sys.exit("dbus: no Write calls within %.1f s (streams: %s)" % (seconds, {k: v.get("enabled") for k, v in streams.items()}))
s = max(active, key=lambda s: len(s["data"]))
if s.get("bits") != 16 or s.get("float") or s.get("be"):
sys.exit("dbus: unexpected PCM format %s" % {k: v for k, v in s.items() if k != "data"})
elapsed = time.monotonic() - first_write[0]
print("dbus: %d bytes in %.2f s since the first Write (%.0f bytes/s)" % (len(s["data"]), elapsed, len(s["data"]) / elapsed))
analyse(bytes(s["data"]), s["freq"], s["channels"], "dbus")
EOF
}
# boot_direct <dir> <qemu args...>: private dbus-daemon plus QEMU with the guest kernel (no run_vm.sh)
boot_direct() {
local dir="$1"
shift
mkdir -p "${dir}"
dbus-daemon --session --nofork --nopidfile --address="unix:path=${dir}/bus.sock" > "${dir}/dbus.log" 2>&1 &
echo $! > "${dir}/dbus.pid"
for _ in $(seq 1 100); do
[[ -S "${dir}/bus.sock" ]] && break
sleep 0.05
done
"${qemu}" -accel "${VM_ACCEL}" -m 128 -nodefaults \
-kernel "${GUEST_DIR}/vmlinuz" -initrd "${GUEST_DIR}/initramfs.gz" \
-qmp unix:"${dir}/qmp.sock",server=on,wait=off \
"$@" > "${dir}/qemu.log" 2>&1 &
echo $! > "${dir}/qemu.pid"
}
wait_bus_name() {
local bus="$1"
for _ in $(seq 1 300); do
if gdbus call --address "${bus}" --dest org.freedesktop.DBus --object-path /org/freedesktop/DBus \
--method org.freedesktop.DBus.NameHasOwner org.qemu 2> /dev/null | grep -q true; then
return 0
fi
sleep 0.1
done
return 1
}
case_harness() {
local dir="${work}/harness" bus log start width height is_abs x y
start=${SECONDS}
if ! bus="$("${script_dir}/run.sh" "${dir}")"; then
fail "run.sh failed"
return
fi
log="${dir}/serial.log"
wait_ready "${log}" "${start}" || return
expect "${log}" '^SQGUEST DEVICE .*name=QEMU Virtio Tablet$' "virtio tablet present"
expect "${log}" '^SQGUEST DEVICE .*name=QEMU Virtio Keyboard$' "virtio keyboard present"
expect "${log}" '^SQGUEST TONE running ' "tone running"
width="$(console_prop "${bus}" org.qemu.Display1.Console Width)"
height="$(console_prop "${bus}" org.qemu.Display1.Console Height)"
is_abs="$(console_prop "${bus}" org.qemu.Display1.Mouse IsAbsolute)"
pass "console ${width}x${height}, IsAbsolute=${is_abs}"
# "a", then Shift+"A", through org.qemu.Display1.Keyboard (QEMU qnum codes: a=0x1e, lshift=0x2a)
console "${bus}" org.qemu.Display1.Keyboard.Press 30
console "${bus}" org.qemu.Display1.Keyboard.Release 30
expect "${log}" '^SQGUEST EVDEV dev=event[0-9]+ time=[0-9.]+ type=1 code=30 value=1 name=' "D-Bus key a down"
expect "${log}" '^SQGUEST EVDEV dev=event[0-9]+ time=[0-9.]+ type=1 code=30 value=0 name=' "D-Bus key a up"
console "${bus}" org.qemu.Display1.Keyboard.Press 42
console "${bus}" org.qemu.Display1.Keyboard.Press 30
console "${bus}" org.qemu.Display1.Keyboard.Release 30
console "${bus}" org.qemu.Display1.Keyboard.Release 42
expect "${log}" '^SQGUEST EVDEV .* type=1 code=42 value=1 name=' "D-Bus left shift down"
expect "${log}" '^SQGUEST EVDEV .* type=1 code=42 value=0 name=' "D-Bus left shift up"
if [[ "$(tr -d '\r' < "${log}" | grep -a -c -E '^SQGUEST EVDEV .* type=1 code=30 value=1 name=')" == 2 ]]; then
pass "second a press logged"
else
fail "expected two KEY_A presses"
fi
# the logger's last event of a burst (SYN_REPORT) must not lag behind
sleep 0.5
if tr -d '\r' < "${log}" | grep -a -E '^SQGUEST EVDEV ' | tail -n 1 | grep -q ' type=0 code=0 value=0 '; then
pass "trailing SYN_REPORT flushed"
else
fail "last logged event isn't a SYN_REPORT: $(tr -d '\r' < "${log}" | grep -a '^SQGUEST EVDEV ' | tail -n 1)"
fi
# absolute mouse move, click and wheel through org.qemu.Display1.Mouse (tablet range 0..32767)
x=$((width / 2))
y=$((height / 4))
console "${bus}" org.qemu.Display1.Mouse.SetAbsPosition "${x}" "${y}"
console "${bus}" org.qemu.Display1.Mouse.Press 0
console "${bus}" org.qemu.Display1.Mouse.Release 0
console "${bus}" org.qemu.Display1.Mouse.Press 4
console "${bus}" org.qemu.Display1.Mouse.Release 4
expect "${log}" "^SQGUEST EVDEV .* type=3 code=0 value=$((x * 32767 / width)) name=QEMU Virtio Tablet$" "abs x ${x}"
expect "${log}" "^SQGUEST EVDEV .* type=3 code=1 value=$((y * 32767 / height)) name=QEMU Virtio Tablet$" "abs y ${y}"
expect "${log}" '^SQGUEST EVDEV .* type=1 code=272 value=1 name=QEMU Virtio Tablet$' "left button down"
expect "${log}" '^SQGUEST EVDEV .* type=1 code=272 value=0 name=QEMU Virtio Tablet$' "left button up"
expect "${log}" '^SQGUEST EVDEV .* type=2 code=8 value=-1 name=QEMU Virtio Tablet$' "wheel down"
# QMP input-send-event as a second injection path
if qmp "${dir}/qmp.sock" "$(qmp_key b true)" "$(qmp_key b false)" > /dev/null; then
expect "${log}" '^SQGUEST EVDEV .* type=1 code=48 value=1 name=' "QMP key b down"
expect "${log}" '^SQGUEST EVDEV .* type=1 code=48 value=0 name=' "QMP key b up"
else
fail "QMP input-send-event"
fi
# the framebuffer console shows text: the screendump is neither empty nor a single color
if qmp "${dir}/qmp.sock" "{\"execute\": \"screendump\", \"arguments\": {\"filename\": \"${dir}/screen.ppm\"}}" > /dev/null &&
python3 - "${dir}/screen.ppm" << 'EOF'; then
import sys
data = open(sys.argv[1], "rb").read()
parts = data.split(b"\n", 3)
width, height = map(int, parts[1].split())
pixels = parts[3]
lit = sum(1 for i in range(0, len(pixels), 3) if pixels[i] + pixels[i + 1] + pixels[i + 2] > 200)
print("screendump %dx%d, %d lit pixels" % (width, height, lit))
sys.exit(0 if 200 < lit < width * height // 2 else 1)
EOF
pass "console text visible in screendump"
else
fail "console screendump has no text"
fi
if gdbus introspect --address "${bus}" --dest org.qemu --object-path /org/qemu/Display1/Audio 2> /dev/null |
grep -q 'interface org.qemu.Display1.Audio '; then
dbus_audio "${bus}"
else
echo "selftest: NOTE ${current}: run_vm.sh doesn't export /org/qemu/Display1/Audio (QEMU needs" \
"-display dbus,...,audiodev=snd0); D-Bus audio is checked in the dbus-audio case"
fi
stop_vm "${dir}"
}
dbus_audio() {
if ! python3 -c 'import gi; gi.require_version("Gio", "2.0")' 2> /dev/null; then
echo "selftest: SKIP ${current}: D-Bus audio check (python3-gi not installed)"
return
fi
if tone_check dbus "$1" 3 1000 | sed 's/^/selftest: /'; then
pass "D-Bus AudioOutListener receives a continuous 1000 Hz tone"
else
fail "D-Bus audio"
fi
}
# case_dbus_audio: the harness devices, with the audiodev attached to the D-Bus display
case_dbus_audio() {
local dir="${work}/dbus-audio" bus start
start=${SECONDS}
mkdir -p "${dir}"
bus="unix:path=${dir}/bus.sock"
boot_direct "${dir}" -device virtio-vga -display dbus,addr="${bus}",audiodev=snd0 \
-audiodev dbus,id=snd0 -device intel-hda -device hda-output,audiodev=snd0 \
-device virtio-tablet-pci -device virtio-keyboard-pci \
-serial file:"${dir}/serial.log" -append "${cmdline}"
if ! wait_bus_name "${bus}"; then
fail "QEMU didn't start: $(cat "${dir}/qemu.log")"
return
fi
wait_ready "${dir}/serial.log" "${start}" || return
dbus_audio "${bus}"
stop_vm "${dir}"
}
case_ps2() {
local dir="${work}/ps2" bus log start is_abs
start=${SECONDS}
mkdir -p "${dir}"
bus="unix:path=${dir}/bus.sock"
log="${dir}/serial.log"
boot_direct "${dir}" -machine pc,vmport=off -device virtio-vga -display dbus,addr="${bus}" \
-audiodev none,id=snd0 -device intel-hda -device hda-output,audiodev=snd0 \
-chardev file,id=serial0,path="${log}" -serial chardev:serial0 \
-append "${cmdline} sq.tone=off"
if ! wait_bus_name "${bus}"; then
fail "QEMU didn't start: $(cat "${dir}/qemu.log")"
return
fi
wait_ready "${log}" "${start}" || return
expect "${log}" '^SQGUEST DEVICE .*name=AT Translated Set 2 keyboard$' "PS/2 keyboard present"
expect "${log}" '^SQGUEST DEVICE .* rel=[0-9a-f]+ abs=0 name=ImExPS/2 Generic Explorer Mouse$' "PS/2 mouse present"
expect "${log}" '^SQGUEST READY .* tone=off ' "tone disabled by sq.tone=off"
is_abs="$(console_prop "${bus}" org.qemu.Display1.Mouse IsAbsolute)"
if [[ "${is_abs}" == false ]]; then
pass "IsAbsolute=false"
else
fail "IsAbsolute=${is_abs}, expected false"
fi
console "${bus}" org.qemu.Display1.Keyboard.Press 30
console "${bus}" org.qemu.Display1.Keyboard.Release 30
expect "${log}" '^SQGUEST EVDEV .* type=1 code=30 value=1 name=AT Translated Set 2 keyboard$' "PS/2 key a down"
expect "${log}" '^SQGUEST EVDEV .* type=1 code=30 value=0 name=AT Translated Set 2 keyboard$' "PS/2 key a up"
console "${bus}" org.qemu.Display1.Mouse.RelMotion 10 -- -5
expect "${log}" '^SQGUEST EVDEV .* type=2 code=0 value=10 name=ImExPS/2 Generic Explorer Mouse$' "rel x +10"
expect "${log}" '^SQGUEST EVDEV .* type=2 code=1 value=-5 name=ImExPS/2 Generic Explorer Mouse$' "rel y -5"
console "${bus}" org.qemu.Display1.Mouse.Press 2
console "${bus}" org.qemu.Display1.Mouse.Release 2
expect "${log}" '^SQGUEST EVDEV .* type=1 code=273 value=1 name=ImExPS/2 Generic Explorer Mouse$' "right button down"
expect "${log}" '^SQGUEST EVDEV .* type=1 code=273 value=0 name=ImExPS/2 Generic Explorer Mouse$' "right button up"
stop_vm "${dir}"
}
# case_audio <name> <audio device args...>
case_audio() {
local dir="${work}/$1" start
shift
start=${SECONDS}
mkdir -p "${dir}"
boot_direct "${dir}" -device virtio-vga -display none \
-audiodev wav,id=snd0,path="${dir}/tone.wav" "$@" \
-serial file:"${dir}/serial.log" -append "${cmdline}"
wait_ready "${dir}/serial.log" "${start}" || return
expect "${dir}/serial.log" '^SQGUEST TONE running method=sine freq=1000 ' "tone running"
sleep 3
qmp "${dir}/qmp.sock" '{"execute": "quit"}' > /dev/null 2>&1 || true
for _ in $(seq 1 50); do
kill -0 "$(cat "${dir}/qemu.pid")" 2> /dev/null || break
sleep 0.1
done
if tone_check wav "${dir}/tone.wav" 0 1000 | sed 's/^/selftest: /'; then
pass "continuous 1000 Hz tone in the wav capture"
else
fail "wav tone check"
fi
stop_vm "${dir}"
}
echo "selftest: QEMU $("${qemu}" --version | head -n 1), accel ${VM_ACCEL}, guest ${GUEST_DIR}"
for current in ${cases}; do
case "${current}" in
harness) case_harness ;;
ps2) case_ps2 ;;
dbus-audio) case_dbus_audio ;;
audio-hda) case_audio audio-hda -device intel-hda -device hda-output,audiodev=snd0 ;;
audio-virtio) case_audio audio-virtio -device virtio-sound-pci,audiodev=snd0 ;;
*) fail "unknown case" ;;
esac
done
if [[ ${failures} == 0 ]]; then
echo "selftest: ALL PASSED (${cases})"
else
echo "selftest: ${failures} FAILURE(S)" >&2
exit 1
fi