]>
Commit | Line | Data |
---|---|---|
fc281c80 EGE |
1 | /* |
2 | * libqos driver framework | |
3 | * | |
4 | * Copyright (c) 2018 Emanuele Giuseppe Esposito <[email protected]> | |
5 | * | |
6 | * This library is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Lesser General Public | |
8 | * License version 2 as published by the Free Software Foundation. | |
9 | * | |
10 | * This library is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | * Lesser General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU Lesser General Public | |
16 | * License along with this library; if not, see <http://www.gnu.org/licenses/> | |
17 | */ | |
18 | ||
fc281c80 | 19 | #include "qemu/osdep.h" |
bbfff196 | 20 | #include <getopt.h> |
fc281c80 EGE |
21 | #include "libqtest.h" |
22 | #include "qapi/qmp/qdict.h" | |
23 | #include "qapi/qmp/qbool.h" | |
24 | #include "qapi/qmp/qstring.h" | |
0b8fa32f | 25 | #include "qemu/module.h" |
fc281c80 EGE |
26 | #include "qapi/qmp/qlist.h" |
27 | #include "libqos/malloc.h" | |
28 | #include "libqos/qgraph.h" | |
29 | #include "libqos/qgraph_internal.h" | |
30 | ||
31 | static char *old_path; | |
32 | ||
33 | static void apply_to_node(const char *name, bool is_machine, bool is_abstract) | |
34 | { | |
35 | char *machine_name = NULL; | |
36 | if (is_machine) { | |
37 | const char *arch = qtest_get_arch(); | |
38 | machine_name = g_strconcat(arch, "/", name, NULL); | |
39 | name = machine_name; | |
40 | } | |
41 | qos_graph_node_set_availability(name, true); | |
42 | if (is_abstract) { | |
43 | qos_delete_cmd_line(name); | |
44 | } | |
45 | g_free(machine_name); | |
46 | } | |
47 | ||
48 | /** | |
49 | * apply_to_qlist(): using QMP queries QEMU for a list of | |
50 | * machines and devices available, and sets the respective node | |
51 | * as true. If a node is found, also all its produced and contained | |
52 | * child are marked available. | |
53 | * | |
54 | * See qos_graph_node_set_availability() for more info | |
55 | */ | |
56 | static void apply_to_qlist(QList *list, bool is_machine) | |
57 | { | |
58 | const QListEntry *p; | |
59 | const char *name; | |
60 | bool abstract; | |
61 | QDict *minfo; | |
62 | QObject *qobj; | |
63 | QString *qstr; | |
64 | QBool *qbool; | |
65 | ||
66 | for (p = qlist_first(list); p; p = qlist_next(p)) { | |
67 | minfo = qobject_to(QDict, qlist_entry_obj(p)); | |
68 | qobj = qdict_get(minfo, "name"); | |
69 | qstr = qobject_to(QString, qobj); | |
70 | name = qstring_get_str(qstr); | |
71 | ||
72 | qobj = qdict_get(minfo, "abstract"); | |
73 | if (qobj) { | |
74 | qbool = qobject_to(QBool, qobj); | |
75 | abstract = qbool_get_bool(qbool); | |
76 | } else { | |
77 | abstract = false; | |
78 | } | |
79 | ||
80 | apply_to_node(name, is_machine, abstract); | |
81 | qobj = qdict_get(minfo, "alias"); | |
82 | if (qobj) { | |
83 | qstr = qobject_to(QString, qobj); | |
84 | name = qstring_get_str(qstr); | |
85 | apply_to_node(name, is_machine, abstract); | |
86 | } | |
87 | } | |
88 | } | |
89 | ||
90 | /** | |
91 | * qos_set_machines_devices_available(): sets availability of qgraph | |
92 | * machines and devices. | |
93 | * | |
94 | * This function firstly starts QEMU with "-machine none" option, | |
95 | * and then executes the QMP protocol asking for the list of devices | |
96 | * and machines available. | |
97 | * | |
98 | * for each of these items, it looks up the corresponding qgraph node, | |
99 | * setting it as available. The list currently returns all devices that | |
100 | * are either machines or QEDGE_CONSUMED_BY other nodes. | |
101 | * Therefore, in order to mark all other nodes, it recursively sets | |
102 | * all its QEDGE_CONTAINS and QEDGE_PRODUCES child as available too. | |
103 | */ | |
104 | static void qos_set_machines_devices_available(void) | |
105 | { | |
106 | QDict *response; | |
107 | QDict *args = qdict_new(); | |
108 | QList *list; | |
109 | ||
110 | qtest_start("-machine none"); | |
111 | response = qmp("{ 'execute': 'query-machines' }"); | |
112 | list = qdict_get_qlist(response, "return"); | |
113 | ||
114 | apply_to_qlist(list, true); | |
115 | ||
116 | qobject_unref(response); | |
117 | ||
118 | qdict_put_bool(args, "abstract", true); | |
119 | qdict_put_str(args, "implements", "device"); | |
120 | ||
121 | response = qmp("{'execute': 'qom-list-types'," | |
122 | " 'arguments': %p }", args); | |
123 | g_assert(qdict_haskey(response, "return")); | |
124 | list = qdict_get_qlist(response, "return"); | |
125 | ||
126 | apply_to_qlist(list, false); | |
127 | ||
128 | qtest_end(); | |
129 | qobject_unref(response); | |
130 | } | |
131 | ||
132 | static QGuestAllocator *get_machine_allocator(QOSGraphObject *obj) | |
133 | { | |
134 | return obj->get_driver(obj, "memory"); | |
135 | } | |
136 | ||
137 | static void restart_qemu_or_continue(char *path) | |
138 | { | |
139 | /* compares the current command line with the | |
140 | * one previously executed: if they are the same, | |
141 | * don't restart QEMU, if they differ, stop previous | |
142 | * QEMU subprocess (if active) and start over with | |
143 | * the new command line | |
144 | */ | |
145 | if (g_strcmp0(old_path, path)) { | |
146 | qtest_end(); | |
147 | qos_invalidate_command_line(); | |
148 | old_path = g_strdup(path); | |
149 | qtest_start(path); | |
150 | } else { /* if cmd line is the same, reset the guest */ | |
151 | qobject_unref(qmp("{ 'execute': 'system_reset' }")); | |
152 | qmp_eventwait("RESET"); | |
153 | } | |
154 | } | |
155 | ||
156 | void qos_invalidate_command_line(void) | |
157 | { | |
158 | g_free(old_path); | |
159 | old_path = NULL; | |
160 | } | |
161 | ||
162 | /** | |
163 | * allocate_objects(): given an array of nodes @arg, | |
164 | * walks the path invoking all constructors and | |
165 | * passing the corresponding parameter in order to | |
166 | * continue the objects allocation. | |
167 | * Once the test is reached, return the object it consumes. | |
168 | * | |
169 | * Since the machine and QEDGE_CONSUMED_BY nodes allocate | |
170 | * memory in the constructor, g_test_queue_destroy is used so | |
171 | * that after execution they can be safely free'd. (The test's | |
172 | * ->before callback is also welcome to use g_test_queue_destroy). | |
173 | * | |
174 | * Note: as specified in walk_path() too, @arg is an array of | |
175 | * char *, where arg[0] is a pointer to the command line | |
176 | * string that will be used to properly start QEMU when executing | |
177 | * the test, and the remaining elements represent the actual objects | |
178 | * that will be allocated. | |
179 | */ | |
180 | static void *allocate_objects(QTestState *qts, char **path, QGuestAllocator **p_alloc) | |
181 | { | |
182 | int current = 0; | |
183 | QGuestAllocator *alloc; | |
184 | QOSGraphObject *parent = NULL; | |
185 | QOSGraphEdge *edge; | |
186 | QOSGraphNode *node; | |
187 | void *edge_arg; | |
188 | void *obj; | |
189 | ||
190 | node = qos_graph_get_node(path[current]); | |
191 | g_assert(node->type == QNODE_MACHINE); | |
192 | ||
193 | obj = qos_machine_new(node, qts); | |
194 | qos_object_queue_destroy(obj); | |
195 | ||
196 | alloc = get_machine_allocator(obj); | |
197 | if (p_alloc) { | |
198 | *p_alloc = alloc; | |
199 | } | |
200 | ||
201 | for (;;) { | |
202 | if (node->type != QNODE_INTERFACE) { | |
203 | qos_object_start_hw(obj); | |
204 | parent = obj; | |
205 | } | |
206 | ||
207 | /* follow edge and get object for next node constructor */ | |
208 | current++; | |
209 | edge = qos_graph_get_edge(path[current - 1], path[current]); | |
210 | node = qos_graph_get_node(path[current]); | |
211 | ||
212 | if (node->type == QNODE_TEST) { | |
213 | g_assert(qos_graph_edge_get_type(edge) == QEDGE_CONSUMED_BY); | |
214 | return obj; | |
215 | } | |
216 | ||
217 | switch (qos_graph_edge_get_type(edge)) { | |
218 | case QEDGE_PRODUCES: | |
219 | obj = parent->get_driver(parent, path[current]); | |
220 | break; | |
221 | ||
222 | case QEDGE_CONSUMED_BY: | |
223 | edge_arg = qos_graph_edge_get_arg(edge); | |
224 | obj = qos_driver_new(node, obj, alloc, edge_arg); | |
225 | qos_object_queue_destroy(obj); | |
226 | break; | |
227 | ||
228 | case QEDGE_CONTAINS: | |
229 | obj = parent->get_device(parent, path[current]); | |
230 | break; | |
231 | } | |
232 | } | |
233 | } | |
234 | ||
235 | /* The argument to run_one_test, which is the test function that is registered | |
236 | * with GTest, is a vector of strings. The first item is the initial command | |
237 | * line (before it is modified by the test's "before" function), the remaining | |
238 | * items are node names forming the path to the test node. | |
239 | */ | |
240 | static char **current_path; | |
241 | ||
242 | const char *qos_get_current_command_line(void) | |
243 | { | |
244 | return current_path[0]; | |
245 | } | |
246 | ||
247 | void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc) | |
248 | { | |
249 | return allocate_objects(qts, current_path + 1, p_alloc); | |
250 | } | |
251 | ||
252 | /** | |
253 | * run_one_test(): given an array of nodes @arg, | |
254 | * walks the path invoking all constructors and | |
255 | * passing the corresponding parameter in order to | |
256 | * continue the objects allocation. | |
257 | * Once the test is reached, its function is executed. | |
258 | * | |
259 | * Since the machine and QEDGE_CONSUMED_BY nodes allocate | |
260 | * memory in the constructor, g_test_queue_destroy is used so | |
261 | * that after execution they can be safely free'd. The test's | |
262 | * ->before callback is also welcome to use g_test_queue_destroy. | |
263 | * | |
264 | * Note: as specified in walk_path() too, @arg is an array of | |
265 | * char *, where arg[0] is a pointer to the command line | |
266 | * string that will be used to properly start QEMU when executing | |
267 | * the test, and the remaining elements represent the actual objects | |
268 | * that will be allocated. | |
269 | * | |
270 | * The order of execution is the following: | |
271 | * 1) @before test function as defined in the given QOSGraphTestOptions | |
272 | * 2) start QEMU | |
273 | * 3) call all nodes constructor and get_driver/get_device depending on edge, | |
274 | * start the hardware (*_device_enable functions) | |
275 | * 4) start test | |
276 | */ | |
277 | static void run_one_test(const void *arg) | |
278 | { | |
279 | QOSGraphNode *test_node; | |
280 | QGuestAllocator *alloc = NULL; | |
281 | void *obj; | |
282 | char **path = (char **) arg; | |
283 | GString *cmd_line = g_string_new(path[0]); | |
284 | void *test_arg; | |
285 | ||
286 | /* Before test */ | |
287 | current_path = path; | |
288 | test_node = qos_graph_get_node(path[(g_strv_length(path) - 1)]); | |
289 | test_arg = test_node->u.test.arg; | |
290 | if (test_node->u.test.before) { | |
291 | test_arg = test_node->u.test.before(cmd_line, test_arg); | |
292 | } | |
293 | ||
294 | restart_qemu_or_continue(cmd_line->str); | |
295 | g_string_free(cmd_line, true); | |
296 | ||
297 | obj = qos_allocate_objects(global_qtest, &alloc); | |
298 | test_node->u.test.function(obj, test_arg, alloc); | |
299 | } | |
300 | ||
301 | static void subprocess_run_one_test(const void *arg) | |
302 | { | |
303 | const gchar *path = arg; | |
304 | g_test_trap_subprocess(path, 0, 0); | |
305 | g_test_trap_assert_passed(); | |
306 | } | |
307 | ||
308 | /* | |
309 | * in this function, 2 path will be built: | |
310 | * path_str, a one-string path (ex "pc/i440FX-pcihost/...") | |
311 | * path_vec, a string-array path (ex [0] = "pc", [1] = "i440FX-pcihost"). | |
312 | * | |
313 | * path_str will be only used to build the test name, and won't need the | |
314 | * architecture name at beginning, since it will be added by qtest_add_func(). | |
315 | * | |
316 | * path_vec is used to allocate all constructors of the path nodes. | |
317 | * Each name in this array except position 0 must correspond to a valid | |
318 | * QOSGraphNode name. | |
319 | * Position 0 is special, initially contains just the <machine> name of | |
320 | * the node, (ex for "x86_64/pc" it will be "pc"), used to build the test | |
321 | * path (see below). After it will contain the command line used to start | |
322 | * qemu with all required devices. | |
323 | * | |
324 | * Note that the machine node name must be with format <arch>/<machine> | |
325 | * (ex "x86_64/pc"), because it will identify the node "x86_64/pc" | |
326 | * and start QEMU with "-M pc". For this reason, | |
327 | * when building path_str, path_vec | |
328 | * initially contains the <machine> at position 0 ("pc"), | |
329 | * and the node name at position 1 (<arch>/<machine>) | |
330 | * ("x86_64/pc"), followed by the rest of the nodes. | |
331 | */ | |
332 | static void walk_path(QOSGraphNode *orig_path, int len) | |
333 | { | |
334 | QOSGraphNode *path; | |
335 | QOSGraphEdge *edge; | |
336 | ||
337 | /* etype set to QEDGE_CONSUMED_BY so that machine can add to the command line */ | |
338 | QOSEdgeType etype = QEDGE_CONSUMED_BY; | |
339 | ||
340 | /* twice QOS_PATH_MAX_ELEMENT_SIZE since each edge can have its arg */ | |
341 | char **path_vec = g_new0(char *, (QOS_PATH_MAX_ELEMENT_SIZE * 2)); | |
342 | int path_vec_size = 0; | |
343 | ||
de94b54a PB |
344 | char *after_cmd, *before_cmd, *after_device; |
345 | GString *after_device_str = g_string_new(""); | |
fc281c80 EGE |
346 | char *node_name = orig_path->name, *path_str; |
347 | ||
348 | GString *cmd_line = g_string_new(""); | |
349 | GString *cmd_line2 = g_string_new(""); | |
350 | ||
351 | path = qos_graph_get_node(node_name); /* root */ | |
352 | node_name = qos_graph_edge_get_dest(path->path_edge); /* machine name */ | |
353 | ||
354 | path_vec[path_vec_size++] = node_name; | |
355 | path_vec[path_vec_size++] = qos_get_machine_type(node_name); | |
356 | ||
357 | for (;;) { | |
358 | path = qos_graph_get_node(node_name); | |
359 | if (!path->path_edge) { | |
360 | break; | |
361 | } | |
362 | ||
363 | node_name = qos_graph_edge_get_dest(path->path_edge); | |
364 | ||
365 | /* append node command line + previous edge command line */ | |
366 | if (path->command_line && etype == QEDGE_CONSUMED_BY) { | |
367 | g_string_append(cmd_line, path->command_line); | |
de94b54a PB |
368 | g_string_append(cmd_line, after_device_str->str); |
369 | g_string_truncate(after_device_str, 0); | |
fc281c80 EGE |
370 | } |
371 | ||
372 | path_vec[path_vec_size++] = qos_graph_edge_get_name(path->path_edge); | |
373 | /* detect if edge has command line args */ | |
374 | after_cmd = qos_graph_edge_get_after_cmd_line(path->path_edge); | |
375 | after_device = qos_graph_edge_get_extra_device_opts(path->path_edge); | |
376 | before_cmd = qos_graph_edge_get_before_cmd_line(path->path_edge); | |
377 | edge = qos_graph_get_edge(path->name, node_name); | |
378 | etype = qos_graph_edge_get_type(edge); | |
379 | ||
380 | if (before_cmd) { | |
381 | g_string_append(cmd_line, before_cmd); | |
382 | } | |
383 | if (after_cmd) { | |
384 | g_string_append(cmd_line2, after_cmd); | |
385 | } | |
de94b54a PB |
386 | if (after_device) { |
387 | g_string_append(after_device_str, after_device); | |
388 | } | |
fc281c80 EGE |
389 | } |
390 | ||
391 | path_vec[path_vec_size++] = NULL; | |
de94b54a PB |
392 | g_string_append(cmd_line, after_device_str->str); |
393 | g_string_free(after_device_str, true); | |
394 | ||
fc281c80 EGE |
395 | g_string_append(cmd_line, cmd_line2->str); |
396 | g_string_free(cmd_line2, true); | |
397 | ||
398 | /* here position 0 has <arch>/<machine>, position 1 has <machine>. | |
399 | * The path must not have the <arch>, qtest_add_data_func adds it. | |
400 | */ | |
401 | path_str = g_strjoinv("/", path_vec + 1); | |
402 | ||
403 | /* put arch/machine in position 1 so run_one_test can do its work | |
404 | * and add the command line at position 0. | |
405 | */ | |
406 | path_vec[1] = path_vec[0]; | |
407 | path_vec[0] = g_string_free(cmd_line, false); | |
408 | ||
409 | if (path->u.test.subprocess) { | |
410 | gchar *subprocess_path = g_strdup_printf("/%s/%s/subprocess", | |
411 | qtest_get_arch(), path_str); | |
412 | qtest_add_data_func(path_str, subprocess_path, subprocess_run_one_test); | |
413 | g_test_add_data_func(subprocess_path, path_vec, run_one_test); | |
414 | } else { | |
415 | qtest_add_data_func(path_str, path_vec, run_one_test); | |
416 | } | |
417 | ||
418 | g_free(path_str); | |
419 | } | |
420 | ||
421 | ||
422 | ||
423 | /** | |
424 | * main(): heart of the qgraph framework. | |
425 | * | |
426 | * - Initializes the glib test framework | |
427 | * - Creates the graph by invoking the various _init constructors | |
428 | * - Starts QEMU to mark the available devices | |
429 | * - Walks the graph, and each path is added to | |
430 | * the glib test framework (walk_path) | |
431 | * - Runs the tests, calling allocate_object() and allocating the | |
432 | * machine/drivers/test objects | |
433 | * - Cleans up everything | |
434 | */ | |
435 | int main(int argc, char **argv) | |
436 | { | |
437 | g_test_init(&argc, &argv, NULL); | |
438 | qos_graph_init(); | |
439 | module_call_init(MODULE_INIT_QOM); | |
440 | module_call_init(MODULE_INIT_LIBQOS); | |
441 | qos_set_machines_devices_available(); | |
442 | ||
443 | qos_graph_foreach_test_path(walk_path); | |
444 | g_test_run(); | |
445 | qtest_end(); | |
446 | qos_graph_destroy(); | |
447 | g_free(old_path); | |
448 | return 0; | |
449 | } |