So far we only test CPUID -> CPU def conversion on artificial CPUID data
computed from another CPU def. This patch adds the infrastructure to
test this conversion on real data gathered from a host CPU and two
helper scripts for adding new test data:
- cpu-gather.sh runs cpuid tool and qemu-system-x86_64 to get CPUID data
from the host CPU; this is what users can be asked to run if they run
into an issue with host CPU detection in libvirt
- cpu-parse.sh takes the data generated by cpu-gather.sh and creates
data files for CPU detection tests
The CPUID data queried from QEMU will eventually switch to the format
used by query-host-cpu QMP command once QEMU implements it. Until then
we just spawn QEMU with -cpu host and query the guest CPU in QOM. They
should both provide the same CPUID results, but query-host-cpu does not
require any guest CPU to be created by QEMU.
Signed-off-by: Jiri Denemark <jdenemar(a)redhat.com>
---
tests/Makefile.am | 4 ++
tests/cputest.c | 156 +++++++++++++++++++++++++++++++++++++++-
tests/cputestdata/cpu-gather.sh | 35 +++++++++
tests/cputestdata/cpu-parse.sh | 57 +++++++++++++++
4 files changed, 249 insertions(+), 3 deletions(-)
create mode 100755 tests/cputestdata/cpu-gather.sh
create mode 100755 tests/cputestdata/cpu-parse.sh
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 9238a73..8d37298 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -861,6 +861,10 @@ cputest_SOURCES = \
cputest.c \
testutils.c testutils.h
cputest_LDADD = $(LDADDS) $(LIBXML_LIBS)
+if WITH_QEMU
+cputest_SOURCES += testutilsqemu.c testutilsqemu.h
+cputest_LDADD += libqemumonitortestutils.la $(qemu_LDADDS) $(GNULIB_LIBS)
+endif WITH_QEMU
metadatatest_SOURCES = \
metadatatest.c \
diff --git a/tests/cputest.c b/tests/cputest.c
index 431b587..2b243bb 100644
--- a/tests/cputest.c
+++ b/tests/cputest.c
@@ -40,6 +40,12 @@
#include "cpu/cpu_map.h"
#include "virstring.h"
+#if WITH_QEMU && WITH_YAJL
+# include "testutilsqemu.h"
+# include "qemumonitortestutils.h"
+# include "qemu/qemu_monitor_json.h"
+#endif
+
#define VIR_FROM_THIS VIR_FROM_CPU
enum cpuTestBoolWithError {
@@ -53,7 +59,10 @@ enum api {
API_GUEST_DATA,
API_BASELINE,
API_UPDATE,
- API_HAS_FEATURE
+ API_HAS_FEATURE,
+ API_HOST_CPUID,
+ API_GUEST_CPUID,
+ API_JSON_CPUID,
};
static const char *apis[] = {
@@ -61,7 +70,10 @@ static const char *apis[] = {
"guest data",
"baseline",
"update",
- "has feature"
+ "has feature",
+ "host CPUID",
+ "guest CPUID",
+ "json CPUID",
};
struct data {
@@ -77,6 +89,10 @@ struct data {
int result;
};
+#if WITH_QEMU && WITH_YAJL
+static virQEMUDriver driver;
+#endif
+
static virCPUDefPtr
cpuTestLoadXML(const char *arch, const char *name)
@@ -458,12 +474,114 @@ cpuTestHasFeature(const void *arg)
}
+static int
+cpuTestCPUID(const void *arg)
+{
+ const struct data *data = arg;
+ int ret = -1;
+ virCPUDataPtr hostData = NULL;
+ char *hostFile = NULL;
+ char *host;
+ virCPUDefPtr cpu = NULL;
+ char *result = NULL;
+
+ if (virAsprintf(&hostFile, "%s/cputestdata/%s-cpuid-%s.xml",
+ abs_srcdir, data->arch, data->host) < 0 ||
+ virtTestLoadFile(hostFile, &host) < 0 ||
+ !(hostData = cpuDataParse(host)))
+ goto cleanup;
+
+ if (VIR_ALLOC(cpu) < 0)
+ goto cleanup;
+
+ cpu->arch = hostData->arch;
+ if (data->api == API_GUEST_CPUID) {
+ cpu->type = VIR_CPU_TYPE_GUEST;
+ cpu->match = VIR_CPU_MATCH_EXACT;
+ cpu->fallback = VIR_CPU_FALLBACK_FORBID;
+ } else {
+ cpu->type = VIR_CPU_TYPE_HOST;
+ }
+
+ if (cpuDecode(cpu, hostData, NULL, 0, NULL) < 0)
+ goto cleanup;
+
+ if (virAsprintf(&result, "cpuid-%s-%s",
+ data->host,
+ data->api == API_HOST_CPUID ? "host" :
"guest") < 0)
+ goto cleanup;
+
+ ret = cpuTestCompareXML(data->arch, cpu, result, false);
+
+ cleanup:
+ cpuDataFree(hostData);
+ virCPUDefFree(cpu);
+ VIR_FREE(result);
+ return ret;
+}
+
+
+#if WITH_QEMU && WITH_YAJL
+static int
+cpuTestJSONCPUID(const void *arg)
+{
+ const struct data *data = arg;
+ virCPUDataPtr cpuData = NULL;
+ virCPUDefPtr cpu = NULL;
+ qemuMonitorTestPtr testMon = NULL;
+ char *json = NULL;
+ char *result = NULL;
+ int ret = -1;
+
+ if (virAsprintf(&json, "%s/cputestdata/%s-cpuid-%s.json",
+ abs_srcdir, data->arch, data->host) < 0 ||
+ virAsprintf(&result, "cpuid-%s-json", data->host) < 0)
+ goto cleanup;
+
+ if (!(testMon = qemuMonitorTestNewFromFile(json, driver.xmlopt, true)))
+ goto cleanup;
+
+ if (qemuMonitorJSONGetCPUx86Data(qemuMonitorTestGetMonitor(testMon),
+ "feature-words", &cpuData) < 0)
+ goto cleanup;
+
+ if (VIR_ALLOC(cpu) < 0)
+ goto cleanup;
+
+ cpu->arch = cpuData->arch;
+ cpu->type = VIR_CPU_TYPE_GUEST;
+ cpu->match = VIR_CPU_MATCH_EXACT;
+ cpu->fallback = VIR_CPU_FALLBACK_FORBID;
+
+ if (cpuDecode(cpu, cpuData, NULL, 0, NULL) < 0)
+ goto cleanup;
+
+ ret = cpuTestCompareXML(data->arch, cpu, result, false);
+
+ cleanup:
+ qemuMonitorTestFree(testMon);
+ cpuDataFree(cpuData);
+ virCPUDefFree(cpu);
+ VIR_FREE(result);
+ VIR_FREE(json);
+ return ret;
+}
+#endif
+
+
static int (*cpuTest[])(const void *) = {
cpuTestCompare,
cpuTestGuestData,
cpuTestBaseline,
cpuTestUpdate,
- cpuTestHasFeature
+ cpuTestHasFeature,
+ cpuTestCPUID,
+ cpuTestCPUID,
+#if WITH_QEMU && WITH_YAJL
+ cpuTestJSONCPUID,
+#else
+ NULL,
+#endif
};
@@ -508,6 +626,13 @@ mymain(void)
{
int ret = 0;
+#if WITH_QEMU && WITH_YAJL
+ if (qemuTestDriverInit(&driver) < 0)
+ return EXIT_FAILURE;
+
+ virEventRegisterDefaultImpl();
+#endif
+
#define DO_TEST(arch, api, name, host, cpu, \
models, nmodels, preferred, flags, result) \
do { \
@@ -562,6 +687,27 @@ mymain(void)
models == NULL ? 0 : sizeof(models) / sizeof(char *), \
preferred, 0, result)
+#if WITH_QEMU && WITH_YAJL
+# define DO_TEST_CPUID_JSON(arch, host, json) \
+ do { \
+ if (json) { \
+ DO_TEST(arch, API_JSON_CPUID, host, host, \
+ NULL, NULL, 0, NULL, 0, 0); \
+ } \
+ } while (0)
+#else
+# define DO_TEST_CPUID_JSON(arch, host)
+#endif
+
+#define DO_TEST_CPUID(arch, host, json) \
+ do { \
+ DO_TEST(arch, API_HOST_CPUID, host, host, \
+ NULL, NULL, 0, NULL, 0, 0); \
+ DO_TEST(arch, API_GUEST_CPUID, host, host, \
+ NULL, NULL, 0, NULL, 0, 0); \
+ DO_TEST_CPUID_JSON(arch, host, json); \
+ } while (0)
+
/* host to host comparison */
DO_TEST_COMPARE("x86", "host", "host",
VIR_CPU_COMPARE_IDENTICAL);
DO_TEST_COMPARE("x86", "host", "host-better",
VIR_CPU_COMPARE_INCOMPATIBLE);
@@ -695,6 +841,10 @@ mymain(void)
DO_TEST_GUESTDATA("ppc64", "host",
"guest-legacy-incompatible", ppc_models, NULL, -1);
DO_TEST_GUESTDATA("ppc64", "host",
"guest-legacy-invalid", ppc_models, NULL, -1);
+#if WITH_QEMU && WITH_YAJL
+ qemuTestDriverFree(&driver);
+#endif
+
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
diff --git a/tests/cputestdata/cpu-gather.sh b/tests/cputestdata/cpu-gather.sh
new file mode 100755
index 0000000..c8439a6
--- /dev/null
+++ b/tests/cputestdata/cpu-gather.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+#
+# The cpuid tool can be usually found in a package called "cpuid". If your
+# distro does not provide such package, you can find the sources or binary
+# packages at
http://www.etallen.com/cpuid.html
+
+grep 'model name' /proc/cpuinfo | head -n1
+
+cpuid -1r
+
+echo
+qemu=qemu-system-x86_64
+for cmd in /usr/bin/$qemu /usr/bin/qemu-kvm /usr/libexec/qemu-kvm; do
+ if [[ -x $cmd ]]; then
+ qemu=$cmd
+ break
+ fi
+done
+
+qom_get()
+{
+ path='/machine/unattached/device[0]'
+ echo
'{"execute":"qom-get","arguments":{"path":"'$path'",'
\
+
'"property":"'$1'"},"id":"'$1'"}'
+}
+
+$qemu -machine accel=kvm -cpu host -nodefaults -nographic -qmp stdio <<EOF
+{"execute":"qmp_capabilities"}
+`qom_get feature-words`
+`qom_get family`
+`qom_get model`
+`qom_get stepping`
+`qom_get model-id`
+{"execute":"quit"}
+EOF
diff --git a/tests/cputestdata/cpu-parse.sh b/tests/cputestdata/cpu-parse.sh
new file mode 100755
index 0000000..1b5ab4a
--- /dev/null
+++ b/tests/cputestdata/cpu-parse.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+# Usage:
+# ./cpu-gather.sh | ./cpu-parse.sh
+
+data=`cat`
+
+model=`sed -ne '/^model name[ ]*:/ {s/^[^:]*: \(.*\)/\1/p; q}'
<<<"$data"`
+
+fname=`sed -e 's/^ *//;
+ s/ *$//;
+ s/[ -]\+ \+/ /g;
+ s/(\([Rr]\|[Tt][Mm]\))//g;
+ s/.*\(Intel\|AMD\) //;
+ s/ \(Duo\|Quad\|II X[0-9]\+\) / /;
+ s/ \(CPU\|Processor\)\>//;
+ s/ @.*//;
+ s/ APU .*//;
+ s/ \(v[0-9]\|SE\)$//;
+ s/ /-/g' <<<"$model"`
+fname="x86-cpuid-$fname"
+
+xml()
+{
+ hex='\(0x[0-9a-f]\+\)'
+ match="$hex $hex: eax=$hex ebx=$hex ecx=$hex edx=$hex"
+ subst="<cpuid eax_in='\\1' ecx_in='\\2' eax='\\3'
ebx='\\4' ecx='\\5' edx='\\6'\\/>"
+
+ echo "<!-- $model -->"
+ echo "<cpudata arch='x86'>"
+ sed -ne "s/^ *$match$/ $subst/p"
+ echo "</cpudata>"
+}
+
+json()
+{
+ first=true
+ sed -ne '/{"QMP".*/d;
+ /{"return": {}}/d;
+ /{"timestamp":.*/d;
+ /^{/p' <<<"$data" | \
+ while read; do
+ $first || echo
+ first=false
+ json_reformat <<<"$REPLY" | tr -s '\n'
+ done
+}
+
+xml <<<"$data" >$fname.xml
+echo $fname.xml
+
+json <<<"$data" >$fname.json
+if [[ -s $fname.json ]]; then
+ echo $fname.json
+else
+ rm $fname.new.json
+fi
--
2.8.3