]>
Commit | Line | Data |
---|---|---|
2d1abb85 MA |
1 | /* |
2 | * Device introspection test cases | |
3 | * | |
4 | * Copyright (c) 2015 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 | /* | |
14 | * Covers QMP device-list-properties and HMP device_add help. We | |
15 | * currently don't check that their output makes sense, only that QEMU | |
16 | * survives. Useful since we've had an astounding number of crash | |
17 | * bugs around here. | |
18 | */ | |
19 | ||
681c28a3 | 20 | #include "qemu/osdep.h" |
2d1abb85 MA |
21 | #include "qemu-common.h" |
22 | #include "qapi/qmp/qstring.h" | |
1c6d75d5 | 23 | #include "qapi/qmp/qdict.h" |
47e6b297 | 24 | #include "qapi/qmp/qlist.h" |
a2ce7dbd | 25 | #include "libqos/libqtest.h" |
2d1abb85 MA |
26 | |
27 | const char common_args[] = "-nodefaults -machine none"; | |
28 | ||
39fb795a TH |
29 | static QList *qom_list_types(QTestState * qts, const char *implements, |
30 | bool abstract) | |
2d1abb85 MA |
31 | { |
32 | QDict *resp; | |
33 | QList *ret; | |
1c6d75d5 | 34 | QDict *args = qdict_new(); |
2d1abb85 | 35 | |
46f5ac20 | 36 | qdict_put_bool(args, "abstract", abstract); |
1c6d75d5 | 37 | if (implements) { |
46f5ac20 | 38 | qdict_put_str(args, "implements", implements); |
1c6d75d5 | 39 | } |
39fb795a TH |
40 | resp = qtest_qmp(qts, "{'execute': 'qom-list-types', 'arguments': %p }", |
41 | args); | |
2d1abb85 MA |
42 | g_assert(qdict_haskey(resp, "return")); |
43 | ret = qdict_get_qlist(resp, "return"); | |
cb3e7f08 MAL |
44 | qobject_ref(ret); |
45 | qobject_unref(resp); | |
2d1abb85 MA |
46 | return ret; |
47 | } | |
48 | ||
f86285c5 EH |
49 | /* Build a name -> ObjectTypeInfo index from a ObjectTypeInfo list */ |
50 | static QDict *qom_type_index(QList *types) | |
51 | { | |
52 | QDict *index = qdict_new(); | |
53 | QListEntry *e; | |
54 | ||
55 | QLIST_FOREACH_ENTRY(types, e) { | |
7dc847eb | 56 | QDict *d = qobject_to(QDict, qlist_entry_obj(e)); |
f86285c5 | 57 | const char *name = qdict_get_str(d, "name"); |
cb3e7f08 | 58 | qobject_ref(d); |
f86285c5 EH |
59 | qdict_put(index, name, d); |
60 | } | |
61 | return index; | |
62 | } | |
63 | ||
64 | /* Check if @parent is present in the parent chain of @type */ | |
65 | static bool qom_has_parent(QDict *index, const char *type, const char *parent) | |
66 | { | |
67 | while (type) { | |
68 | QDict *d = qdict_get_qdict(index, type); | |
69 | const char *p = d && qdict_haskey(d, "parent") ? | |
70 | qdict_get_str(d, "parent") : | |
71 | NULL; | |
72 | ||
73 | if (!strcmp(type, parent)) { | |
74 | return true; | |
75 | } | |
76 | ||
77 | type = p; | |
78 | } | |
79 | ||
80 | return false; | |
81 | } | |
82 | ||
dbb2a604 EH |
83 | /* Find an entry on a list returned by qom-list-types */ |
84 | static QDict *type_list_find(QList *types, const char *name) | |
85 | { | |
86 | QListEntry *e; | |
87 | ||
88 | QLIST_FOREACH_ENTRY(types, e) { | |
7dc847eb | 89 | QDict *d = qobject_to(QDict, qlist_entry_obj(e)); |
dbb2a604 EH |
90 | const char *ename = qdict_get_str(d, "name"); |
91 | if (!strcmp(ename, name)) { | |
92 | return d; | |
93 | } | |
94 | } | |
95 | ||
96 | return NULL; | |
97 | } | |
98 | ||
39fb795a | 99 | static QList *device_type_list(QTestState *qts, bool abstract) |
1c6d75d5 | 100 | { |
39fb795a | 101 | return qom_list_types(qts, "device", abstract); |
1c6d75d5 EH |
102 | } |
103 | ||
39fb795a | 104 | static void test_one_device(QTestState *qts, const char *type) |
2d1abb85 MA |
105 | { |
106 | QDict *resp; | |
e27bd498 PB |
107 | char *help, *escaped; |
108 | GRegex *comma; | |
d0685212 TH |
109 | |
110 | g_test_message("Testing device '%s'", type); | |
111 | ||
39fb795a TH |
112 | resp = qtest_qmp(qts, "{'execute': 'device-list-properties'," |
113 | " 'arguments': {'typename': %s}}", | |
edb1523d | 114 | type); |
cb3e7f08 | 115 | qobject_unref(resp); |
2d1abb85 | 116 | |
e27bd498 PB |
117 | comma = g_regex_new(",", 0, 0, NULL); |
118 | escaped = g_regex_replace_literal(comma, type, -1, 0, ",,", 0, NULL); | |
119 | g_regex_unref(comma); | |
120 | ||
121 | help = qtest_hmp(qts, "device_add \"%s,help\"", escaped); | |
2d1abb85 | 122 | g_free(help); |
e27bd498 | 123 | g_free(escaped); |
2d1abb85 MA |
124 | } |
125 | ||
126 | static void test_device_intro_list(void) | |
127 | { | |
128 | QList *types; | |
129 | char *help; | |
39fb795a | 130 | QTestState *qts; |
2d1abb85 | 131 | |
39fb795a | 132 | qts = qtest_init(common_args); |
2d1abb85 | 133 | |
39fb795a | 134 | types = device_type_list(qts, true); |
cb3e7f08 | 135 | qobject_unref(types); |
2d1abb85 | 136 | |
39fb795a | 137 | help = qtest_hmp(qts, "device_add help"); |
2d1abb85 MA |
138 | g_free(help); |
139 | ||
39fb795a | 140 | qtest_quit(qts); |
2d1abb85 MA |
141 | } |
142 | ||
f86285c5 EH |
143 | /* |
144 | * Ensure all entries returned by qom-list-types implements=<parent> | |
145 | * have <parent> as a parent. | |
146 | */ | |
39fb795a | 147 | static void test_qom_list_parents(QTestState *qts, const char *parent) |
f86285c5 EH |
148 | { |
149 | QList *types; | |
150 | QListEntry *e; | |
151 | QDict *index; | |
152 | ||
39fb795a | 153 | types = qom_list_types(qts, parent, true); |
f86285c5 EH |
154 | index = qom_type_index(types); |
155 | ||
156 | QLIST_FOREACH_ENTRY(types, e) { | |
7dc847eb | 157 | QDict *d = qobject_to(QDict, qlist_entry_obj(e)); |
f86285c5 EH |
158 | const char *name = qdict_get_str(d, "name"); |
159 | ||
160 | g_assert(qom_has_parent(index, name, parent)); | |
161 | } | |
162 | ||
cb3e7f08 MAL |
163 | qobject_unref(types); |
164 | qobject_unref(index); | |
f86285c5 EH |
165 | } |
166 | ||
87467eae EH |
167 | static void test_qom_list_fields(void) |
168 | { | |
169 | QList *all_types; | |
170 | QList *non_abstract; | |
171 | QListEntry *e; | |
39fb795a | 172 | QTestState *qts; |
87467eae | 173 | |
39fb795a | 174 | qts = qtest_init(common_args); |
87467eae | 175 | |
39fb795a TH |
176 | all_types = qom_list_types(qts, NULL, true); |
177 | non_abstract = qom_list_types(qts, NULL, false); | |
87467eae EH |
178 | |
179 | QLIST_FOREACH_ENTRY(all_types, e) { | |
7dc847eb | 180 | QDict *d = qobject_to(QDict, qlist_entry_obj(e)); |
87467eae EH |
181 | const char *name = qdict_get_str(d, "name"); |
182 | bool abstract = qdict_haskey(d, "abstract") ? | |
183 | qdict_get_bool(d, "abstract") : | |
184 | false; | |
185 | bool expected_abstract = !type_list_find(non_abstract, name); | |
186 | ||
187 | g_assert(abstract == expected_abstract); | |
188 | } | |
189 | ||
39fb795a TH |
190 | test_qom_list_parents(qts, "object"); |
191 | test_qom_list_parents(qts, "device"); | |
192 | test_qom_list_parents(qts, "sys-bus-device"); | |
f86285c5 | 193 | |
cb3e7f08 MAL |
194 | qobject_unref(all_types); |
195 | qobject_unref(non_abstract); | |
39fb795a | 196 | qtest_quit(qts); |
87467eae EH |
197 | } |
198 | ||
2d1abb85 MA |
199 | static void test_device_intro_none(void) |
200 | { | |
39fb795a | 201 | QTestState *qts = qtest_init(common_args); |
3e7b80f8 DB |
202 | g_autofree char *qom_tree_start = qtest_hmp(qts, "info qom-tree"); |
203 | g_autofree char *qom_tree_end = NULL; | |
204 | g_autofree char *qtree_start = qtest_hmp(qts, "info qtree"); | |
205 | g_autofree char *qtree_end = NULL; | |
39fb795a TH |
206 | |
207 | test_one_device(qts, "nonexistent"); | |
3e7b80f8 DB |
208 | |
209 | /* Make sure that really nothing changed in the trees */ | |
210 | qom_tree_end = qtest_hmp(qts, "info qom-tree"); | |
211 | g_assert_cmpstr(qom_tree_start, ==, qom_tree_end); | |
212 | qtree_end = qtest_hmp(qts, "info qtree"); | |
213 | g_assert_cmpstr(qtree_start, ==, qtree_end); | |
214 | ||
39fb795a | 215 | qtest_quit(qts); |
2d1abb85 MA |
216 | } |
217 | ||
218 | static void test_device_intro_abstract(void) | |
219 | { | |
39fb795a | 220 | QTestState *qts = qtest_init(common_args); |
3e7b80f8 DB |
221 | g_autofree char *qom_tree_start = qtest_hmp(qts, "info qom-tree"); |
222 | g_autofree char *qom_tree_end = NULL; | |
223 | g_autofree char *qtree_start = qtest_hmp(qts, "info qtree"); | |
224 | g_autofree char *qtree_end = NULL; | |
39fb795a TH |
225 | |
226 | test_one_device(qts, "device"); | |
3e7b80f8 DB |
227 | |
228 | /* Make sure that really nothing changed in the trees */ | |
229 | qom_tree_end = qtest_hmp(qts, "info qom-tree"); | |
230 | g_assert_cmpstr(qom_tree_start, ==, qom_tree_end); | |
231 | qtree_end = qtest_hmp(qts, "info qtree"); | |
232 | g_assert_cmpstr(qtree_start, ==, qtree_end); | |
233 | ||
39fb795a | 234 | qtest_quit(qts); |
2d1abb85 MA |
235 | } |
236 | ||
410573aa | 237 | static void test_device_intro_concrete(const void *args) |
2d1abb85 MA |
238 | { |
239 | QList *types; | |
240 | QListEntry *entry; | |
241 | const char *type; | |
3e7b80f8 DB |
242 | QTestState *qts = qtest_init(args); |
243 | g_autofree char *qom_tree_start = qtest_hmp(qts, "info qom-tree"); | |
244 | g_autofree char *qom_tree_end = NULL; | |
245 | g_autofree char *qtree_start = qtest_hmp(qts, "info qtree"); | |
246 | g_autofree char *qtree_end = NULL; | |
2d1abb85 | 247 | |
39fb795a | 248 | types = device_type_list(qts, false); |
2d1abb85 MA |
249 | |
250 | QLIST_FOREACH_ENTRY(types, entry) { | |
7dc847eb HR |
251 | type = qdict_get_try_str(qobject_to(QDict, qlist_entry_obj(entry)), |
252 | "name"); | |
2d1abb85 | 253 | g_assert(type); |
39fb795a | 254 | test_one_device(qts, type); |
2d1abb85 MA |
255 | } |
256 | ||
3e7b80f8 DB |
257 | /* |
258 | * Some devices leave dangling pointers in QOM behind. | |
259 | * "info qom-tree" or "info qtree" have a good chance at crashing then. | |
260 | * Also make sure that the tree did not change. | |
261 | */ | |
262 | qom_tree_end = qtest_hmp(qts, "info qom-tree"); | |
263 | g_assert_cmpstr(qom_tree_start, ==, qom_tree_end); | |
264 | ||
265 | qtree_end = qtest_hmp(qts, "info qtree"); | |
266 | g_assert_cmpstr(qtree_start, ==, qtree_end); | |
267 | ||
cb3e7f08 | 268 | qobject_unref(types); |
39fb795a | 269 | qtest_quit(qts); |
410573aa | 270 | g_free((void *)args); |
2d1abb85 MA |
271 | } |
272 | ||
1c6d75d5 EH |
273 | static void test_abstract_interfaces(void) |
274 | { | |
275 | QList *all_types; | |
dbb2a604 | 276 | QListEntry *e; |
f86285c5 | 277 | QDict *index; |
39fb795a | 278 | QTestState *qts; |
1c6d75d5 | 279 | |
39fb795a | 280 | qts = qtest_init(common_args); |
f86285c5 | 281 | |
39fb795a | 282 | all_types = qom_list_types(qts, "interface", true); |
f86285c5 | 283 | index = qom_type_index(all_types); |
1c6d75d5 | 284 | |
dbb2a604 | 285 | QLIST_FOREACH_ENTRY(all_types, e) { |
7dc847eb | 286 | QDict *d = qobject_to(QDict, qlist_entry_obj(e)); |
f86285c5 | 287 | const char *name = qdict_get_str(d, "name"); |
dbb2a604 | 288 | |
f86285c5 EH |
289 | /* |
290 | * qom-list-types implements=interface returns all types | |
291 | * that implement _any_ interface (not just interface | |
292 | * types), so skip the ones that don't have "interface" | |
293 | * on the parent type chain. | |
294 | */ | |
295 | if (!qom_has_parent(index, name, "interface")) { | |
87467eae EH |
296 | /* Not an interface type */ |
297 | continue; | |
298 | } | |
1c6d75d5 | 299 | |
87467eae | 300 | g_assert(qdict_haskey(d, "abstract") && qdict_get_bool(d, "abstract")); |
1c6d75d5 EH |
301 | } |
302 | ||
cb3e7f08 MAL |
303 | qobject_unref(all_types); |
304 | qobject_unref(index); | |
39fb795a | 305 | qtest_quit(qts); |
1c6d75d5 EH |
306 | } |
307 | ||
410573aa TH |
308 | static void add_machine_test_case(const char *mname) |
309 | { | |
310 | char *path, *args; | |
311 | ||
410573aa TH |
312 | path = g_strdup_printf("device/introspect/concrete/defaults/%s", mname); |
313 | args = g_strdup_printf("-M %s", mname); | |
314 | qtest_add_data_func(path, args, test_device_intro_concrete); | |
315 | g_free(path); | |
316 | ||
317 | path = g_strdup_printf("device/introspect/concrete/nodefaults/%s", mname); | |
318 | args = g_strdup_printf("-nodefaults -M %s", mname); | |
319 | qtest_add_data_func(path, args, test_device_intro_concrete); | |
320 | g_free(path); | |
321 | } | |
322 | ||
2d1abb85 MA |
323 | int main(int argc, char **argv) |
324 | { | |
325 | g_test_init(&argc, &argv, NULL); | |
326 | ||
327 | qtest_add_func("device/introspect/list", test_device_intro_list); | |
87467eae | 328 | qtest_add_func("device/introspect/list-fields", test_qom_list_fields); |
2d1abb85 MA |
329 | qtest_add_func("device/introspect/none", test_device_intro_none); |
330 | qtest_add_func("device/introspect/abstract", test_device_intro_abstract); | |
1c6d75d5 | 331 | qtest_add_func("device/introspect/abstract-interfaces", test_abstract_interfaces); |
410573aa TH |
332 | if (g_test_quick()) { |
333 | qtest_add_data_func("device/introspect/concrete/defaults/none", | |
334 | g_strdup(common_args), test_device_intro_concrete); | |
335 | } else { | |
336 | qtest_cb_for_every_machine(add_machine_test_case, true); | |
337 | } | |
2d1abb85 MA |
338 | |
339 | return g_test_run(); | |
340 | } |