qemu/tests/qtest/qmp-cmd-test.c
<<
>>
Prefs
   1/*
   2 * QMP command test cases
   3 *
   4 * Copyright (c) 2017 Red Hat Inc.
   5 *
   6 * Authors:
   7 *  Markus Armbruster <armbru@redhat.com>
   8 *
   9 * This work is licensed under the terms of the GNU GPL, version 2 or later.
  10 * See the COPYING file in the top-level directory.
  11 */
  12
  13#include "qemu/osdep.h"
  14#include "libqos/libqtest.h"
  15#include "qapi/error.h"
  16#include "qapi/qapi-visit-introspect.h"
  17#include "qapi/qmp/qdict.h"
  18#include "qapi/qobject-input-visitor.h"
  19
  20const char common_args[] = "-nodefaults -machine none";
  21
  22/* Query smoke tests */
  23
  24static int query_error_class(const char *cmd)
  25{
  26    static struct {
  27        const char *cmd;
  28        int err_class;
  29    } fails[] = {
  30        /* Success depends on build configuration: */
  31#ifndef CONFIG_SPICE
  32        { "query-spice", ERROR_CLASS_COMMAND_NOT_FOUND },
  33#endif
  34#ifndef CONFIG_TCG
  35        { "query-replay", ERROR_CLASS_COMMAND_NOT_FOUND },
  36#endif
  37#ifndef CONFIG_VNC
  38        { "query-vnc", ERROR_CLASS_GENERIC_ERROR },
  39        { "query-vnc-servers", ERROR_CLASS_GENERIC_ERROR },
  40#endif
  41#ifndef CONFIG_REPLICATION
  42        { "query-xen-replication-status", ERROR_CLASS_COMMAND_NOT_FOUND },
  43#endif
  44        /* Likewise, and require special QEMU command-line arguments: */
  45        { "query-acpi-ospm-status", ERROR_CLASS_GENERIC_ERROR },
  46        { "query-balloon", ERROR_CLASS_DEVICE_NOT_ACTIVE },
  47        { "query-hotpluggable-cpus", ERROR_CLASS_GENERIC_ERROR },
  48        { "query-vm-generation-id", ERROR_CLASS_GENERIC_ERROR },
  49        { NULL, -1 }
  50    };
  51    int i;
  52
  53    for (i = 0; fails[i].cmd; i++) {
  54        if (!strcmp(cmd, fails[i].cmd)) {
  55            return fails[i].err_class;
  56        }
  57    }
  58    return -1;
  59}
  60
  61static void test_query(const void *data)
  62{
  63    const char *cmd = data;
  64    int expected_error_class = query_error_class(cmd);
  65    QDict *resp, *error;
  66    const char *error_class;
  67    QTestState *qts;
  68
  69    qts = qtest_init(common_args);
  70
  71    resp = qtest_qmp(qts, "{ 'execute': %s }", cmd);
  72    error = qdict_get_qdict(resp, "error");
  73    error_class = error ? qdict_get_str(error, "class") : NULL;
  74
  75    if (expected_error_class < 0) {
  76        g_assert(qdict_haskey(resp, "return"));
  77    } else {
  78        g_assert(error);
  79        g_assert_cmpint(qapi_enum_parse(&QapiErrorClass_lookup, error_class,
  80                                        -1, &error_abort),
  81                        ==, expected_error_class);
  82    }
  83    qobject_unref(resp);
  84
  85    qtest_quit(qts);
  86}
  87
  88static bool query_is_ignored(const char *cmd)
  89{
  90    const char *ignored[] = {
  91        /* Not actually queries: */
  92        "add-fd",
  93        /* Success depends on target arch: */
  94        "query-cpu-definitions",  /* arm, i386, ppc, s390x */
  95        "query-gic-capabilities", /* arm */
  96        /* Success depends on target-specific build configuration: */
  97        "query-pci",              /* CONFIG_PCI */
  98        /* Success depends on launching SEV guest */
  99        "query-sev-launch-measure",
 100        /* Success depends on Host or Hypervisor SEV support */
 101        "query-sev",
 102        "query-sev-capabilities",
 103        NULL
 104    };
 105    int i;
 106
 107    for (i = 0; ignored[i]; i++) {
 108        if (!strcmp(cmd, ignored[i])) {
 109            return true;
 110        }
 111    }
 112    return false;
 113}
 114
 115typedef struct {
 116    SchemaInfoList *list;
 117    GHashTable *hash;
 118} QmpSchema;
 119
 120static void qmp_schema_init(QmpSchema *schema)
 121{
 122    QDict *resp;
 123    Visitor *qiv;
 124    SchemaInfoList *tail;
 125    QTestState *qts;
 126
 127    qts = qtest_init(common_args);
 128
 129    resp = qtest_qmp(qts, "{ 'execute': 'query-qmp-schema' }");
 130
 131    qiv = qobject_input_visitor_new(qdict_get(resp, "return"));
 132    visit_type_SchemaInfoList(qiv, NULL, &schema->list, &error_abort);
 133    visit_free(qiv);
 134
 135    qobject_unref(resp);
 136    qtest_quit(qts);
 137
 138    schema->hash = g_hash_table_new(g_str_hash, g_str_equal);
 139
 140    /* Build @schema: hash table mapping entity name to SchemaInfo */
 141    for (tail = schema->list; tail; tail = tail->next) {
 142        g_hash_table_insert(schema->hash, tail->value->name, tail->value);
 143    }
 144}
 145
 146static SchemaInfo *qmp_schema_lookup(QmpSchema *schema, const char *name)
 147{
 148    return g_hash_table_lookup(schema->hash, name);
 149}
 150
 151static void qmp_schema_cleanup(QmpSchema *schema)
 152{
 153    qapi_free_SchemaInfoList(schema->list);
 154    g_hash_table_destroy(schema->hash);
 155}
 156
 157static bool object_type_has_mandatory_members(SchemaInfo *type)
 158{
 159    SchemaInfoObjectMemberList *tail;
 160
 161    g_assert(type->meta_type == SCHEMA_META_TYPE_OBJECT);
 162
 163    for (tail = type->u.object.members; tail; tail = tail->next) {
 164        if (!tail->value->has_q_default) {
 165            return true;
 166        }
 167    }
 168
 169    return false;
 170}
 171
 172static void add_query_tests(QmpSchema *schema)
 173{
 174    SchemaInfoList *tail;
 175    SchemaInfo *si, *arg_type, *ret_type;
 176    char *test_name;
 177
 178    /* Test the query-like commands */
 179    for (tail = schema->list; tail; tail = tail->next) {
 180        si = tail->value;
 181        if (si->meta_type != SCHEMA_META_TYPE_COMMAND) {
 182            continue;
 183        }
 184
 185        if (query_is_ignored(si->name)) {
 186            continue;
 187        }
 188
 189        arg_type = qmp_schema_lookup(schema, si->u.command.arg_type);
 190        if (object_type_has_mandatory_members(arg_type)) {
 191            continue;
 192        }
 193
 194        ret_type = qmp_schema_lookup(schema, si->u.command.ret_type);
 195        if (ret_type->meta_type == SCHEMA_META_TYPE_OBJECT
 196            && !ret_type->u.object.members) {
 197            continue;
 198        }
 199
 200        test_name = g_strdup_printf("qmp/%s", si->name);
 201        qtest_add_data_func(test_name, si->name, test_query);
 202        g_free(test_name);
 203    }
 204}
 205
 206static void test_object_add_failure_modes(void)
 207{
 208    QTestState *qts;
 209    QDict *resp;
 210
 211    /* attempt to create an object without props */
 212    qts = qtest_init(common_args);
 213    resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
 214                     " {'qom-type': 'memory-backend-ram', 'id': 'ram1' } }");
 215    g_assert_nonnull(resp);
 216    qmp_expect_error_and_unref(resp, "GenericError");
 217
 218    /* attempt to create an object without qom-type */
 219    resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
 220                     " {'id': 'ram1' } }");
 221    g_assert_nonnull(resp);
 222    qmp_expect_error_and_unref(resp, "GenericError");
 223
 224    /* attempt to delete an object that does not exist */
 225    resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
 226                     " {'id': 'ram1' } }");
 227    g_assert_nonnull(resp);
 228    qmp_expect_error_and_unref(resp, "GenericError");
 229
 230    /* attempt to create 2 objects with duplicate id */
 231    resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
 232                     " {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
 233                     " 'size': 1048576 } }");
 234    g_assert_nonnull(resp);
 235    g_assert(qdict_haskey(resp, "return"));
 236    qobject_unref(resp);
 237
 238    resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
 239                     " {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
 240                     " 'size': 1048576 } }");
 241    g_assert_nonnull(resp);
 242    qmp_expect_error_and_unref(resp, "GenericError");
 243
 244    /* delete ram1 object */
 245    resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
 246                     " {'id': 'ram1' } }");
 247    g_assert_nonnull(resp);
 248    g_assert(qdict_haskey(resp, "return"));
 249    qobject_unref(resp);
 250
 251    /* attempt to create an object with a property of a wrong type */
 252    resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
 253                     " {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
 254                     " 'size': '1048576' } }");
 255    g_assert_nonnull(resp);
 256    /* now do it right */
 257    qmp_expect_error_and_unref(resp, "GenericError");
 258
 259    resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
 260                     " {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
 261                     " 'size': 1048576 } }");
 262    g_assert_nonnull(resp);
 263    g_assert(qdict_haskey(resp, "return"));
 264    qobject_unref(resp);
 265
 266    /* delete ram1 object */
 267    resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
 268                     " {'id': 'ram1' } }");
 269    g_assert_nonnull(resp);
 270    g_assert(qdict_haskey(resp, "return"));
 271    qobject_unref(resp);
 272
 273    /* attempt to create an object without the id */
 274    resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
 275                     " {'qom-type': 'memory-backend-ram',"
 276                     " 'size': 1048576 } }");
 277    g_assert_nonnull(resp);
 278    qmp_expect_error_and_unref(resp, "GenericError");
 279
 280    /* now do it right */
 281    resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
 282                     " {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
 283                     " 'size': 1048576 } }");
 284    g_assert_nonnull(resp);
 285    g_assert(qdict_haskey(resp, "return"));
 286    qobject_unref(resp);
 287
 288    /* delete ram1 object */
 289    resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
 290                     " {'id': 'ram1' } }");
 291    g_assert_nonnull(resp);
 292    g_assert(qdict_haskey(resp, "return"));
 293    qobject_unref(resp);
 294
 295    /* attempt to set a non existing property */
 296    resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
 297                     " {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
 298                     " 'sized': 1048576 } }");
 299    g_assert_nonnull(resp);
 300    qmp_expect_error_and_unref(resp, "GenericError");
 301
 302    /* now do it right */
 303    resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
 304                     " {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
 305                     " 'size': 1048576 } }");
 306    g_assert_nonnull(resp);
 307    g_assert(qdict_haskey(resp, "return"));
 308    qobject_unref(resp);
 309
 310    /* delete ram1 object without id */
 311    resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
 312                     " {'ida': 'ram1' } }");
 313    g_assert_nonnull(resp);
 314    qobject_unref(resp);
 315
 316    /* delete ram1 object */
 317    resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
 318                     " {'id': 'ram1' } }");
 319    g_assert_nonnull(resp);
 320    g_assert(qdict_haskey(resp, "return"));
 321    qobject_unref(resp);
 322
 323    /* delete ram1 object that does not exist anymore*/
 324    resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
 325                     " {'id': 'ram1' } }");
 326    g_assert_nonnull(resp);
 327    qmp_expect_error_and_unref(resp, "GenericError");
 328
 329    qtest_quit(qts);
 330}
 331
 332int main(int argc, char *argv[])
 333{
 334    QmpSchema schema;
 335    int ret;
 336
 337    g_test_init(&argc, &argv, NULL);
 338
 339    qmp_schema_init(&schema);
 340    add_query_tests(&schema);
 341
 342    qtest_add_func("qmp/object-add-failure-modes",
 343                   test_object_add_failure_modes);
 344
 345    ret = g_test_run();
 346
 347    qmp_schema_cleanup(&schema);
 348    return ret;
 349}
 350