qapi: drop the sentinel in enum array
[qemu/armbru.git] / tests / qmp-test.c
blobc5a5c10b417b077c4c275eb20559ea2ae9112baa
1 /*
2 * QMP protocol test cases
4 * Copyright (c) 2017 Red Hat Inc.
6 * Authors:
7 * Markus Armbruster <armbru@redhat.com>,
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.
13 #include "qemu/osdep.h"
14 #include "libqtest.h"
15 #include "qapi-visit.h"
16 #include "qapi/error.h"
17 #include "qapi/qobject-input-visitor.h"
18 #include "qapi/util.h"
19 #include "qapi/visitor.h"
21 const char common_args[] = "-nodefaults -machine none";
23 static const char *get_error_class(QDict *resp)
25 QDict *error = qdict_get_qdict(resp, "error");
26 const char *desc = qdict_get_try_str(error, "desc");
28 g_assert(desc);
29 return error ? qdict_get_try_str(error, "class") : NULL;
32 static void test_version(QObject *version)
34 Visitor *v;
35 VersionInfo *vinfo;
37 g_assert(version);
38 v = qobject_input_visitor_new(version);
39 visit_type_VersionInfo(v, "version", &vinfo, &error_abort);
40 qapi_free_VersionInfo(vinfo);
41 visit_free(v);
44 static void test_malformed(void)
46 QDict *resp;
48 /* Not even a dictionary */
49 resp = qmp("null");
50 g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
51 QDECREF(resp);
53 /* No "execute" key */
54 resp = qmp("{}");
55 g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
56 QDECREF(resp);
58 /* "execute" isn't a string */
59 resp = qmp("{ 'execute': true }");
60 g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
61 QDECREF(resp);
63 /* "arguments" isn't a dictionary */
64 resp = qmp("{ 'execute': 'no-such-cmd', 'arguments': [] }");
65 g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
66 QDECREF(resp);
68 /* extra key */
69 resp = qmp("{ 'execute': 'no-such-cmd', 'extra': true }");
70 g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
71 QDECREF(resp);
74 static void test_qmp_protocol(void)
76 QDict *resp, *q, *ret;
77 QList *capabilities;
79 global_qtest = qtest_init_without_qmp_handshake(common_args);
81 /* Test greeting */
82 resp = qmp_receive();
83 q = qdict_get_qdict(resp, "QMP");
84 g_assert(q);
85 test_version(qdict_get(q, "version"));
86 capabilities = qdict_get_qlist(q, "capabilities");
87 g_assert(capabilities && qlist_empty(capabilities));
88 QDECREF(resp);
90 /* Test valid command before handshake */
91 resp = qmp("{ 'execute': 'query-version' }");
92 g_assert_cmpstr(get_error_class(resp), ==, "CommandNotFound");
93 QDECREF(resp);
95 /* Test malformed commands before handshake */
96 test_malformed();
98 /* Test handshake */
99 resp = qmp("{ 'execute': 'qmp_capabilities' }");
100 ret = qdict_get_qdict(resp, "return");
101 g_assert(ret && !qdict_size(ret));
102 QDECREF(resp);
104 /* Test repeated handshake */
105 resp = qmp("{ 'execute': 'qmp_capabilities' }");
106 g_assert_cmpstr(get_error_class(resp), ==, "CommandNotFound");
107 QDECREF(resp);
109 /* Test valid command */
110 resp = qmp("{ 'execute': 'query-version' }");
111 test_version(qdict_get(resp, "return"));
112 QDECREF(resp);
114 /* Test malformed commands */
115 test_malformed();
117 /* Test 'id' */
118 resp = qmp("{ 'execute': 'query-name', 'id': 'cookie#1' }");
119 ret = qdict_get_qdict(resp, "return");
120 g_assert(ret);
121 g_assert_cmpstr(qdict_get_try_str(resp, "id"), ==, "cookie#1");
122 QDECREF(resp);
124 /* Test command failure with 'id' */
125 resp = qmp("{ 'execute': 'human-monitor-command', 'id': 2 }");
126 g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
127 g_assert_cmpint(qdict_get_int(resp, "id"), ==, 2);
128 QDECREF(resp);
130 qtest_end();
133 static int query_error_class(const char *cmd)
135 static struct {
136 const char *cmd;
137 int err_class;
138 } fails[] = {
139 /* Success depends on build configuration: */
140 #ifndef CONFIG_SPICE
141 { "query-spice", ERROR_CLASS_COMMAND_NOT_FOUND },
142 #endif
143 #ifndef CONFIG_VNC
144 { "query-vnc", ERROR_CLASS_GENERIC_ERROR },
145 { "query-vnc-servers", ERROR_CLASS_GENERIC_ERROR },
146 #endif
147 #ifndef CONFIG_REPLICATION
148 { "query-xen-replication-status", ERROR_CLASS_COMMAND_NOT_FOUND },
149 #endif
150 /* Likewise, and require special QEMU command-line arguments: */
151 { "query-acpi-ospm-status", ERROR_CLASS_GENERIC_ERROR },
152 { "query-balloon", ERROR_CLASS_DEVICE_NOT_ACTIVE },
153 { "query-hotpluggable-cpus", ERROR_CLASS_GENERIC_ERROR },
154 { "query-vm-generation-id", ERROR_CLASS_GENERIC_ERROR },
155 { NULL, -1 }
157 int i;
159 for (i = 0; fails[i].cmd; i++) {
160 if (!strcmp(cmd, fails[i].cmd)) {
161 return fails[i].err_class;
164 return -1;
167 static void test_query(const void *data)
169 const char *cmd = data;
170 int expected_error_class = query_error_class(cmd);
171 QDict *resp, *error;
172 const char *error_class;
174 qtest_start(common_args);
176 resp = qmp("{ 'execute': %s }", cmd);
177 error = qdict_get_qdict(resp, "error");
178 error_class = error ? qdict_get_str(error, "class") : NULL;
180 if (expected_error_class < 0) {
181 g_assert(qdict_haskey(resp, "return"));
182 } else {
183 g_assert(error);
184 g_assert_cmpint(qapi_enum_parse(&QapiErrorClass_lookup, error_class,
185 -1, &error_abort),
186 ==, expected_error_class);
188 QDECREF(resp);
190 qtest_end();
193 static bool query_is_blacklisted(const char *cmd)
195 const char *blacklist[] = {
196 /* Not actually queries: */
197 "add-fd",
198 /* Success depends on target arch: */
199 "query-cpu-definitions", /* arm, i386, ppc, s390x */
200 "query-gic-capabilities", /* arm */
201 /* Success depends on target-specific build configuration: */
202 "query-pci", /* CONFIG_PCI */
203 NULL
205 int i;
207 for (i = 0; blacklist[i]; i++) {
208 if (!strcmp(cmd, blacklist[i])) {
209 return true;
212 return false;
215 typedef struct {
216 SchemaInfoList *list;
217 GHashTable *hash;
218 } QmpSchema;
220 static void qmp_schema_init(QmpSchema *schema)
222 QDict *resp;
223 Visitor *qiv;
224 SchemaInfoList *tail;
226 qtest_start(common_args);
227 resp = qmp("{ 'execute': 'query-qmp-schema' }");
229 qiv = qobject_input_visitor_new(qdict_get(resp, "return"));
230 visit_type_SchemaInfoList(qiv, NULL, &schema->list, &error_abort);
231 visit_free(qiv);
233 QDECREF(resp);
234 qtest_end();
236 schema->hash = g_hash_table_new(g_str_hash, g_str_equal);
238 /* Build @schema: hash table mapping entity name to SchemaInfo */
239 for (tail = schema->list; tail; tail = tail->next) {
240 g_hash_table_insert(schema->hash, tail->value->name, tail->value);
244 static SchemaInfo *qmp_schema_lookup(QmpSchema *schema, const char *name)
246 return g_hash_table_lookup(schema->hash, name);
249 static void qmp_schema_cleanup(QmpSchema *schema)
251 qapi_free_SchemaInfoList(schema->list);
252 g_hash_table_destroy(schema->hash);
255 static bool object_type_has_mandatory_members(SchemaInfo *type)
257 SchemaInfoObjectMemberList *tail;
259 g_assert(type->meta_type == SCHEMA_META_TYPE_OBJECT);
261 for (tail = type->u.object.members; tail; tail = tail->next) {
262 if (!tail->value->has_q_default) {
263 return true;
267 return false;
270 static void add_query_tests(QmpSchema *schema)
272 SchemaInfoList *tail;
273 SchemaInfo *si, *arg_type, *ret_type;
274 const char *test_name;
276 /* Test the query-like commands */
277 for (tail = schema->list; tail; tail = tail->next) {
278 si = tail->value;
279 if (si->meta_type != SCHEMA_META_TYPE_COMMAND) {
280 continue;
283 if (query_is_blacklisted(si->name)) {
284 continue;
287 arg_type = qmp_schema_lookup(schema, si->u.command.arg_type);
288 if (object_type_has_mandatory_members(arg_type)) {
289 continue;
292 ret_type = qmp_schema_lookup(schema, si->u.command.ret_type);
293 if (ret_type->meta_type == SCHEMA_META_TYPE_OBJECT
294 && !ret_type->u.object.members) {
295 continue;
298 test_name = g_strdup_printf("qmp/%s", si->name);
299 qtest_add_data_func(test_name, si->name, test_query);
303 int main(int argc, char *argv[])
305 QmpSchema schema;
306 int ret;
308 g_test_init(&argc, &argv, NULL);
310 qtest_add_func("qmp/protocol", test_qmp_protocol);
311 qmp_schema_init(&schema);
312 add_query_tests(&schema);
314 ret = g_test_run();
316 qmp_schema_cleanup(&schema);
317 return ret;