[libvirt] [PATCH 12/41] tests: Add CPU detection tests

Jiri Denemark jdenemar at redhat.com
Wed Jun 8 08:22:26 UTC 2016


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 at 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




More information about the libvir-list mailing list