]>
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 <[email protected]>, | |
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; | |
d0685212 | 107 | char *help; |
d0685212 TH |
108 | |
109 | g_test_message("Testing device '%s'", type); | |
110 | ||
39fb795a TH |
111 | resp = qtest_qmp(qts, "{'execute': 'device-list-properties'," |
112 | " 'arguments': {'typename': %s}}", | |
edb1523d | 113 | type); |
cb3e7f08 | 114 | qobject_unref(resp); |
2d1abb85 | 115 | |
39fb795a | 116 | help = qtest_hmp(qts, "device_add \"%s,help\"", type); |
2d1abb85 | 117 | g_free(help); |
2d1abb85 MA |
118 | } |
119 | ||
120 | static void test_device_intro_list(void) | |
121 | { | |
122 | QList *types; | |
123 | char *help; | |
39fb795a | 124 | QTestState *qts; |
2d1abb85 | 125 | |
39fb795a | 126 | qts = qtest_init(common_args); |
2d1abb85 | 127 | |
39fb795a | 128 | types = device_type_list(qts, true); |
cb3e7f08 | 129 | qobject_unref(types); |
2d1abb85 | 130 | |
39fb795a | 131 | help = qtest_hmp(qts, "device_add help"); |
2d1abb85 MA |
132 | g_free(help); |
133 | ||
39fb795a | 134 | qtest_quit(qts); |
2d1abb85 MA |
135 | } |
136 | ||
f86285c5 EH |
137 | /* |
138 | * Ensure all entries returned by qom-list-types implements=<parent> | |
139 | * have <parent> as a parent. | |
140 | */ | |
39fb795a | 141 | static void test_qom_list_parents(QTestState *qts, const char *parent) |
f86285c5 EH |
142 | { |
143 | QList *types; | |
144 | QListEntry *e; | |
145 | QDict *index; | |
146 | ||
39fb795a | 147 | types = qom_list_types(qts, parent, true); |
f86285c5 EH |
148 | index = qom_type_index(types); |
149 | ||
150 | QLIST_FOREACH_ENTRY(types, e) { | |
7dc847eb | 151 | QDict *d = qobject_to(QDict, qlist_entry_obj(e)); |
f86285c5 EH |
152 | const char *name = qdict_get_str(d, "name"); |
153 | ||
154 | g_assert(qom_has_parent(index, name, parent)); | |
155 | } | |
156 | ||
cb3e7f08 MAL |
157 | qobject_unref(types); |
158 | qobject_unref(index); | |
f86285c5 EH |
159 | } |
160 | ||
87467eae EH |
161 | static void test_qom_list_fields(void) |
162 | { | |
163 | QList *all_types; | |
164 | QList *non_abstract; | |
165 | QListEntry *e; | |
39fb795a | 166 | QTestState *qts; |
87467eae | 167 | |
39fb795a | 168 | qts = qtest_init(common_args); |
87467eae | 169 | |
39fb795a TH |
170 | all_types = qom_list_types(qts, NULL, true); |
171 | non_abstract = qom_list_types(qts, NULL, false); | |
87467eae EH |
172 | |
173 | QLIST_FOREACH_ENTRY(all_types, e) { | |
7dc847eb | 174 | QDict *d = qobject_to(QDict, qlist_entry_obj(e)); |
87467eae EH |
175 | const char *name = qdict_get_str(d, "name"); |
176 | bool abstract = qdict_haskey(d, "abstract") ? | |
177 | qdict_get_bool(d, "abstract") : | |
178 | false; | |
179 | bool expected_abstract = !type_list_find(non_abstract, name); | |
180 | ||
181 | g_assert(abstract == expected_abstract); | |
182 | } | |
183 | ||
39fb795a TH |
184 | test_qom_list_parents(qts, "object"); |
185 | test_qom_list_parents(qts, "device"); | |
186 | test_qom_list_parents(qts, "sys-bus-device"); | |
f86285c5 | 187 | |
cb3e7f08 MAL |
188 | qobject_unref(all_types); |
189 | qobject_unref(non_abstract); | |
39fb795a | 190 | qtest_quit(qts); |
87467eae EH |
191 | } |
192 | ||
2d1abb85 MA |
193 | static void test_device_intro_none(void) |
194 | { | |
39fb795a | 195 | QTestState *qts = qtest_init(common_args); |
3e7b80f8 DB |
196 | g_autofree char *qom_tree_start = qtest_hmp(qts, "info qom-tree"); |
197 | g_autofree char *qom_tree_end = NULL; | |
198 | g_autofree char *qtree_start = qtest_hmp(qts, "info qtree"); | |
199 | g_autofree char *qtree_end = NULL; | |
39fb795a TH |
200 | |
201 | test_one_device(qts, "nonexistent"); | |
3e7b80f8 DB |
202 | |
203 | /* Make sure that really nothing changed in the trees */ | |
204 | qom_tree_end = qtest_hmp(qts, "info qom-tree"); | |
205 | g_assert_cmpstr(qom_tree_start, ==, qom_tree_end); | |
206 | qtree_end = qtest_hmp(qts, "info qtree"); | |
207 | g_assert_cmpstr(qtree_start, ==, qtree_end); | |
208 | ||
39fb795a | 209 | qtest_quit(qts); |
2d1abb85 MA |
210 | } |
211 | ||
212 | static void test_device_intro_abstract(void) | |
213 | { | |
39fb795a | 214 | QTestState *qts = qtest_init(common_args); |
3e7b80f8 DB |
215 | g_autofree char *qom_tree_start = qtest_hmp(qts, "info qom-tree"); |
216 | g_autofree char *qom_tree_end = NULL; | |
217 | g_autofree char *qtree_start = qtest_hmp(qts, "info qtree"); | |
218 | g_autofree char *qtree_end = NULL; | |
39fb795a TH |
219 | |
220 | test_one_device(qts, "device"); | |
3e7b80f8 DB |
221 | |
222 | /* Make sure that really nothing changed in the trees */ | |
223 | qom_tree_end = qtest_hmp(qts, "info qom-tree"); | |
224 | g_assert_cmpstr(qom_tree_start, ==, qom_tree_end); | |
225 | qtree_end = qtest_hmp(qts, "info qtree"); | |
226 | g_assert_cmpstr(qtree_start, ==, qtree_end); | |
227 | ||
39fb795a | 228 | qtest_quit(qts); |
2d1abb85 MA |
229 | } |
230 | ||
410573aa | 231 | static void test_device_intro_concrete(const void *args) |
2d1abb85 MA |
232 | { |
233 | QList *types; | |
234 | QListEntry *entry; | |
235 | const char *type; | |
3e7b80f8 DB |
236 | QTestState *qts = qtest_init(args); |
237 | g_autofree char *qom_tree_start = qtest_hmp(qts, "info qom-tree"); | |
238 | g_autofree char *qom_tree_end = NULL; | |
239 | g_autofree char *qtree_start = qtest_hmp(qts, "info qtree"); | |
240 | g_autofree char *qtree_end = NULL; | |
2d1abb85 | 241 | |
39fb795a | 242 | types = device_type_list(qts, false); |
2d1abb85 MA |
243 | |
244 | QLIST_FOREACH_ENTRY(types, entry) { | |
7dc847eb HR |
245 | type = qdict_get_try_str(qobject_to(QDict, qlist_entry_obj(entry)), |
246 | "name"); | |
2d1abb85 | 247 | g_assert(type); |
39fb795a | 248 | test_one_device(qts, type); |
2d1abb85 MA |
249 | } |
250 | ||
3e7b80f8 DB |
251 | /* |
252 | * Some devices leave dangling pointers in QOM behind. | |
253 | * "info qom-tree" or "info qtree" have a good chance at crashing then. | |
254 | * Also make sure that the tree did not change. | |
255 | */ | |
256 | qom_tree_end = qtest_hmp(qts, "info qom-tree"); | |
257 | g_assert_cmpstr(qom_tree_start, ==, qom_tree_end); | |
258 | ||
259 | qtree_end = qtest_hmp(qts, "info qtree"); | |
260 | g_assert_cmpstr(qtree_start, ==, qtree_end); | |
261 | ||
cb3e7f08 | 262 | qobject_unref(types); |
39fb795a | 263 | qtest_quit(qts); |
410573aa | 264 | g_free((void *)args); |
2d1abb85 MA |
265 | } |
266 | ||
1c6d75d5 EH |
267 | static void test_abstract_interfaces(void) |
268 | { | |
269 | QList *all_types; | |
dbb2a604 | 270 | QListEntry *e; |
f86285c5 | 271 | QDict *index; |
39fb795a | 272 | QTestState *qts; |
1c6d75d5 | 273 | |
39fb795a | 274 | qts = qtest_init(common_args); |
f86285c5 | 275 | |
39fb795a | 276 | all_types = qom_list_types(qts, "interface", true); |
f86285c5 | 277 | index = qom_type_index(all_types); |
1c6d75d5 | 278 | |
dbb2a604 | 279 | QLIST_FOREACH_ENTRY(all_types, e) { |
7dc847eb | 280 | QDict *d = qobject_to(QDict, qlist_entry_obj(e)); |
f86285c5 | 281 | const char *name = qdict_get_str(d, "name"); |
dbb2a604 | 282 | |
f86285c5 EH |
283 | /* |
284 | * qom-list-types implements=interface returns all types | |
285 | * that implement _any_ interface (not just interface | |
286 | * types), so skip the ones that don't have "interface" | |
287 | * on the parent type chain. | |
288 | */ | |
289 | if (!qom_has_parent(index, name, "interface")) { | |
87467eae EH |
290 | /* Not an interface type */ |
291 | continue; | |
292 | } | |
1c6d75d5 | 293 | |
87467eae | 294 | g_assert(qdict_haskey(d, "abstract") && qdict_get_bool(d, "abstract")); |
1c6d75d5 EH |
295 | } |
296 | ||
cb3e7f08 MAL |
297 | qobject_unref(all_types); |
298 | qobject_unref(index); | |
39fb795a | 299 | qtest_quit(qts); |
1c6d75d5 EH |
300 | } |
301 | ||
410573aa TH |
302 | static void add_machine_test_case(const char *mname) |
303 | { | |
304 | char *path, *args; | |
305 | ||
410573aa TH |
306 | path = g_strdup_printf("device/introspect/concrete/defaults/%s", mname); |
307 | args = g_strdup_printf("-M %s", mname); | |
308 | qtest_add_data_func(path, args, test_device_intro_concrete); | |
309 | g_free(path); | |
310 | ||
311 | path = g_strdup_printf("device/introspect/concrete/nodefaults/%s", mname); | |
312 | args = g_strdup_printf("-nodefaults -M %s", mname); | |
313 | qtest_add_data_func(path, args, test_device_intro_concrete); | |
314 | g_free(path); | |
315 | } | |
316 | ||
2d1abb85 MA |
317 | int main(int argc, char **argv) |
318 | { | |
319 | g_test_init(&argc, &argv, NULL); | |
320 | ||
321 | qtest_add_func("device/introspect/list", test_device_intro_list); | |
87467eae | 322 | qtest_add_func("device/introspect/list-fields", test_qom_list_fields); |
2d1abb85 MA |
323 | qtest_add_func("device/introspect/none", test_device_intro_none); |
324 | qtest_add_func("device/introspect/abstract", test_device_intro_abstract); | |
1c6d75d5 | 325 | qtest_add_func("device/introspect/abstract-interfaces", test_abstract_interfaces); |
410573aa TH |
326 | if (g_test_quick()) { |
327 | qtest_add_data_func("device/introspect/concrete/defaults/none", | |
328 | g_strdup(common_args), test_device_intro_concrete); | |
329 | } else { | |
330 | qtest_cb_for_every_machine(add_machine_test_case, true); | |
331 | } | |
2d1abb85 MA |
332 | |
333 | return g_test_run(); | |
334 | } |