tests: Add CPU detection tests

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@redhat.com>
This commit is contained in:
Jiri Denemark 2016-06-01 15:57:00 +02:00
parent a54234c37b
commit 3704b9003f
4 changed files with 253 additions and 3 deletions

View File

@ -861,6 +861,10 @@ cputest_SOURCES = \
cputest.c \ cputest.c \
testutils.c testutils.h testutils.c testutils.h
cputest_LDADD = $(LDADDS) $(LIBXML_LIBS) 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_SOURCES = \
metadatatest.c \ metadatatest.c \

View File

@ -40,6 +40,12 @@
#include "cpu/cpu_map.h" #include "cpu/cpu_map.h"
#include "virstring.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 #define VIR_FROM_THIS VIR_FROM_CPU
enum cpuTestBoolWithError { enum cpuTestBoolWithError {
@ -53,7 +59,10 @@ enum api {
API_GUEST_DATA, API_GUEST_DATA,
API_BASELINE, API_BASELINE,
API_UPDATE, API_UPDATE,
API_HAS_FEATURE API_HAS_FEATURE,
API_HOST_CPUID,
API_GUEST_CPUID,
API_JSON_CPUID,
}; };
static const char *apis[] = { static const char *apis[] = {
@ -61,7 +70,10 @@ static const char *apis[] = {
"guest data", "guest data",
"baseline", "baseline",
"update", "update",
"has feature" "has feature",
"host CPUID",
"guest CPUID",
"json CPUID",
}; };
struct data { struct data {
@ -77,6 +89,10 @@ struct data {
int result; int result;
}; };
#if WITH_QEMU && WITH_YAJL
static virQEMUDriver driver;
#endif
static virCPUDefPtr static virCPUDefPtr
cpuTestLoadXML(const char *arch, const char *name) cpuTestLoadXML(const char *arch, const char *name)
@ -458,12 +474,118 @@ 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 = NULL;
virCPUDefPtr cpu = NULL;
char *result = NULL;
if (virAsprintf(&hostFile, "%s/cputestdata/%s-cpuid-%s.xml",
abs_srcdir, data->arch, data->host) < 0)
goto cleanup;
if (virTestLoadFile(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:
VIR_FREE(hostFile);
VIR_FREE(host);
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 *) = { static int (*cpuTest[])(const void *) = {
cpuTestCompare, cpuTestCompare,
cpuTestGuestData, cpuTestGuestData,
cpuTestBaseline, cpuTestBaseline,
cpuTestUpdate, cpuTestUpdate,
cpuTestHasFeature cpuTestHasFeature,
cpuTestCPUID,
cpuTestCPUID,
#if WITH_QEMU && WITH_YAJL
cpuTestJSONCPUID,
#else
NULL,
#endif
}; };
@ -508,6 +630,13 @@ mymain(void)
{ {
int ret = 0; 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, \ #define DO_TEST(arch, api, name, host, cpu, \
models, nmodels, preferred, flags, result) \ models, nmodels, preferred, flags, result) \
do { \ do { \
@ -562,6 +691,27 @@ mymain(void)
models == NULL ? 0 : sizeof(models) / sizeof(char *), \ models == NULL ? 0 : sizeof(models) / sizeof(char *), \
preferred, 0, result) 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 */ /* host to host comparison */
DO_TEST_COMPARE("x86", "host", "host", VIR_CPU_COMPARE_IDENTICAL); DO_TEST_COMPARE("x86", "host", "host", VIR_CPU_COMPARE_IDENTICAL);
DO_TEST_COMPARE("x86", "host", "host-better", VIR_CPU_COMPARE_INCOMPATIBLE); DO_TEST_COMPARE("x86", "host", "host-better", VIR_CPU_COMPARE_INCOMPATIBLE);
@ -695,6 +845,10 @@ mymain(void)
DO_TEST_GUESTDATA("ppc64", "host", "guest-legacy-incompatible", ppc_models, NULL, -1); DO_TEST_GUESTDATA("ppc64", "host", "guest-legacy-incompatible", ppc_models, NULL, -1);
DO_TEST_GUESTDATA("ppc64", "host", "guest-legacy-invalid", 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; return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
} }

35
tests/cputestdata/cpu-gather.sh Executable file
View File

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

57
tests/cputestdata/cpu-parse.sh Executable file
View File

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