qemu/tests/qos-test.c
<<
>>
Prefs
   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.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
  31static char *old_path;
  32
  33static 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 */
  56static 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 */
 104static 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
 132static QGuestAllocator *get_machine_allocator(QOSGraphObject *obj)
 133{
 134    return obj->get_driver(obj, "memory");
 135}
 136
 137static 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
 156void 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 */
 180static 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 */
 240static char **current_path;
 241
 242const char *qos_get_current_command_line(void)
 243{
 244    return current_path[0];
 245}
 246
 247void *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 */
 277static 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
 301static 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 */
 332static 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 */
 435int 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