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