/*
 * cputest.c: Test the libvirtd internal CPU APIs
 *
 * Copyright (C) 2010-2014 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, see
 * <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <unistd.h>

#include <sys/types.h>
#include <fcntl.h>

#include "internal.h"
#include "virxml.h"
#include "viralloc.h"
#include "virbuffer.h"
#include "testutils.h"
#include "cpu_conf.h"
#include "cpu/cpu.h"
#include "cpu/cpu_x86.h"

#if WITH_QEMU
# include "testutilsqemu.h"
# include "qemumonitortestutils.h"
# define LIBVIRT_QEMU_CAPSPRIV_H_ALLOW
# include "qemu/qemu_capspriv.h"
#endif

#define VIR_FROM_THIS VIR_FROM_CPU

enum cpuTestBoolWithError {
    FAIL    = -1,
    NO      = 0,
    YES     = 1
};


struct data {
    virArch arch;
    const char *host;
    const char *name;
    virDomainCapsCPUModels *models;
    const char *modelsName;
    const char **cpus;
    int ncpus;
    unsigned int flags;
    int result;
    GHashTable *existingTestCases;
};

#if WITH_QEMU
static virQEMUDriver driver;
#endif


static virCPUDef *
cpuTestLoadXML(virArch arch,
               const char *name,
               GHashTable *existingTestCases)
{
    g_autofree char *xml = NULL;
    g_autoptr(xmlDoc) doc = NULL;
    g_autoptr(xmlXPathContext) ctxt = NULL;
    virCPUDef *cpu = NULL;

    xml = g_strdup_printf("%s/cputestdata/%s-%s.xml", abs_srcdir,
                          virArchToString(arch), name);

    virTestCaseMarkUsed(existingTestCases, xml);

    if (!(doc = virXMLParseFileCtxt(xml, &ctxt)))
        return NULL;

    virCPUDefParseXML(ctxt, NULL, VIR_CPU_TYPE_AUTO, &cpu, false);

    return cpu;
}


static virCPUDef **
cpuTestLoadMultiXML(virArch arch,
                    const char *name,
                    GHashTable *existingTestCases,
                    unsigned int *count)
{
    g_autofree char *xml = NULL;
    g_autoptr(xmlDoc) doc = NULL;
    g_autoptr(xmlXPathContext) ctxt = NULL;
    g_autofree xmlNodePtr *nodes = NULL;
    virCPUDef **cpus = NULL;
    int n;
    size_t i;

    xml = g_strdup_printf("%s/cputestdata/%s-%s.xml", abs_srcdir,
                          virArchToString(arch), name);

    virTestCaseMarkUsed(existingTestCases, xml);

    if (!(doc = virXMLParseFileCtxt(xml, &ctxt)))
        return NULL;

    n = virXPathNodeSet("/cpuTest/cpu", ctxt, &nodes);
    if (n <= 0) {
        fprintf(stderr, "\nNo /cpuTest/cpu elements found in %s\n", xml);
        return NULL;
    }

    cpus = g_new0(virCPUDef *, n);

    for (i = 0; i < n; i++) {
        ctxt->node = nodes[i];
        if (virCPUDefParseXML(ctxt, NULL, VIR_CPU_TYPE_HOST, &cpus[i],
                              false) < 0)
            goto error;
    }

    *count = n;

    return cpus;

 error:
    for (i = 0; i < n; i++)
        virCPUDefFree(cpus[i]);
    VIR_FREE(cpus);
    return NULL;
}


static int
cpuTestCompareXML(virArch arch,
                  virCPUDef *cpu,
                  const char *name,
                  GHashTable *existingTestCases)
{
    g_autofree char *xml = NULL;
    g_autofree char *actual = NULL;

    xml = g_strdup_printf("%s/cputestdata/%s-%s.xml", abs_srcdir,
                          virArchToString(arch), name);

    virTestCaseMarkUsed(existingTestCases, xml);

    if (!(actual = virCPUDefFormat(cpu, NULL)))
        return -1;

    if (virTestCompareToFile(actual, xml) < 0)
        return -1;

    return 0;
}


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";
    case VIR_CPU_COMPARE_LAST:          break;
    }

    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;
    g_autoptr(virCPUDef) host = NULL;
    g_autoptr(virCPUDef) cpu = NULL;
    virCPUCompareResult result;

    if (!(host = cpuTestLoadXML(data->arch, data->host,
                                data->existingTestCases)) ||
        !(cpu = cpuTestLoadXML(data->arch, data->name,
                               data->existingTestCases))) {
        return -1;
    }

    result = virCPUCompare(host->arch, host, cpu, false);
    if (data->result == VIR_CPU_COMPARE_ERROR)
        virResetLastError();

    if (data->result != result) {
        VIR_TEST_VERBOSE("\nExpected result %s, got %s",
                    cpuTestCompResStr(data->result),
                    cpuTestCompResStr(result));
        /* Pad to line up with test name ... in virTestRun */
        VIR_TEST_VERBOSE("%74s", "... ");
        return -1;
    }

    return 0;
}


static int
cpuTestGuestCPU(const void *arg)
{
    const struct data *data = arg;
    int ret = -2;
    g_autoptr(virCPUDef) host = NULL;
    g_autoptr(virCPUDef) cpu = NULL;
    virCPUCompareResult cmpResult;
    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
    g_autofree char *result = NULL;

    if (!(host = cpuTestLoadXML(data->arch, data->host,
                                data->existingTestCases)) ||
        !(cpu = cpuTestLoadXML(data->arch, data->name,
                               data->existingTestCases))) {
        goto cleanup;
    }

    if (virCPUConvertLegacy(host->arch, cpu) < 0)
        goto cleanup;

    cmpResult = virCPUCompare(host->arch, host, cpu, false);
    if (cmpResult == VIR_CPU_COMPARE_ERROR ||
        cmpResult == VIR_CPU_COMPARE_INCOMPATIBLE) {
        ret = -1;
        goto cleanup;
    }

    if (virCPUUpdate(host->arch, cpu, host, VIR_CPU_FEATURE_DISABLE) < 0 ||
        virCPUTranslate(host->arch, cpu, data->models) < 0) {
        ret = -1;
        goto cleanup;
    }

    virBufferAsprintf(&buf, "%s+%s", data->host, data->name);
    if (data->modelsName)
        virBufferAsprintf(&buf, ",%s", data->modelsName);
    virBufferAddLit(&buf, "-result");

    result = virBufferContentAndReset(&buf);

    if (cpuTestCompareXML(data->arch, cpu, result, data->existingTestCases) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    if (ret == data->result) {
        /* We got the result we expected, whether it was
         * a success or a failure */
        virResetLastError();
        ret = 0;
    } else {
        VIR_TEST_VERBOSE("\nExpected result %d, got %d",
                         data->result, ret);
        /* Pad to line up with test name ... in virTestRun */
        VIR_TEST_VERBOSE("%74s", "... ");
        ret = -1;
    }

    return ret;
}


static int
cpuTestBaseline(const void *arg)
{
    const struct data *data = arg;
    int ret = -1;
    virCPUDef **cpus = NULL;
    virCPUDef *baseline = NULL;
    unsigned int ncpus = 0;
    g_autofree char *result = NULL;
    const char *suffix;
    size_t i;

    if (!(cpus = cpuTestLoadMultiXML(data->arch, data->name,
                                     data->existingTestCases, &ncpus))) {
        goto cleanup;
    }

    baseline = virCPUBaseline(data->arch, cpus, ncpus, NULL, NULL,
                              !!(data->flags & VIR_CONNECT_BASELINE_CPU_MIGRATABLE));

    if (baseline &&
        (data->flags & VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES) &&
        virCPUExpandFeatures(data->arch, baseline) < 0) {
        g_clear_pointer(&baseline, virCPUDefFree);
    }

    if (data->result < 0) {
        virResetLastError();
        if (!baseline) {
            ret = 0;
        } else {
            VIR_TEST_VERBOSE("\n%-70s... ",
                    "virCPUBaseline was expected to fail but it succeeded");
        }
        goto cleanup;
    }
    if (!baseline)
        goto cleanup;

    if (data->flags & VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES)
        suffix = "expanded";
    else if (data->flags & VIR_CONNECT_BASELINE_CPU_MIGRATABLE)
        suffix = "migratable";
    else
        suffix = "result";
    result = g_strdup_printf("%s-%s", data->name, suffix);

    if (cpuTestCompareXML(data->arch, baseline, result,
                          data->existingTestCases) < 0) {
        goto cleanup;
    }

    for (i = 0; i < ncpus; i++) {
        virCPUCompareResult cmp;

        cmp = virCPUCompare(cpus[i]->arch, cpus[i], baseline, false);
        if (cmp != VIR_CPU_COMPARE_SUPERSET &&
            cmp != VIR_CPU_COMPARE_IDENTICAL) {
            VIR_TEST_VERBOSE("\nbaseline CPU is incompatible with CPU %zu",
                             i);
            VIR_TEST_VERBOSE("%74s", "... ");
            ret = -1;
            goto cleanup;
        }
    }

    ret = 0;

 cleanup:
    if (cpus) {
        for (i = 0; i < ncpus; i++)
            virCPUDefFree(cpus[i]);
        VIR_FREE(cpus);
    }
    virCPUDefFree(baseline);
    return ret;
}


static int
cpuTestUpdate(const void *arg)
{
    const struct data *data = arg;
    g_autoptr(virCPUDef) host = NULL;
    g_autoptr(virCPUDef) migHost = NULL;
    g_autoptr(virCPUDef) cpu = NULL;
    g_autofree char *result = NULL;

    if (!(host = cpuTestLoadXML(data->arch, data->host,
                                data->existingTestCases)) ||
        !(cpu = cpuTestLoadXML(data->arch, data->name,
                               data->existingTestCases))) {
        return -1;
    }

    if (!(migHost = virCPUCopyMigratable(data->arch, host)))
        return -1;

    if (virCPUUpdate(host->arch, cpu, migHost, VIR_CPU_FEATURE_DISABLE) < 0)
        return -1;

    result = g_strdup_printf("%s+%s", data->host, data->name);

    return cpuTestCompareXML(data->arch, cpu, result, data->existingTestCases);
}


static int
cpuTestHasFeature(const void *arg)
{
    const struct data *data = arg;
    g_autoptr(virCPUDef) host = NULL;
    g_autoptr(virCPUData) hostData = NULL;
    int result;

    if (!(host = cpuTestLoadXML(data->arch, data->host, data->existingTestCases)))
        return -1;

    if (cpuEncode(host->arch, host, NULL, &hostData,
                  NULL, NULL, NULL, NULL) < 0)
        return -1;

    result = virCPUCheckFeature(host->arch, host, data->name);

    if (data->result == result)
        result = virCPUDataCheckFeature(hostData, data->name);

    if (data->result == -1)
        virResetLastError();

    if (data->result != result) {
        VIR_TEST_VERBOSE("\nExpected result %s, got %s",
            cpuTestBoolWithErrorStr(data->result),
            cpuTestBoolWithErrorStr(result));
        /* Pad to line up with test name ... in virTestRun */
        VIR_TEST_VERBOSE("%74s", "... ");
        return -1;
    }

    return 0;
}


static int
cpuTestValidateFeatures(const void *arg)
{
    const struct data *data = arg;
    g_autoptr(virCPUDef) cpu = NULL;
    int result;

    if (!(cpu = cpuTestLoadXML(data->arch, data->name, data->existingTestCases)))
        return -1;

    result = virCPUValidateFeatures(data->arch, cpu);

    if (data->result == -1)
        virResetLastError();

    if (data->result != result) {
        VIR_TEST_VERBOSE("\nExpected result %s, got %s",
            cpuTestBoolWithErrorStr(data->result),
            cpuTestBoolWithErrorStr(result));
        /* Pad to line up with test name ... in virTestRun */
        VIR_TEST_VERBOSE("%74s", "... ");
        return -1;
    }

    return 0;
}


typedef enum {
    /* No JSON data from QEMU. */
    JSON_NONE,
    /* Only a reply from query-cpu-model-expansion QMP command. */
    JSON_HOST,
    /* Replies from both query-cpu-model-expansion and query-cpu-definitions
     * QMP commands.
     */
    JSON_MODELS,
    /* Same as JSON_MODELS, but the reply from query-cpu-definitions has to
     * be parsed for providing the correct result. This happens when the
     * CPU model detected by libvirt has non-empty unavailable-features array
     * in query-cpu-definitions reply or when the CPU model detected from CPUID
     * differs from the one we get from QEMU and we need to translate them for
     * comparison. Such tests require QEMU driver to be enabled.
     */
    JSON_MODELS_REQUIRED,
} cpuTestCPUIDJson;

#if WITH_QEMU
static virQEMUCaps *
cpuTestMakeQEMUCaps(const struct data *data)
{
    g_autoptr(virQEMUCaps) qemuCaps = NULL;
    g_autoptr(qemuMonitorTest) testMon = NULL;
    g_autoptr(qemuMonitorCPUModelInfo) model = NULL;
    g_autoptr(virCPUDef) cpu = NULL;
    bool fail_no_props = true;
    g_autofree char *json = NULL;

    json = g_strdup_printf("%s/cputestdata/%s-cpuid-%s.json", abs_srcdir,
                           virArchToString(data->arch), data->host);

    virTestCaseMarkUsed(data->existingTestCases, json);

    if (!(testMon = qemuMonitorTestNewFromFile(json, driver.xmlopt, true)))
        return NULL;

    qemuMonitorTestAllowUnusedCommands(testMon);

    cpu = virCPUDefNew();

    cpu->model = g_strdup("host");

    if (ARCH_IS_S390(data->arch))
        fail_no_props = false;

    if (qemuMonitorGetCPUModelExpansion(qemuMonitorTestGetMonitor(testMon),
                                        QEMU_MONITOR_CPU_MODEL_EXPANSION_STATIC,
                                        cpu, true, false, fail_no_props, &model) < 0)
        return NULL;

    qemuCaps = virQEMUCapsNew();

    virQEMUCapsSet(qemuCaps, QEMU_CAPS_KVM);
    if (data->flags == JSON_MODELS ||
        data->flags == JSON_MODELS_REQUIRED)
        virQEMUCapsSet(qemuCaps, QEMU_CAPS_QUERY_CPU_DEFINITIONS);

    virQEMUCapsSetArch(qemuCaps, data->arch);
    virQEMUCapsSetCPUModelInfo(qemuCaps, VIR_DOMAIN_VIRT_KVM, model);
    model = NULL;

    if (virQEMUCapsProbeCPUDefinitionsTest(qemuCaps,
                                           qemuMonitorTestGetMonitor(testMon)) < 0)
        return NULL;

    return g_steal_pointer(&qemuCaps);
}


static int
cpuTestGetCPUModels(const struct data *data,
                    virDomainCapsCPUModels **models)
{
    g_autoptr(virQEMUCaps) qemuCaps = NULL;

    *models = NULL;

    if (data->flags != JSON_MODELS &&
        data->flags != JSON_MODELS_REQUIRED)
        return 0;

    if (!(qemuCaps = cpuTestMakeQEMUCaps(data)))
        return -1;

    *models = virQEMUCapsGetCPUModels(qemuCaps, VIR_DOMAIN_VIRT_KVM, NULL, NULL);

    return 0;
}

#else /* if WITH_QEMU */

static int
cpuTestGetCPUModels(const struct data *data,
                    virDomainCapsCPUModels **models)
{
    *models = NULL;

    if (data->flags == JSON_MODELS_REQUIRED)
        return EXIT_AM_SKIP;

    return 0;
}

#endif


static int
cpuTestCPUID(bool guest, const void *arg)
{
    const struct data *data = arg;
    g_autoptr(virCPUData) hostData = NULL;
    g_autofree char *hostFile = NULL;
    g_autofree char *host = NULL;
    g_autoptr(virCPUDef) cpu = NULL;
    g_autofree char *result = NULL;
    g_autoptr(virDomainCapsCPUModels) models = NULL;

    hostFile = g_strdup_printf("%s/cputestdata/%s-cpuid-%s.xml", abs_srcdir,
                               virArchToString(data->arch), data->host);

    virTestCaseMarkUsed(data->existingTestCases, hostFile);

    if (virTestLoadFile(hostFile, &host) < 0 ||
        !(hostData = virCPUDataParse(host)))
        return -1;

    cpu = virCPUDefNew();
    cpu->arch = hostData->arch;
    if (guest) {
        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 (guest) {
        int rc;

        rc = cpuTestGetCPUModels(data, &models);
        if (rc != 0)
            return rc;
    }

    if (cpuDecode(cpu, hostData, models) < 0)
        return -1;

    result = g_strdup_printf("cpuid-%s-%s", data->host, guest ? "guest" : "host");

    return cpuTestCompareXML(data->arch, cpu, result, data->existingTestCases);
}


static int
cpuTestCPUIDBaseline(const void *arg)
{
    const struct data *data = arg;
    int ret = -1;
    virCPUDef **cpus = NULL;
    virCPUDef *baseline = NULL;
    g_autofree char *result = NULL;
    size_t i;

    cpus = g_new0(virCPUDef *, data->ncpus);
    for (i = 0; i < data->ncpus; i++) {
        g_autofree char *name = NULL;

        name = g_strdup_printf("cpuid-%s-json", data->cpus[i]);
        if (!(cpus[i] = cpuTestLoadXML(data->arch, name,
                                       data->existingTestCases))) {
            goto cleanup;
        }
    }

    baseline = virCPUBaseline(data->arch, cpus, data->ncpus, NULL, NULL, false);
    if (!baseline)
        goto cleanup;

    result = g_strdup_printf("cpuid-baseline-%s", data->name);

    if (cpuTestCompareXML(data->arch, baseline, result,
                          data->existingTestCases) < 0) {
        goto cleanup;
    }

    for (i = 0; i < data->ncpus; i++) {
        virCPUCompareResult cmp;

        cmp = virCPUCompare(data->arch, cpus[i], baseline, false);
        if (cmp != VIR_CPU_COMPARE_SUPERSET &&
            cmp != VIR_CPU_COMPARE_IDENTICAL) {
            VIR_TEST_VERBOSE("\nbaseline CPU is incompatible with CPU %zu", i);
            VIR_TEST_VERBOSE("%74s", "... ");
            ret = -1;
            goto cleanup;
        }
    }

    ret = 0;

 cleanup:
    if (cpus) {
        for (i = 0; i < data->ncpus; i++)
            virCPUDefFree(cpus[i]);
        VIR_FREE(cpus);
    }
    virCPUDefFree(baseline);
    return ret;
}


static int
cpuTestHostCPUID(const void *arg)
{
    return cpuTestCPUID(false, arg);
}


static int
cpuTestGuestCPUID(const void *arg)
{
    return cpuTestCPUID(true, arg);
}


static int
cpuTestCompareSignature(const struct data *data,
                        virCPUData *hostData)
{
    g_autofree char *result = NULL;
    g_autofree char *sigStr = NULL;
    unsigned long signature;
    unsigned int family;
    unsigned int model;
    unsigned int stepping;

    signature = virCPUx86DataGetSignature(hostData, &family, &model, &stepping);

    result = g_strdup_printf("%s/cputestdata/%s-cpuid-%s.sig", abs_srcdir,
                             virArchToString(data->arch), data->host);

    sigStr = g_strdup_printf("%1$06lx\n" "family:   %2$3u (0x%2$02x)\n"
                             "model:    %3$3u (0x%3$02x)\n" "stepping: %4$3u (0x%4$02x)\n",
                             signature, family, model, stepping);

    virTestCaseMarkUsed(data->existingTestCases, result);
    return virTestCompareToFile(sigStr, result);
}


static int
cpuTestCPUIDSignature(const void *arg)
{
    const struct data *data = arg;
    g_autoptr(virCPUData) hostData = NULL;
    g_autofree char *hostFile = NULL;
    g_autofree char *host = NULL;

    hostFile = g_strdup_printf("%s/cputestdata/%s-cpuid-%s.xml", abs_srcdir,
                               virArchToString(data->arch), data->host);

    virTestCaseMarkUsed(data->existingTestCases, hostFile);

    if (virTestLoadFile(hostFile, &host) < 0 ||
        !(hostData = virCPUDataParse(host)))
        return -1;

    return cpuTestCompareSignature(data, hostData);
}


static int
cpuTestUpdateLiveCompare(virArch arch,
                         virCPUDef *actual,
                         virCPUDef *expected)
{
    size_t i, j;
    int ret = 0;

    if (virCPUExpandFeatures(arch, actual) < 0 ||
        virCPUExpandFeatures(arch, expected) < 0)
        return -1;

    if (STRNEQ(actual->model, expected->model)) {
        VIR_TEST_VERBOSE("Actual CPU model '%s', expected '%s'",
                         actual->model, expected->model);
        return -1;
    }

    i = j = 0;
    while (i < actual->nfeatures || j < expected->nfeatures) {
        virCPUFeatureDef *featAct = NULL;
        virCPUFeatureDef *featExp = NULL;
        int cmp;

        if (i < actual->nfeatures)
            featAct = actual->features + i;

        if (j < expected->nfeatures)
            featExp = expected->features + j;

        /*
         * Act < Exp => cmp < 0 (missing entry in Exp)
         * Act = Exp => cmp = 0
         * Act > Exp => cmp > 0 (missing entry in Act)
         *
         * NULL > name for any name != NULL
         */
        if (featAct && featExp)
            cmp = strcmp(featAct->name, featExp->name);
        else
            cmp = featExp ? 1 : -1;

        if (cmp <= 0)
            i++;
        if (cmp >= 0)
            j++;

        /* Possible combinations of cmp, featAct->policy, and featExp->policy:
         *  cmp     Act     Exp     result
         * ---------------------------------
         *   0      dis     dis      ok
         *   0      dis     req     missing
         *   0      req     dis     extra
         *   0      req     req      ok
         * ---------------------------------
         *   -      dis      X       ok     # ignoring extra disabled features
         *   -      req      X      extra
         * ---------------------------------
         *   +       X      dis     extra
         *   +       X      req     missing
         */
        if ((cmp == 0 &&
             featAct->policy == VIR_CPU_FEATURE_DISABLE &&
             featExp->policy == VIR_CPU_FEATURE_REQUIRE) ||
            (cmp > 0 &&
             featExp->policy == VIR_CPU_FEATURE_REQUIRE)) {
            VIR_TEST_VERBOSE("Actual CPU lacks feature '%s'",
                             featExp->name);
            ret = -1;
            continue;
        }

        if ((cmp == 0 &&
             featAct->policy == VIR_CPU_FEATURE_REQUIRE &&
             featExp->policy == VIR_CPU_FEATURE_DISABLE) ||
            (cmp < 0 && featAct &&
             featAct->policy == VIR_CPU_FEATURE_REQUIRE) ||
            (cmp > 0 &&
             featExp->policy == VIR_CPU_FEATURE_DISABLE)) {
            VIR_TEST_VERBOSE("Actual CPU has extra feature '%s'",
                             cmp <= 0 ? featAct->name : featExp->name);
            ret = -1;
        }
    }

    return ret;
}


static int
cpuTestUpdateLive(const void *arg)
{
    const struct data *data = arg;
    g_autofree char *cpuFile = NULL;
    g_autoptr(virCPUDef) cpu = NULL;
    g_autofree char *enabledFile = NULL;
    g_autofree char *enabled = NULL;
    g_autoptr(virCPUData) enabledData = NULL;
    g_autofree char *disabledFile = NULL;
    g_autofree char *disabled = NULL;
    g_autoptr(virCPUData) disabledData = NULL;
    g_autofree char *expectedFile = NULL;
    g_autoptr(virCPUDef) expected = NULL;
    g_autoptr(virDomainCapsCPUModels) hvModels = NULL;
    g_autoptr(virDomainCapsCPUModels) models = NULL;

    cpuFile = g_strdup_printf("cpuid-%s-guest", data->host);
    if (!(cpu = cpuTestLoadXML(data->arch, cpuFile, data->existingTestCases)))
        return -1;

    enabledFile = g_strdup_printf("%s/cputestdata/%s-cpuid-%s-enabled.xml",
                                  abs_srcdir, virArchToString(data->arch), data->host);
    virTestCaseMarkUsed(data->existingTestCases, enabledFile);
    if (virTestLoadFile(enabledFile, &enabled) < 0 ||
        !(enabledData = virCPUDataParse(enabled)))
        return -1;

    disabledFile = g_strdup_printf("%s/cputestdata/%s-cpuid-%s-disabled.xml",
                                   abs_srcdir, virArchToString(data->arch), data->host);
    virTestCaseMarkUsed(data->existingTestCases, disabledFile);
    if (virTestLoadFile(disabledFile, &disabled) < 0 ||
        !(disabledData = virCPUDataParse(disabled)))
        return -1;

    expectedFile = g_strdup_printf("cpuid-%s-json", data->host);
    if (!(expected = cpuTestLoadXML(data->arch, expectedFile,
                                    data->existingTestCases))) {
        return -1;
    }

    /* In case the host CPU signature does not exactly match any CPU model in
     * src/cpu_map, the CPU model we detect from CPUID may differ from the one
     * we compute by asking QEMU. Since this test expands both CPU models and
     * compares their features, we can try to translate the 'actual' CPU to
     * use the CPU model from 'expected'.
     */
    if (STRNEQ(cpu->model, expected->model)) {
        virDomainCapsCPUModel *hvModel;
        char **blockers = NULL;
        virDomainCapsCPUUsable usable = VIR_DOMCAPS_CPU_USABLE_UNKNOWN;
        int rc;

        if (!(models = virDomainCapsCPUModelsNew(0)))
            return -1;

        rc = cpuTestGetCPUModels(data, &hvModels);
        if (rc != 0)
            return rc;

        hvModel = virDomainCapsCPUModelsGet(hvModels, expected->model);

        if (hvModel) {
            blockers = hvModel->blockers;
            usable = hvModel->usable;
        }

        virDomainCapsCPUModelsAdd(models, expected->model,
                                  usable, blockers, false,
                                  expected->vendor, NULL);

        cpu->fallback = VIR_CPU_FALLBACK_ALLOW;
        ignore_value(virCPUTranslate(data->arch, cpu, models));
        cpu->fallback = VIR_CPU_FALLBACK_FORBID;
    }

    if (virCPUUpdateLive(data->arch, cpu, enabledData, disabledData) < 0)
        return -1;

    return cpuTestUpdateLiveCompare(data->arch, cpu, expected);
}


#if WITH_QEMU
static int
cpuTestJSONCPUID(const void *arg)
{
    const struct data *data = arg;
    g_autoptr(virQEMUCaps) qemuCaps = NULL;
    g_autoptr(virCPUDef) cpu = NULL;
    g_autofree char *result = NULL;

    result = g_strdup_printf("cpuid-%s-json", data->host);

    if (!(qemuCaps = cpuTestMakeQEMUCaps(data)))
        return -1;

    cpu = virCPUDefNew();
    cpu->arch = data->arch;
    cpu->type = VIR_CPU_TYPE_GUEST;
    cpu->match = VIR_CPU_MATCH_EXACT;
    cpu->fallback = VIR_CPU_FALLBACK_FORBID;

    if (virQEMUCapsInitCPUModel(qemuCaps, VIR_DOMAIN_VIRT_KVM, cpu, false) != 0)
        return -1;

    return cpuTestCompareXML(data->arch, cpu, result, data->existingTestCases);
}


static int
cpuTestJSONSignature(const void *arg)
{
    const struct data *data = arg;
    g_autoptr(virQEMUCaps) qemuCaps = NULL;
    g_autoptr(virCPUData) hostData = NULL;
    qemuMonitorCPUModelInfo *modelInfo;

    if (!(qemuCaps = cpuTestMakeQEMUCaps(data)))
        return -1;

    modelInfo = virQEMUCapsGetCPUModelInfo(qemuCaps, VIR_DOMAIN_VIRT_KVM);
    if (!(hostData = virQEMUCapsGetCPUModelX86Data(qemuCaps, modelInfo, false)))
        return -1;

    return cpuTestCompareSignature(data, hostData);
}
#endif


static const char *model486_list[]   = { "486", NULL };
static const char *nomodel_list[]    = { "nomodel", NULL };
static const char *models_list[]     = { "qemu64", "core2duo", "Nehalem", NULL };
static const char *haswell_list[]    = { "SandyBridge", "Haswell", NULL };
static const char *ppc_models_list[] = { "POWER6", "POWER7", "POWER8", NULL };

static virDomainCapsCPUModels *
cpuTestInitModels(const char **list)
{
    virDomainCapsCPUModels *cpus;
    const char **model;

    if (!(cpus = virDomainCapsCPUModelsNew(0)))
        return NULL;

    for (model = list; *model; model++) {
        virDomainCapsCPUModelsAdd(cpus, *model,
                                  VIR_DOMCAPS_CPU_USABLE_UNKNOWN,
                                  NULL, false, NULL, NULL);
    }

    return cpus;
}


static bool
testCaseEnumerate(struct dirent *ent)
{
    return virStringHasSuffix(ent->d_name, ".xml") ||
#if WITH_QEMU
        virStringHasSuffix(ent->d_name, ".json") ||
#endif
        0;
}


static int
mymain(void)
{
    g_autoptr(GHashTable) existingTestCases = NULL;
    virDomainCapsCPUModels *model486 = NULL;
    virDomainCapsCPUModels *nomodel = NULL;
    virDomainCapsCPUModels *models = NULL;
    virDomainCapsCPUModels *haswell = NULL;
    virDomainCapsCPUModels *ppc_models = NULL;
    int ret = 0;

    if (virTestEnumerateTestCases(abs_srcdir "/cputestdata",
                                  testCaseEnumerate,
                                  &existingTestCases) < 0) {
        return EXIT_FAILURE;
    }

#if WITH_QEMU
    if (qemuTestDriverInit(&driver) < 0)
        return EXIT_FAILURE;

    virEventRegisterDefaultImpl();
#endif

    if (!(model486 = cpuTestInitModels(model486_list)) ||
        !(nomodel = cpuTestInitModels(nomodel_list)) ||
        !(models = cpuTestInitModels(models_list)) ||
        !(haswell = cpuTestInitModels(haswell_list)) ||
        !(ppc_models = cpuTestInitModels(ppc_models_list))) {
        ret = -1;
        goto cleanup;
    }

#define DO_TEST(arch, api, name, host, cpu, cpus, ncpus, \
                models, flags, result) \
    do { \
        struct data data = { \
            arch, host, cpu, models, \
            models == NULL ? NULL : #models, \
            cpus, ncpus, flags, result, \
            existingTestCases \
        }; \
        g_autofree char *testLabel = NULL; \
 \
        testLabel = g_strdup_printf("%s(%s): %s", #api, \
                                    virArchToString(arch), name); \
 \
        virTestRunLog(&ret, testLabel, api, &data); \
    } while (0)

#define DO_TEST_COMPARE(arch, host, cpu, result) \
    DO_TEST(arch, cpuTestCompare, \
            host "/" cpu " (" #result ")", \
            host, cpu, NULL, 0, NULL, 0, result)

#define DO_TEST_UPDATE_ONLY(arch, host, cpu) \
    DO_TEST(arch, cpuTestUpdate, \
            cpu " on " host, \
            host, cpu, NULL, 0, NULL, 0, 0)

#define DO_TEST_UPDATE(arch, host, cpu, result) \
    do { \
        DO_TEST_UPDATE_ONLY(arch, host, cpu); \
        DO_TEST_COMPARE(arch, host, host "+" cpu, result); \
    } while (0)

#define DO_TEST_BASELINE(arch, name, flags, result) \
    do { \
        const char *suffix = ""; \
        g_autofree char *label = NULL; \
        if ((flags) & VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES) \
            suffix = " (expanded)"; \
        if ((flags) & VIR_CONNECT_BASELINE_CPU_MIGRATABLE) \
            suffix = " (migratable)"; \
        label = g_strdup_printf("%s%s", name, suffix); \
        DO_TEST(arch, cpuTestBaseline, label, NULL, \
                "baseline-" name, NULL, 0, NULL, flags, result); \
    } while (0)

#define DO_TEST_HASFEATURE(arch, host, feature, result) \
    DO_TEST(arch, cpuTestHasFeature, \
            host "/" feature " (" #result ")", \
            host, feature, NULL, 0, NULL, 0, result)

#define DO_TEST_VALIDATEFEATURES(arch, name, result) \
    DO_TEST(arch, cpuTestValidateFeatures, name, \
            NULL, name, NULL, 0, NULL, 0, result)

#define DO_TEST_GUESTCPU(arch, host, cpu, models, result) \
    DO_TEST(arch, cpuTestGuestCPU, \
            host "/" cpu " (" #models ")", \
            host, cpu, NULL, 0, models, 0, result)

#if WITH_QEMU
# define DO_TEST_JSON(arch, host, json) \
    do { \
        if (json != JSON_NONE) { \
            DO_TEST(arch, cpuTestJSONCPUID, host, host, \
                    NULL, NULL, 0, NULL, json, 0); \
            DO_TEST(arch, cpuTestJSONSignature, host, host, \
                    NULL, NULL, 0, NULL, 0, 0); \
        } \
    } while (0)
#else
# define DO_TEST_JSON(arch, host, json)
#endif

#define DO_TEST_CPUID(arch, host, json) \
    do { \
        DO_TEST(arch, cpuTestHostCPUID, host, host, \
                NULL, NULL, 0, NULL, 0, 0); \
        DO_TEST(arch, cpuTestGuestCPUID, host, host, \
                NULL, NULL, 0, NULL, json, 0); \
        DO_TEST(arch, cpuTestCPUIDSignature, host, host, \
                NULL, NULL, 0, NULL, 0, 0); \
        DO_TEST_JSON(arch, host, json); \
        if (json != JSON_NONE) { \
            DO_TEST(arch, cpuTestUpdateLive, host, host, \
                    NULL, NULL, 0, NULL, json, 0); \
        } \
    } while (0)

#define DO_TEST_CPUID_BASELINE(arch, label, cpu1, cpu2) \
    do { \
        const char *cpus[] = {cpu1, cpu2}; \
        DO_TEST(arch, cpuTestCPUIDBaseline, \
                label " (" cpu1 ", " cpu2 ")", \
                NULL, label, cpus, 2, NULL, 0, 0); \
    } while (0)

    /* host to host comparison */
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "host", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "host-better", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "host-worse", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "host-amd-fake", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "host-incomp-arch", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "host-no-vendor", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host-no-vendor", "host", VIR_CPU_COMPARE_INCOMPATIBLE);

    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "host", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "host-better", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "host-worse", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "host-incomp-arch", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "host-no-vendor", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host-no-vendor", "host", VIR_CPU_COMPARE_INCOMPATIBLE);

    /* guest to host comparison */
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "bogus-model", VIR_CPU_COMPARE_ERROR);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "bogus-feature", VIR_CPU_COMPARE_ERROR);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "min", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "pentium3", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "exact", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "exact-forbid", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "exact-forbid-extra", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "exact-disable", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "exact-disable2", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "exact-disable-extra", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "exact-require", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "exact-require-extra", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "exact-force", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "strict", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "strict-full", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "strict-disable", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "strict-force-extra", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "guest", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host", "pentium3-amd", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host-amd", "pentium3-amd", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host-worse", "penryn-force", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_X86_64, "host-SandyBridge", "exact-force-Haswell", VIR_CPU_COMPARE_IDENTICAL);

    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "guest-strict", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "guest-exact", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "guest-legacy", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "guest-legacy-incompatible", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "guest-legacy-bad", VIR_CPU_COMPARE_ERROR);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "guest-compat-none", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "guest-compat-valid", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "guest-compat-bad", VIR_CPU_COMPARE_ERROR);
    DO_TEST_COMPARE(VIR_ARCH_PPC64, "host", "guest-compat-incompatible", VIR_CPU_COMPARE_INCOMPATIBLE);

    /* guest updates for migration
     * automatically compares host CPU with the result */
    DO_TEST_UPDATE(VIR_ARCH_X86_64, "host", "min", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_UPDATE(VIR_ARCH_X86_64, "host", "pentium3", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_UPDATE(VIR_ARCH_X86_64, "host", "guest", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_UPDATE(VIR_ARCH_X86_64, "host", "host-model", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_UPDATE(VIR_ARCH_X86_64, "host", "host-model-nofallback", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_UPDATE(VIR_ARCH_X86_64, "host-invtsc", "host-model", VIR_CPU_COMPARE_SUPERSET);
    DO_TEST_UPDATE_ONLY(VIR_ARCH_X86_64, "host", "host-passthrough");
    DO_TEST_UPDATE_ONLY(VIR_ARCH_X86_64, "host", "host-passthrough-features");

    DO_TEST_UPDATE(VIR_ARCH_PPC64, "host", "guest", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_UPDATE(VIR_ARCH_PPC64, "host", "guest-nofallback", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_UPDATE(VIR_ARCH_PPC64, "host", "guest-legacy", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_UPDATE(VIR_ARCH_PPC64, "host", "guest-legacy-incompatible", VIR_CPU_COMPARE_INCOMPATIBLE);
    DO_TEST_UPDATE(VIR_ARCH_PPC64, "host", "guest-legacy-bad", VIR_CPU_COMPARE_ERROR);
    DO_TEST_UPDATE(VIR_ARCH_PPC64, "host", "guest-compat-none", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_UPDATE(VIR_ARCH_PPC64, "host", "guest-compat-valid", VIR_CPU_COMPARE_IDENTICAL);
    DO_TEST_UPDATE(VIR_ARCH_PPC64, "host", "guest-compat-bad", VIR_CPU_COMPARE_ERROR);
    DO_TEST_UPDATE(VIR_ARCH_PPC64, "host", "guest-compat-incompatible", VIR_CPU_COMPARE_INCOMPATIBLE);

    /* computing baseline CPUs */
    DO_TEST_BASELINE(VIR_ARCH_X86_64, "incompatible-vendors", 0, -1);
    DO_TEST_BASELINE(VIR_ARCH_X86_64, "no-vendor", 0, 0);
    DO_TEST_BASELINE(VIR_ARCH_X86_64, "some-vendors", 0, 0);
    DO_TEST_BASELINE(VIR_ARCH_X86_64, "simple", 0, 0);
    DO_TEST_BASELINE(VIR_ARCH_X86_64, "simple", VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES, 0);
    DO_TEST_BASELINE(VIR_ARCH_X86_64, "features", 0, 0);
    DO_TEST_BASELINE(VIR_ARCH_X86_64, "features", VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES, 0);
    DO_TEST_BASELINE(VIR_ARCH_X86_64, "Westmere+Nehalem", 0, 0);
    DO_TEST_BASELINE(VIR_ARCH_X86_64, "Westmere+Nehalem", VIR_CONNECT_BASELINE_CPU_MIGRATABLE, 0);

    DO_TEST_BASELINE(VIR_ARCH_PPC64, "incompatible-vendors", 0, -1);
    DO_TEST_BASELINE(VIR_ARCH_PPC64, "no-vendor", 0, 0);
    DO_TEST_BASELINE(VIR_ARCH_PPC64, "incompatible-models", 0, -1);
    DO_TEST_BASELINE(VIR_ARCH_PPC64, "same-model", 0, 0);
    DO_TEST_BASELINE(VIR_ARCH_PPC64, "legacy", 0, -1);

    /* CPU features */
    DO_TEST_HASFEATURE(VIR_ARCH_X86_64, "host", "vmx", YES);
    DO_TEST_HASFEATURE(VIR_ARCH_X86_64, "host", "lm", YES);
    DO_TEST_HASFEATURE(VIR_ARCH_X86_64, "host", "sse4.1", YES);
    DO_TEST_HASFEATURE(VIR_ARCH_X86_64, "host", "3dnowext", NO);
    DO_TEST_HASFEATURE(VIR_ARCH_X86_64, "host", "skinit", NO);
    DO_TEST_HASFEATURE(VIR_ARCH_X86_64, "host", "foo", FAIL);

    /* computing guest data and decoding the data into a guest CPU XML */
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host", "guest", NULL, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host-better", "pentium3", NULL, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host-worse", "guest", NULL, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host", "strict-force-extra", NULL, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host", "penryn-force", NULL, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host", "guest", model486, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host", "guest", models, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host", "guest", nomodel, -1);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host", "guest-nofallback", models, -1);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host", "host+host-model", models, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host", "host+host-model-nofallback", models, -1);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host-Haswell-noTSX", "Haswell", haswell, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host-Haswell-noTSX", "Haswell-noTSX", haswell, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host-Haswell-noTSX", "Haswell-noTSX-nofallback", haswell, -1);
    DO_TEST_GUESTCPU(VIR_ARCH_X86_64, "host-Haswell-noTSX", "Haswell-noTSX", NULL, 0);

    DO_TEST_GUESTCPU(VIR_ARCH_PPC64, "host", "guest", ppc_models, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_PPC64, "host", "guest-nofallback", ppc_models, -1);
    DO_TEST_GUESTCPU(VIR_ARCH_PPC64, "host", "guest-legacy", ppc_models, 0);
    DO_TEST_GUESTCPU(VIR_ARCH_PPC64, "host", "guest-legacy-incompatible", ppc_models, -1);
    DO_TEST_GUESTCPU(VIR_ARCH_PPC64, "host", "guest-legacy-bad", ppc_models, -1);

    DO_TEST_CPUID(VIR_ARCH_X86_64, "A10-5800K", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Atom-D510", JSON_NONE);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Atom-N450", JSON_NONE);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Atom-P5362", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Atom-P5362-2", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i5-650", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i5-2500", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i5-2540M", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i5-4670T", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i5-6600", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-1270P", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-1365U", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-2600", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-2600-xsaveopt", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-3520M", JSON_NONE);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-3740QM", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-3770", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-4600U", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-4510U", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-5600U", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-5600U-arat", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-5600U-ibrs", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-7600U", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-7700", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-8550U", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-8700", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core2-E6850", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Core2-Q9500", JSON_NONE);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Hygon-C86-7185-32-core", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-7601-32-Core", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-7601-32-Core-ibpb", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-7502-32-Core", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-7713-64-Core", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-9334-32-Core", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "FX-8150", JSON_NONE);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Opteron-1352", JSON_NONE);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Opteron-2350", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Opteron-6234", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Opteron-6282", JSON_NONE);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Pentium-P6100", JSON_NONE);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Phenom-B95", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Ryzen-5-5500U-with-Radeon-Graphics", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Ryzen-7-1800X-Eight-Core", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Ryzen-9-3900X-12-Core", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Ryzen-9-9950X-16-Core", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-5110", JSON_NONE);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-6731E", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-6788P", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-Bronze-3408U", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E3-1225-v5", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E3-1245-v5", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E3-1270-v5", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E5-2609-v3", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E5-2623-v4", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E5-2630-v3", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E5-2630-v4", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E5-2650", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E5-2650-v3", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E5-2650-v4", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E7-4820", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E7-4830", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E7-8890-v3", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-E7540", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-Gold-5115", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-Gold-6130", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-Gold-6148", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-Gold-6152", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-Gold-6530", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-Platinum-8268", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-Platinum-9242", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-Silver-4214R", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-W3520", JSON_HOST);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-w7-3465X", JSON_MODELS_REQUIRED);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-X5460", JSON_NONE);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Ice-Lake-Server", JSON_MODELS);
    DO_TEST_CPUID(VIR_ARCH_X86_64, "Cooperlake", JSON_MODELS);

    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Ryzen+Rome",
                           "Ryzen-7-1800X-Eight-Core", "Ryzen-9-3900X-12-Core");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "EPYC+Rome",
                           "EPYC-7601-32-Core", "EPYC-7502-32-Core");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Haswell-noTSX-IBRS+Skylake",
                           "Xeon-E5-2609-v3", "Xeon-Gold-6148");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Haswell-noTSX-IBRS+Skylake-IBRS",
                           "Xeon-E5-2609-v3", "Xeon-Gold-6130");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Broadwell-IBRS+Cascadelake",
                           "Xeon-E5-2623-v4", "Xeon-Platinum-8268");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Cascadelake+Skylake-IBRS",
                           "Xeon-Platinum-8268", "Xeon-Gold-6130");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Cascadelake+Skylake",
                           "Xeon-Platinum-9242", "Xeon-Gold-6148");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Cascadelake+Icelake",
                           "Xeon-Platinum-9242", "Ice-Lake-Server");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Cooperlake+Icelake",
                           "Cooperlake", "Ice-Lake-Server");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Cooperlake+Cascadelake",
                           "Cooperlake", "Xeon-Platinum-9242");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Skylake-Client+Server",
                           "Core-i5-6600", "Xeon-Gold-6148");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Haswell-noTSX-IBRS+Broadwell",
                           "Xeon-E5-2609-v3", "Xeon-E5-2650-v4");
    DO_TEST_CPUID_BASELINE(VIR_ARCH_X86_64, "Haswell+Skylake",
                           "Xeon-E7-8890-v3", "Xeon-Gold-5115");

    DO_TEST_VALIDATEFEATURES(VIR_ARCH_AARCH64, "guest", 0);

 cleanup:
#if WITH_QEMU
    qemuTestDriverFree(&driver);
#endif

    virObjectUnref(model486);
    virObjectUnref(nomodel);
    virObjectUnref(models);
    virObjectUnref(haswell);
    virObjectUnref(ppc_models);

    if (virTestCheckUnusedTestCases(existingTestCases) < 0)
        ret = -1;

    return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

VIR_TEST_MAIN(mymain)
