1 /* 2 * libqos driver framework 3 * 4 * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com> 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 "qemu/osdep.h" 20 #include <getopt.h> 21 #include "libqtest-single.h" 22 #include "qapi/qmp/qdict.h" 23 #include "qapi/qmp/qbool.h" 24 #include "qapi/qmp/qstring.h" 25 #include "qemu/module.h" 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 344 char *after_cmd, *before_cmd, *after_device; 345 GString *after_device_str = g_string_new(""); 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); 368 g_string_append(cmd_line, after_device_str->str); 369 g_string_truncate(after_device_str, 0); 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 } 386 if (after_device) { 387 g_string_append(after_device_str, after_device); 388 } 389 } 390 391 path_vec[path_vec_size++] = NULL; 392 g_string_append(cmd_line, after_device_str->str); 393 g_string_free(after_device_str, true); 394 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 } 450