/* * cputest.c: Test the libvirtd internal CPU APIs * * Copyright (C) 2010-2011 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Author: Jiri Denemark */ #include #include #include #include #include #include #include #include "internal.h" #include "xml.h" #include "memory.h" #include "buf.h" #include "testutils.h" #include "cpu_conf.h" #include "cpu/cpu.h" #include "cpu/cpu_map.h" static const char *abs_top_srcdir; #define VIR_FROM_THIS VIR_FROM_CPU enum compResultShadow { ERROR = VIR_CPU_COMPARE_ERROR, INCOMPATIBLE = VIR_CPU_COMPARE_INCOMPATIBLE, IDENTICAL = VIR_CPU_COMPARE_IDENTICAL, SUPERSET = VIR_CPU_COMPARE_SUPERSET }; enum cpuTestBoolWithError { FAIL = -1, NO = 0, YES = 1 }; enum api { API_COMPARE, API_GUEST_DATA, API_BASELINE, API_UPDATE, API_HAS_FEATURE }; static const char *apis[] = { "compare", "guest data", "baseline", "update", "has feature" }; struct data { const char *arch; enum api api; const char *host; const char *name; const char **models; const char *modelsName; unsigned int nmodels; const char *preferred; int result; }; static virCPUDefPtr cpuTestLoadXML(const char *arch, const char *name) { char *xml = NULL; xmlDocPtr doc = NULL; xmlXPathContextPtr ctxt = NULL; virCPUDefPtr cpu = NULL; if (virAsprintf(&xml, "%s/cputestdata/%s-%s.xml", abs_srcdir, arch, name) < 0) goto cleanup; if (!(doc = virXMLParseFile(xml)) || !(ctxt = xmlXPathNewContext(doc))) goto cleanup; ctxt->node = xmlDocGetRootElement(doc); cpu = virCPUDefParseXML(ctxt->node, ctxt, VIR_CPU_TYPE_AUTO); cleanup: xmlXPathFreeContext(ctxt); xmlFreeDoc(doc); free(xml); return cpu; } static virCPUDefPtr * cpuTestLoadMultiXML(const char *arch, const char *name, unsigned int *count) { char *xml = NULL; xmlDocPtr doc = NULL; xmlXPathContextPtr ctxt = NULL; xmlNodePtr *nodes = NULL; virCPUDefPtr *cpus = NULL; int n; int i; if (virAsprintf(&xml, "%s/cputestdata/%s-%s.xml", abs_srcdir, arch, name) < 0) goto cleanup; if (!(doc = virXMLParseFile(xml)) || !(ctxt = xmlXPathNewContext(doc))) goto error; ctxt->node = xmlDocGetRootElement(doc); n = virXPathNodeSet("/cpuTest/cpu", ctxt, &nodes); if (n <= 0 || !(cpus = calloc(n, sizeof(virCPUDefPtr)))) goto error; for (i = 0; i < n; i++) { ctxt->node = nodes[i]; cpus[i] = virCPUDefParseXML(nodes[i], ctxt, VIR_CPU_TYPE_HOST); if (!cpus[i]) goto error; } *count = n; cleanup: free(xml); free(nodes); xmlXPathFreeContext(ctxt); xmlFreeDoc(doc); return cpus; error: if (cpus) { for (i = 0; i < n; i++) virCPUDefFree(cpus[i]); free(cpus); cpus = NULL; } goto cleanup; } static int cpuTestCompareXML(const char *arch, const virCPUDefPtr cpu, const char *name) { char *xml = NULL; char *expected = NULL; char *actual = NULL; int ret = -1; if (virAsprintf(&xml, "%s/cputestdata/%s-%s.xml", abs_srcdir, arch, name) < 0) goto cleanup; if (virtTestLoadFile(xml, &expected) < 0) goto cleanup; if (!(actual = virCPUDefFormat(cpu, NULL, 0))) goto cleanup; if (STRNEQ(expected, actual)) { virtTestDifference(stderr, expected, actual); goto cleanup; } ret = 0; cleanup: free(xml); free(expected); free(actual); return ret; } static const char * cpuTestCompResStr(virCPUCompareResult result) { switch (result) { case VIR_CPU_COMPARE_ERROR: return "ERROR"; case VIR_CPU_COMPARE_INCOMPATIBLE: return "INCOMPATIBLE"; case VIR_CPU_COMPARE_IDENTICAL: return "IDENTICAL"; case VIR_CPU_COMPARE_SUPERSET: return "SUPERSET"; } return "unknown"; } static const char * cpuTestBoolWithErrorStr(enum cpuTestBoolWithError result) { switch (result) { case FAIL: return "FAIL"; case NO: return "NO"; case YES: return "YES"; } return "unknown"; } static int cpuTestCompare(const void *arg) { const struct data *data = arg; int ret = -1; virCPUDefPtr host = NULL; virCPUDefPtr cpu = NULL; virCPUCompareResult result; if (!(host = cpuTestLoadXML(data->arch, data->host)) || !(cpu = cpuTestLoadXML(data->arch, data->name))) goto cleanup; result = cpuCompare(host, cpu); if (data->result == VIR_CPU_COMPARE_ERROR) virResetLastError(); if (data->result != result) { if (virTestGetVerbose()) { fprintf(stderr, "\nExpected result %s, got %s\n", cpuTestCompResStr(data->result), cpuTestCompResStr(result)); /* Pad to line up with test name ... in virTestRun */ fprintf(stderr, "%74s", "... "); } goto cleanup; } ret = 0; cleanup: virCPUDefFree(host); virCPUDefFree(cpu); return ret; } static int cpuTestGuestData(const void *arg) { const struct data *data = arg; int ret = -1; virCPUDefPtr host = NULL; virCPUDefPtr cpu = NULL; virCPUDefPtr guest = NULL; union cpuData *guestData = NULL; virCPUCompareResult cmpResult; virBuffer buf = VIR_BUFFER_INITIALIZER; char *result = NULL; if (!(host = cpuTestLoadXML(data->arch, data->host)) || !(cpu = cpuTestLoadXML(data->arch, data->name))) goto cleanup; cmpResult = cpuGuestData(host, cpu, &guestData); if (cmpResult == VIR_CPU_COMPARE_ERROR || cmpResult == VIR_CPU_COMPARE_INCOMPATIBLE) goto cleanup; if (VIR_ALLOC(guest) < 0 || !(guest->arch = strdup(host->arch))) goto cleanup; guest->type = VIR_CPU_TYPE_GUEST; guest->match = VIR_CPU_MATCH_EXACT; if (cpuDecode(guest, guestData, data->models, data->nmodels, data->preferred) < 0) { if (data->result < 0) { virResetLastError(); ret = 0; } goto cleanup; } virBufferVSprintf(&buf, "%s+%s", data->host, data->name); if (data->nmodels) virBufferVSprintf(&buf, ",%s", data->modelsName); if (data->preferred) virBufferVSprintf(&buf, ",%s", data->preferred); virBufferAddLit(&buf, "-result"); if (virBufferError(&buf)) { virBufferFreeAndReset(&buf); goto cleanup; } result = virBufferContentAndReset(&buf); ret = cpuTestCompareXML(data->arch, guest, result); cleanup: VIR_FREE(result); if (host) cpuDataFree(host->arch, guestData); virCPUDefFree(host); virCPUDefFree(cpu); virCPUDefFree(guest); return ret; } static int cpuTestBaseline(const void *arg) { const struct data *data = arg; int ret = -1; virCPUDefPtr *cpus = NULL; virCPUDefPtr baseline = NULL; unsigned int ncpus = 0; char *result = NULL; unsigned int i; if (!(cpus = cpuTestLoadMultiXML(data->arch, data->name, &ncpus))) goto cleanup; baseline = cpuBaseline(cpus, ncpus, NULL, 0); if (data->result < 0) { virResetLastError(); if (!baseline) ret = 0; else if (virTestGetVerbose()) { fprintf(stderr, "\n%-70s... ", "cpuBaseline was expected to fail but it succeeded"); } goto cleanup; } if (!baseline) goto cleanup; if (virAsprintf(&result, "%s-result", data->name) < 0) goto cleanup; if (cpuTestCompareXML(data->arch, baseline, result) < 0) goto cleanup; for (i = 0; i < ncpus; i++) { virCPUCompareResult cmp; cmp = cpuCompare(cpus[i], baseline); if (cmp != VIR_CPU_COMPARE_SUPERSET && cmp != VIR_CPU_COMPARE_IDENTICAL) { if (virTestGetVerbose()) { fprintf(stderr, "\nbaseline CPU is incompatible with CPU %u\n", i); fprintf(stderr, "%74s", "... "); } ret = -1; goto cleanup; } } ret = 0; cleanup: if (cpus) { for (i = 0; i < ncpus; i++) virCPUDefFree(cpus[i]); free(cpus); } virCPUDefFree(baseline); free(result); return ret; } static int cpuTestUpdate(const void *arg) { const struct data *data = arg; int ret = -1; virCPUDefPtr host = NULL; virCPUDefPtr cpu = NULL; char *result = NULL; if (!(host = cpuTestLoadXML(data->arch, data->host)) || !(cpu = cpuTestLoadXML(data->arch, data->name))) goto cleanup; if (cpuUpdate(cpu, host) < 0) goto cleanup; if (virAsprintf(&result, "%s+%s", data->host, data->name) < 0) goto cleanup; ret = cpuTestCompareXML(data->arch, cpu, result); cleanup: virCPUDefFree(host); virCPUDefFree(cpu); free(result); return ret; } static int cpuTestHasFeature(const void *arg) { const struct data *data = arg; int ret = -1; virCPUDefPtr host = NULL; union cpuData *hostData = NULL; int result; if (!(host = cpuTestLoadXML(data->arch, data->host))) goto cleanup; if (cpuEncode(host->arch, host, NULL, &hostData, NULL, NULL, NULL, NULL) < 0) goto cleanup; result = cpuHasFeature(host->arch, hostData, data->name); if (data->result == -1) virResetLastError(); if (data->result != result) { if (virTestGetVerbose()) { fprintf(stderr, "\nExpected result %s, got %s\n", cpuTestBoolWithErrorStr(data->result), cpuTestBoolWithErrorStr(result)); /* Pad to line up with test name ... in virTestRun */ fprintf(stderr, "%74s", "... "); } goto cleanup; } ret = 0; cleanup: if (host) cpuDataFree(host->arch, hostData); virCPUDefFree(host); return ret; } static int (*cpuTest[])(const void *) = { cpuTestCompare, cpuTestGuestData, cpuTestBaseline, cpuTestUpdate, cpuTestHasFeature }; static int cpuTestRun(const char *name, const struct data *data) { char *label = NULL; if (virAsprintf(&label, "CPU %s(%s): %s", apis[data->api], data->arch, name) < 0) return -1; free(virtTestLogContentAndReset()); if (virtTestRun(label, 1, cpuTest[data->api], data) < 0) { if (virTestGetDebug()) { char *log; if ((log = virtTestLogContentAndReset()) && strlen(log) > 0) fprintf(stderr, "\n%s\n", log); free(log); } free(label); return -1; } free(label); return 0; } static const char *model486[] = { "486" }; static const char *nomodel[] = { "nomodel" }; static const char *models[] = { "qemu64", "core2duo", "Nehalem" }; static int mymain(void) { int ret = 0; char *map = NULL; abs_top_srcdir = getenv("abs_top_srcdir"); if (!abs_top_srcdir) abs_top_srcdir = ".."; if (virAsprintf(&map, "%s/src/cpu/cpu_map.xml", abs_top_srcdir) < 0 || cpuMapOverride(map) < 0) { free(map); return EXIT_FAILURE; } #define DO_TEST(arch, api, name, host, cpu, \ models, nmodels, preferred, result) \ do { \ struct data data = { \ arch, api, host, cpu, models, \ models == NULL ? NULL : #models, \ nmodels, preferred, result \ }; \ if (cpuTestRun(name, &data) < 0) \ ret = -1; \ } while (0) #define DO_TEST_COMPARE(arch, host, cpu, result) \ DO_TEST(arch, API_COMPARE, \ host "/" cpu " (" #result ")", \ host, cpu, NULL, 0, NULL, result) #define DO_TEST_UPDATE(arch, host, cpu, result) \ do { \ DO_TEST(arch, API_UPDATE, \ cpu " on " host, \ host, cpu, NULL, 0, NULL, 0); \ DO_TEST_COMPARE(arch, host, host "+" cpu, result); \ } while (0) #define DO_TEST_BASELINE(arch, name, result) \ DO_TEST(arch, API_BASELINE, name, NULL, "baseline-" name, \ NULL, 0, NULL, result) #define DO_TEST_HASFEATURE(arch, host, feature, result) \ DO_TEST(arch, API_HAS_FEATURE, \ host "/" feature " (" #result ")", \ host, feature, NULL, 0, NULL, result) #define DO_TEST_GUESTDATA(arch, host, cpu, models, preferred, result) \ DO_TEST(arch, API_GUEST_DATA, \ host "/" cpu " (" #models ", pref=" #preferred ")", \ host, cpu, models, \ models == NULL ? 0 : sizeof(models) / sizeof(char *), \ preferred, result) /* host to host comparison */ DO_TEST_COMPARE("x86", "host", "host", IDENTICAL); DO_TEST_COMPARE("x86", "host", "host-better", INCOMPATIBLE); DO_TEST_COMPARE("x86", "host", "host-worse", SUPERSET); DO_TEST_COMPARE("x86", "host", "host-amd-fake", INCOMPATIBLE); DO_TEST_COMPARE("x86", "host", "host-incomp-arch", INCOMPATIBLE); DO_TEST_COMPARE("x86", "host", "host-no-vendor", IDENTICAL); DO_TEST_COMPARE("x86", "host-no-vendor", "host", INCOMPATIBLE); /* guest to host comparison */ DO_TEST_COMPARE("x86", "host", "bogus-model", ERROR); DO_TEST_COMPARE("x86", "host", "bogus-feature", ERROR); DO_TEST_COMPARE("x86", "host", "min", SUPERSET); DO_TEST_COMPARE("x86", "host", "pentium3", SUPERSET); DO_TEST_COMPARE("x86", "host", "exact", SUPERSET); DO_TEST_COMPARE("x86", "host", "exact-forbid", INCOMPATIBLE); DO_TEST_COMPARE("x86", "host", "exact-forbid-extra", SUPERSET); DO_TEST_COMPARE("x86", "host", "exact-disable", SUPERSET); DO_TEST_COMPARE("x86", "host", "exact-disable2", SUPERSET); DO_TEST_COMPARE("x86", "host", "exact-disable-extra", SUPERSET); DO_TEST_COMPARE("x86", "host", "exact-require", SUPERSET); DO_TEST_COMPARE("x86", "host", "exact-require-extra", INCOMPATIBLE); DO_TEST_COMPARE("x86", "host", "exact-force", SUPERSET); DO_TEST_COMPARE("x86", "host", "strict", INCOMPATIBLE); DO_TEST_COMPARE("x86", "host", "strict-full", IDENTICAL); DO_TEST_COMPARE("x86", "host", "strict-disable", IDENTICAL); DO_TEST_COMPARE("x86", "host", "strict-force-extra", IDENTICAL); DO_TEST_COMPARE("x86", "host", "guest", SUPERSET); DO_TEST_COMPARE("x86", "host", "pentium3-amd", INCOMPATIBLE); DO_TEST_COMPARE("x86", "host-amd", "pentium3-amd", SUPERSET); DO_TEST_COMPARE("x86", "host-worse", "nehalem-force", IDENTICAL); /* guest updates for migration * automatically compares host CPU with the result */ DO_TEST_UPDATE("x86", "host", "min", IDENTICAL); DO_TEST_UPDATE("x86", "host", "pentium3", IDENTICAL); DO_TEST_UPDATE("x86", "host", "guest", SUPERSET); /* computing baseline CPUs */ DO_TEST_BASELINE("x86", "incompatible-vendors", -1); DO_TEST_BASELINE("x86", "no-vendor", 0); DO_TEST_BASELINE("x86", "some-vendors", 0); DO_TEST_BASELINE("x86", "1", 0); DO_TEST_BASELINE("x86", "2", 0); /* CPU features */ DO_TEST_HASFEATURE("x86", "host", "vmx", YES); DO_TEST_HASFEATURE("x86", "host", "lm", YES); DO_TEST_HASFEATURE("x86", "host", "sse4.1", YES); DO_TEST_HASFEATURE("x86", "host", "3dnowext", NO); DO_TEST_HASFEATURE("x86", "host", "skinit", NO); DO_TEST_HASFEATURE("x86", "host", "foo", FAIL); /* computing guest data and decoding the data into a guest CPU XML */ DO_TEST_GUESTDATA("x86", "host", "guest", NULL, NULL, 0); DO_TEST_GUESTDATA("x86", "host-better", "pentium3", NULL, NULL, 0); DO_TEST_GUESTDATA("x86", "host-better", "pentium3", NULL, "pentium3", 0); DO_TEST_GUESTDATA("x86", "host-better", "pentium3", NULL, "core2duo", 0); DO_TEST_GUESTDATA("x86", "host-worse", "guest", NULL, NULL, 0); DO_TEST_GUESTDATA("x86", "host", "strict-force-extra", NULL, NULL, 0); DO_TEST_GUESTDATA("x86", "host", "nehalem-force", NULL, NULL, 0); DO_TEST_GUESTDATA("x86", "host", "guest", model486, NULL, 0); DO_TEST_GUESTDATA("x86", "host", "guest", models, NULL, 0); DO_TEST_GUESTDATA("x86", "host", "guest", models, "Penryn", 0); DO_TEST_GUESTDATA("x86", "host", "guest", models, "qemu64", 0); DO_TEST_GUESTDATA("x86", "host", "guest", nomodel, NULL, -1); free(map); return (ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } VIRT_TEST_MAIN(mymain)