qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH 19/71] tests: qgraph API for the qtest driver fr


From: Thomas Huth
Subject: Re: [Qemu-devel] [PATCH 19/71] tests: qgraph API for the qtest driver framework
Date: Fri, 7 Dec 2018 16:38:31 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.9.1

On 2018-12-03 16:32, Paolo Bonzini wrote:
> From: Emanuele Giuseppe Esposito <address@hidden>
> 
> Add qgraph API that allows to add/remove nodes and edges from the graph,
> implementation of Depth First Search to discover the paths and basic unit
> test to check correctness of the API.
> Included also a main executable that takes care of starting the framework,
> create the nodes, set the available drivers/machines, discover the path and
> run tests.
> 
> graph.h provides the public API to manage the graph nodes/edges
> graph_extra.h provides a more private API used successively by the gtest 
> integration part
> qos-test.c provides the main executable
> 
> Signed-off-by: Emanuele Giuseppe Esposito <address@hidden>
> [Paolo's changes compared to the Google Summer of Code submission:
>  * added subprocess to test options
>  * refactored object creation to support live migration tests
>  * removed driver .before callback (unused)
>  * removed test .after callbacks (replaced by GTest destruction queue)]
[...]
> diff --git a/tests/libqos/qgraph.c b/tests/libqos/qgraph.c
> new file mode 100644
> index 0000000..03783f5
> --- /dev/null
> +++ b/tests/libqos/qgraph.c
> @@ -0,0 +1,760 @@
> +/*
> + * libqos driver framework
> + *
> + * Copyright (c) 2018 Emanuele Giuseppe Esposito <address@hidden>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License version 2 as published by the Free Software Foundation.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see 
> <http://www.gnu.org/licenses/>
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest.h"
> +#include "qemu/queue.h"
> +#include "libqos/qgraph_internal.h"
> +#include "libqos/qgraph.h"
> +
> +#define QGRAPH_PRINT_DEBUG 0
> +#define QOS_ROOT ""
> +typedef struct QOSStackElement QOSStackElement;
> +
> +/* Graph Edge.*/
> +struct QOSGraphEdge {
> +    QOSEdgeType type;
> +    char *dest;
> +    void *arg;                /* just for QEDGE_CONTAINS
> +                               * and QEDGE_CONSUMED_BY */
> +    char *extra_device_opts;  /* added to -device option, "," is
> +                               * automatically added
> +                               */
> +    char *before_cmd_line;    /* added before node cmd_line */
> +    char *after_cmd_line;     /* added after -device options */
> +    char *edge_name;          /* used by QEDGE_CONTAINS */
> +    QSLIST_ENTRY(QOSGraphEdge) edge_list;
> +};
> +
> +/* Linked list grouping all edges with the same source node */
> +QSLIST_HEAD(QOSGraphEdgeList, QOSGraphEdge);
> +
> +
> +/**
> + * Stack used to keep track of the discovered path when using
> + * the DFS algorithm
> + */
> +struct QOSStackElement {
> +    QOSGraphNode *node;
> +    QOSStackElement *parent;
> +    QOSGraphEdge *parent_edge;
> +    int length;
> +};
> +
> +/* Each enty in these hash table will consist of <string, node/edge> pair. */
> +static GHashTable *edge_table;
> +static GHashTable *node_table;
> +
> +/* stack used by the DFS algorithm to store the path from machine to test */
> +static QOSStackElement qos_node_stack[QOS_PATH_MAX_ELEMENT_SIZE];
> +static int qos_node_tos;
> +
> +/**
> + * add_edge(): creates an edge of type @type
> + * from @source to @dest node, and inserts it in the
> + * edges hash table
> + *
> + * Nodes @source and @dest do not necessarily need to exist.
> + * Possibility to add also options (see #QOSGraphEdgeOptions)
> + * edge->edge_name is used as identifier for get_device relationships,
> + * so by default is equal to @dest.
> + */
> +static void add_edge(const char *source, const char *dest,
> +                     QOSEdgeType type, QOSGraphEdgeOptions *opts)
> +{
> +    char *key;
> +    QOSGraphEdgeList *list = g_hash_table_lookup(edge_table, source);
> +
> +    if (!list) {
> +        list = g_new0(QOSGraphEdgeList, 1);
> +        key = g_strdup(source);
> +        g_hash_table_insert(edge_table, key, list);
> +    }
> +
> +    if (!opts) {
> +        opts = &(QOSGraphEdgeOptions) { };
> +    }
> +
> +    QOSGraphEdge *edge = g_new0(QOSGraphEdge, 1);
> +    edge->type = type;
> +    edge->dest = g_strdup(dest);
> +    edge->edge_name = g_strdup(opts->edge_name ? : dest);
> +    edge->arg = g_memdup(opts->arg, opts->size_arg);
> +
> +    edge->before_cmd_line =
> +        opts->before_cmd_line ? g_strconcat(" ", opts->before_cmd_line, 
> NULL) : NULL;
> +    edge->extra_device_opts =
> +        opts->extra_device_opts ? g_strconcat(",", opts->extra_device_opts, 
> NULL) : NULL;
> +    edge->after_cmd_line =
> +        opts->after_cmd_line ? g_strconcat(" ", opts->after_cmd_line, NULL) 
> : NULL;
> +
> +    QSLIST_INSERT_HEAD(list, edge, edge_list);
> +}
> +
> +/* destroy_edges(): frees all edges inside a given @list */
> +static void destroy_edges(void *list)
> +{
> +    QOSGraphEdge *temp;
> +    QOSGraphEdgeList *elist = list;
> +
> +    while (!QSLIST_EMPTY(elist)) {
> +        temp = QSLIST_FIRST(elist);
> +        QSLIST_REMOVE_HEAD(elist, edge_list);
> +        g_free(temp->dest);
> +        g_free(temp->before_cmd_line);
> +        g_free(temp->after_cmd_line);
> +        g_free(temp->extra_device_opts);
> +        g_free(temp->edge_name);
> +        g_free(temp->arg);
> +        g_free(temp);
> +    }
> +    g_free(elist);
> +}
> +
> +/**
> + * create_node(): creates a node @name of type @type
> + * and inserts it to the nodes hash table.
> + * By default, node is not available.
> + */
> +static QOSGraphNode *create_node(const char *name, QOSNodeType type)
> +{
> +    if (g_hash_table_lookup(node_table, name)) {
> +        g_printerr("Node %s already created\n", name);
> +        abort();
> +    }
> +
> +    QOSGraphNode *node = g_new0(QOSGraphNode, 1);
> +    node->type = type;
> +    node->available = FALSE;
> +    node->name = g_strdup(name);
> +    g_hash_table_insert(node_table, node->name, node);
> +    return node;
> +}
> +
> +/**
> + * destroy_node(): frees a node @val from the nodes hash table.
> + * Note that node->name is not free'd since it will represent the
> + * hash table key
> + */
> +static void destroy_node(void *val)
> +{
> +    QOSGraphNode *node = val;
> +    g_free(node->command_line);
> +    g_free(node);
> +}
> +
> +/**
> + * destroy_string(): frees @key from the nodes hash table.
> + * Actually frees the node->name
> + */
> +static void destroy_string(void *key)
> +{
> +    g_free(key);
> +}
> +
> +/**
> + * search_node(): search for a node @key in the nodes hash table
> + * Returns the QOSGraphNode if found, #NULL otherwise
> + */
> +static QOSGraphNode *search_node(const char *key)
> +{
> +    return g_hash_table_lookup(node_table, key);
> +}
> +
> +/**
> + * get_edgelist(): returns the edge list (value) assigned to
> + * the @key in the edge hash table.
> + * This list will contain all edges with source equal to @key
> + *
> + * Returns: on success: the %QOSGraphEdgeList
> + *          otherwise: abort()
> + */
> +static QOSGraphEdgeList *get_edgelist(const char *key)
> +{
> +    return g_hash_table_lookup(edge_table, key);
> +}
> +
> +/**
> + * search_list_edges(): search for an edge with destination @dest
> + * in the given @edgelist.
> + *
> + * Returns: on success: the %QOSGraphEdge
> + *          otherwise: #NULL
> + */
> +static QOSGraphEdge *search_list_edges(QOSGraphEdgeList *edgelist,
> +                                       const char *dest)
> +{
> +    QOSGraphEdge *tmp, *next;
> +    if (!edgelist) {
> +        return NULL;
> +    }
> +    QSLIST_FOREACH_SAFE(tmp, edgelist, edge_list, next) {
> +        if (g_strcmp0(tmp->dest, dest) == 0) {
> +            break;
> +        }
> +    }
> +    return tmp;
> +}
> +
> +/**
> + * search_machine(): search for a machine @name in the node hash
> + * table. A machine is the child of the root node.
> + * This function forces the research in the childs of the root,
> + * to check the node is a proper machine
> + *
> + * Returns: on success: the %QOSGraphNode
> + *          otherwise: #NULL
> + */
> +static QOSGraphNode *search_machine(const char *name)
> +{
> +    QOSGraphNode *n;
> +    QOSGraphEdgeList *root_list = get_edgelist(QOS_ROOT);
> +    QOSGraphEdge *e = search_list_edges(root_list, name);
> +    if (!e) {
> +        return NULL;
> +    }
> +    n = search_node(e->dest);
> +    if (n->type == QNODE_MACHINE) {
> +        return n;
> +    }
> +    return NULL;
> +}
> +
> +/**
> + * create_interface(): checks if there is already
> + * a node @node in the node hash table, if not
> + * creates a node @node of type #QNODE_INTERFACE
> + * and inserts it. If there is one, check it's
> + * a #QNODE_INTERFACE and abort() if it's not.
> + */
> +static void create_interface(const char *node)
> +{
> +    QOSGraphNode *interface;
> +    interface = search_node(node);
> +    if (!interface) {
> +        create_node(node, QNODE_INTERFACE);
> +    } else if (interface->type != QNODE_INTERFACE) {
> +        printf("Error: Node %s is not an interface\n", node);

fprintf to stderr? Or g_printerr() ?

> +        abort();
> +    }
> +}
> +
> +/**
> + * build_machine_cmd_line(): builds the command line for the machine
> + * @node. The node name must be a valid qemu identifier, since it
> + * will be used to build the command line.
> + *
> + * It is also possible to pass an optional @args that will be
> + * concatenated to the command line.
> + *
> + * For machines, prepend -M to the machine name. ", @rgs" is added
> + * after the -M <machine> command.
> + */
> +static void build_machine_cmd_line(QOSGraphNode *node, const char *args)
> +{
> +    char *arch, *machine;
> +    qos_separate_arch_machine(node->name, &arch, &machine);
> +    if (args) {
> +        node->command_line = g_strconcat("-M ", machine, ",", args, NULL);
> +    } else {
> +        node->command_line = g_strconcat("-M ", machine, " ", NULL);
> +    }
> +}
> +
> +/**
> + * build_driver_cmd_line(): builds the command line for the driver
> + * @node. The node name must be a valid qemu identifier, since it
> + * will be used to build the command line.
> + *
> + * Driver do not need additional command line, since it will be
> + * provided by the edge options.
> + *
> + * For drivers, prepend -device to the node name.
> + */
> +static void build_driver_cmd_line(QOSGraphNode *node)
> +{
> +    node->command_line = g_strconcat(" -device ", node->name, NULL);
> +}
> +
> +/* qos_print_cb(): callback prints all path found by the DFS algorithm. */
> +static void qos_print_cb(QOSGraphNode *path, int length)
> +{
> +    #if QGRAPH_PRINT_DEBUG
> +        printf("%d elements\n", length);
> +
> +        if (!path) {
> +            return;
> +        }
> +
> +        while (path->path_edge) {
> +            printf("%s ", path->name);
> +            switch (path->path_edge->type) {
> +            case QEDGE_PRODUCES:
> +                printf("--PRODUCES--> ");
> +                break;
> +            case QEDGE_CONSUMED_BY:
> +                printf("--CONSUMED_BY--> ");
> +                break;
> +            case QEDGE_CONTAINS:
> +                printf("--CONTAINS--> ");
> +                break;
> +            }
> +            path = search_node(path->path_edge->dest);
> +        }
> +
> +        printf("%s\n\n", path->name);
> +    #endif
> +}
> +
> +/* qos_push(): push a node @el and edge @e in the qos_node_stack */
> +static void qos_push(QOSGraphNode *el, QOSStackElement *parent,
> +                     QOSGraphEdge *e)
> +{
> +    int len = 0; /* root is not counted */
> +    if (qos_node_tos == QOS_PATH_MAX_ELEMENT_SIZE) {
> +        g_printerr("QOSStack: full stack, cannot push");
> +        abort();
> +    }
> +
> +    if (parent) {
> +        len = parent->length + 1;
> +    }
> +    qos_node_stack[qos_node_tos++] = (QOSStackElement) {
> +        .node = el,
> +        .parent = parent,
> +        .parent_edge = e,
> +        .length = len,
> +    };
> +}
> +
> +/* qos_tos(): returns the top of stack, without popping */
> +static QOSStackElement *qos_tos(void)
> +{
> +    return &qos_node_stack[(qos_node_tos - 1)];

No need for the round brackets here.

> +}
> +
> +/* qos_pop(): pops an element from the tos, setting it unvisited*/
> +static QOSStackElement *qos_pop(void)
> +{
> +    if (qos_node_tos == 0) {
> +        g_printerr("QOSStack: empty stack, cannot pop");
> +        abort();
> +    }
> +    QOSStackElement *e = qos_tos();
> +    e->node->visited = FALSE;
> +    qos_node_tos--;
> +    return e;
> +}
> +
> +/**
> + * qos_reverse_path(): reverses the found path, going from
> + * test-to-machine to machine-to-test
> + */
> +static QOSGraphNode *qos_reverse_path(QOSStackElement *el)
> +{
> +    if (!el) {
> +        return NULL;
> +    }
> +
> +    el->node->path_edge = NULL;
> +
> +    while (el->parent) {
> +        el->parent->node->path_edge = el->parent_edge;
> +        el = el->parent;
> +    }
> +
> +    return el->node;
> +}
> +
> +/**
> + * qos_traverse_graph(): graph-walking algorithm, using Depth First Search it
> + * starts from the root @machine and walks all possible path until it
> + * reaches a test node.
> + * At that point, it reverses the path found and invokes the @callback.
> + *
> + * Being Depth First Search, time complexity is O(|V| + |E|), while
> + * space is O(|V|). In this case, the maximum stack size is set by
> + * QOS_PATH_MAX_ELEMENT_SIZE.
> + */
> +static void qos_traverse_graph(QOSGraphNode *root, QOSTestCallback callback)
> +{
> +    QOSGraphNode *v, *dest_node, *path;
> +    QOSStackElement *s_el;
> +    QOSGraphEdge *e, *next;
> +    QOSGraphEdgeList *list;
> +
> +    qos_push(root, NULL, NULL);
> +
> +    while (qos_node_tos > 0) {
> +        s_el = qos_tos();
> +        v = s_el->node;
> +        if (v->visited) {
> +            qos_pop();
> +            continue;
> +        }
> +        v->visited = TRUE;
> +        list = get_edgelist(v->name);
> +        if (!list) {
> +            qos_pop();
> +            if (v->type == QNODE_TEST) {
> +                v->visited = FALSE;
> +                path = qos_reverse_path(s_el);
> +                callback(path, s_el->length);
> +            }
> +        } else {
> +            QSLIST_FOREACH_SAFE(e, list, edge_list, next) {
> +                dest_node = search_node(e->dest);
> +
> +                if (!dest_node) {
> +                    printf("node %s in %s -> %s does not exist\n",
> +                            e->dest, v->name, e->dest);

fprintf to stderr? Or g_printerr() ?

> +                    abort();
> +                }
> +
> +                if (!dest_node->visited && dest_node->available) {
> +                    qos_push(dest_node, s_el, e);
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +/* QGRAPH API*/
> +
> +QOSGraphNode *qos_graph_get_node(const char *key)
> +{
> +    return search_node(key);
> +}
> +
> +bool qos_graph_has_node(const char *node)
> +{
> +    QOSGraphNode *n = search_node(node);
> +    return n != NULL;
> +}
> +
> +QOSNodeType qos_graph_get_node_type(const char *node)
> +{
> +    QOSGraphNode *n = search_node(node);
> +    if (n) {
> +        return n->type;
> +    }
> +    return -1;
> +}
> +
> +bool qos_graph_get_node_availability(const char *node)
> +{
> +    QOSGraphNode *n = search_node(node);
> +    if (n) {
> +        return n->available;
> +    }
> +    return FALSE;
> +}
> +
> +QOSGraphEdge *qos_graph_get_edge(const char *node, const char *dest)
> +{
> +    QOSGraphEdgeList *list = get_edgelist(node);
> +    return search_list_edges(list, dest);
> +}
> +
> +QOSEdgeType qos_graph_edge_get_type(QOSGraphEdge *edge)
> +{
> +    if (!edge) {
> +        return -1;
> +    }
> +    return edge->type;;
> +}
> +
> +char *qos_graph_edge_get_dest(QOSGraphEdge *edge)
> +{
> +    if (!edge) {
> +        return NULL;
> +    }
> +    return edge->dest;
> +}
> +
> +void *qos_graph_edge_get_arg(QOSGraphEdge *edge)
> +{
> +    if (!edge) {
> +        return NULL;
> +    }
> +    return edge->arg;
> +}
> +
> +char *qos_graph_edge_get_after_cmd_line(QOSGraphEdge *edge)
> +{
> +    if (!edge) {
> +        return NULL;
> +    }
> +    return edge->after_cmd_line;
> +}
> +
> +char *qos_graph_edge_get_before_cmd_line(QOSGraphEdge *edge)
> +{
> +    if (!edge) {
> +        return NULL;
> +    }
> +    return edge->before_cmd_line;
> +}
> +
> +char *qos_graph_edge_get_extra_device_opts(QOSGraphEdge *edge)
> +{
> +    if (!edge) {
> +        return NULL;
> +    }
> +    return edge->extra_device_opts;
> +}
> +
> +char *qos_graph_edge_get_name(QOSGraphEdge *edge)
> +{
> +    if (!edge) {
> +        return NULL;
> +    }
> +    return edge->edge_name;
> +}
> +
> +bool qos_graph_has_edge(const char *start, const char *dest)
> +{
> +    QOSGraphEdgeList *list = get_edgelist(start);
> +    QOSGraphEdge *e = search_list_edges(list, dest);
> +    if (e) {
> +        return TRUE;
> +    }
> +    return FALSE;
> +}
> +
> +QOSGraphNode *qos_graph_get_machine(const char *node)
> +{
> +    return search_machine(node);
> +}
> +
> +bool qos_graph_has_machine(const char *node)
> +{
> +    QOSGraphNode *m = search_machine(node);
> +    return m != NULL;
> +}
> +
> +void qos_print_graph(void)
> +{
> +    qos_graph_foreach_test_path(qos_print_cb);
> +}
> +
> +void qos_graph_init(void)
> +{
> +    if (!node_table) {
> +        node_table = g_hash_table_new_full(g_str_hash, g_str_equal,
> +                                           destroy_string, destroy_node);
> +        create_node(QOS_ROOT, QNODE_DRIVER);
> +    }
> +
> +    if (!edge_table) {
> +        edge_table = g_hash_table_new_full(g_str_hash, g_str_equal,
> +                                           destroy_string, destroy_edges);
> +    }
> +}
> +
> +void qos_graph_destroy(void)
> +{
> +    if (node_table) {
> +        g_hash_table_destroy(node_table);
> +    }
> +
> +    if (edge_table) {
> +        g_hash_table_destroy(edge_table);
> +    }
> +
> +    node_table = NULL;
> +    edge_table = NULL;
> +}
> +
> +void qos_node_destroy(void *key)
> +{
> +    g_hash_table_remove(node_table, key);
> +}
> +
> +void qos_edge_destroy(void *key)
> +{
> +    g_hash_table_remove(edge_table, key);
> +}
> +
> +void qos_add_test(const char *name, const char *interface,
> +                  QOSTestFunc test_func, QOSGraphTestOptions *opts)
> +{
> +    QOSGraphNode *node;
> +    char *test_name = g_strdup_printf("%s-tests/%s", interface, name);;
> +
> +    if (!opts) {
> +        opts = &(QOSGraphTestOptions) { };
> +    }
> +    node = create_node(test_name, QNODE_TEST);
> +    node->u.test.function = test_func;
> +    node->u.test.arg = opts->arg;
> +    assert(!opts->edge.arg);
> +    assert(!opts->edge.size_arg);
> +
> +    node->u.test.before = opts->before;
> +    node->u.test.subprocess = opts->subprocess;
> +    node->available = TRUE;
> +    add_edge(interface, test_name, QEDGE_CONSUMED_BY, &opts->edge);
> +    g_free(test_name);
> +}
> +
> +void qos_node_create_machine(const char *name, QOSCreateMachineFunc function)
> +{
> +    qos_node_create_machine_args(name, function, NULL);
> +}
> +
> +void qos_node_create_machine_args(const char *name,
> +                                  QOSCreateMachineFunc function,
> +                                  const char *opts)
> +{
> +    QOSGraphNode *node = create_node(name, QNODE_MACHINE);
> +    build_machine_cmd_line(node, opts);
> +    node->u.machine.constructor = function;
> +    add_edge(QOS_ROOT, name, QEDGE_CONTAINS, NULL);
> +}
> +
> +void qos_node_create_driver(const char *name, QOSCreateDriverFunc function)
> +{
> +    QOSGraphNode *node = create_node(name, QNODE_DRIVER);
> +    build_driver_cmd_line(node);
> +    node->u.driver.constructor = function;
> +}
> +
> +void qos_node_contains(const char *container, const char *contained,
> +                       ...)
> +{
> +    va_list va;
> +    va_start(va, contained);
> +    QOSGraphEdgeOptions *opts;
> +
> +    do {
> +        opts = va_arg(va, QOSGraphEdgeOptions *);
> +        add_edge(container, contained, QEDGE_CONTAINS, opts);
> +    } while (opts != NULL);
> +
> +    va_end(va);
> +}
> +
> +void qos_node_produces(const char *producer, const char *interface)
> +{
> +    create_interface(interface);
> +    add_edge(producer, interface, QEDGE_PRODUCES, NULL);
> +}
> +
> +void qos_node_consumes(const char *consumer, const char *interface,
> +                       QOSGraphEdgeOptions *opts)
> +{
> +    create_interface(interface);
> +    add_edge(interface, consumer, QEDGE_CONSUMED_BY, opts);
> +}
> +
> +void qos_graph_node_set_availability(const char *node, bool av)
> +{
> +    QOSGraphEdgeList *elist;
> +    QOSGraphNode *n = search_node(node);
> +    QOSGraphEdge *e, *next;
> +    if (!n) {
> +        return;
> +    }
> +    n->available = av;
> +    elist = get_edgelist(node);
> +    if (!elist) {
> +        return;
> +    }
> +    QSLIST_FOREACH_SAFE(e, elist, edge_list, next) {
> +        if (e->type == QEDGE_CONTAINS || e->type == QEDGE_PRODUCES) {
> +            qos_graph_node_set_availability(e->dest, av);
> +        }
> +    }
> +}
> +
> +void qos_graph_foreach_test_path(QOSTestCallback fn)
> +{
> +    QOSGraphNode *root = qos_graph_get_node(QOS_ROOT);
> +    qos_traverse_graph(root, fn);
> +}
> +
> +QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts)
> +{
> +    QOSGraphObject *obj;
> +
> +    g_assert(node->type == QNODE_MACHINE);
> +    obj = node->u.machine.constructor(qts);
> +    obj->free = g_free;
> +    return obj;
> +}
> +
> +QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent,
> +                               QGuestAllocator *alloc, void *arg)
> +{
> +    QOSGraphObject *obj;
> +
> +    g_assert(node->type == QNODE_DRIVER);
> +    obj = node->u.driver.constructor(parent, alloc, arg);
> +    obj->free = g_free;
> +    return obj;
> +}
> +
> +void qos_object_destroy(QOSGraphObject *obj)
> +{
> +    if (!obj) {
> +        return;
> +    }
> +    if (obj->destructor) {
> +        obj->destructor(obj);
> +    }
> +    if (obj->free) {
> +        obj->free(obj);
> +    }
> +}
> +
> +void qos_object_queue_destroy(QOSGraphObject *obj)
> +{
> +    g_test_queue_destroy((GDestroyNotify) qos_object_destroy, obj);
> +}
> +
> +void qos_object_start_hw(QOSGraphObject *obj)
> +{
> +    if (obj->start_hw) {
> +        obj->start_hw(obj);
> +    }
> +}
> +
> +void qos_separate_arch_machine(char *name, char **arch, char **machine)
> +{
> +    *arch = name;
> +    while (*name != '\0' && *name != '/') {
> +        name++;
> +    }
> +
> +    if (*name == '/' && (*name + 1) != '\0') {

Shouldn't that rather be *(name + 1) instead? Or rather use name[1] ?

> +        *machine = name + 1;
> +    } else {
> +        printf("Machine name has to be of the form <arch>/<machine>\n");

fprintf to stderr? Or g_printerr()?

> +        abort();
> +    }
> +}
> +
> +void qos_delete_abstract_cmd_line(const char *name, bool abstract)
> +{
> +    QOSGraphNode *node = search_node(name);
> +    if (node && abstract) {
> +        g_free(node->command_line);
> +        node->command_line = NULL;
> +    }
> +}
> diff --git a/tests/libqos/qgraph.h b/tests/libqos/qgraph.h
> new file mode 100644
> index 0000000..6ebb2e6
> --- /dev/null
> +++ b/tests/libqos/qgraph.h
> @@ -0,0 +1,575 @@
> +/*
> + * libqos driver framework
> + *
> + * Copyright (c) 2018 Emanuele Giuseppe Esposito <address@hidden>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License version 2 as published by the Free Software Foundation.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see 
> <http://www.gnu.org/licenses/>
> + */
> +
> +#ifndef QGRAPH_H
> +#define QGRAPH_H
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <stdbool.h>
> +#include <gmodule.h>
> +#include <glib.h>
> +#include "qemu/module.h"
> +#include "malloc.h"
> +
> +/* maximum path length */
> +#define QOS_PATH_MAX_ELEMENT_SIZE 50
> +
> +typedef struct QOSGraphObject QOSGraphObject;
> +typedef struct QOSGraphNode QOSGraphNode;
> +typedef struct QOSGraphEdge QOSGraphEdge;
> +typedef struct QOSGraphNodeOptions QOSGraphNodeOptions;
> +typedef struct QOSGraphEdgeOptions QOSGraphEdgeOptions;
> +typedef struct QOSGraphTestOptions QOSGraphTestOptions;
> +
> +/* Constructor for drivers, machines and test */
> +typedef void *(*QOSCreateDriverFunc) (void *parent, QGuestAllocator *alloc,
> +                                      void *addr);
> +typedef void *(*QOSCreateMachineFunc) (QTestState *qts);
> +typedef void (*QOSTestFunc) (void *parent, void *arg, QGuestAllocator 
> *alloc);
> +
> +/* QOSGraphObject functions */
> +typedef void *(*QOSGetDriver) (void *object, const char *interface);
> +typedef QOSGraphObject *(*QOSGetDevice) (void *object, const char *name);
> +typedef void (*QOSDestructorFunc) (QOSGraphObject *object);
> +typedef void (*QOSStartFunct) (QOSGraphObject *object);
> +
> +/* Test options functions */
> +typedef void *(*QOSBeforeTest) (GString *cmd_line, void *arg);
> +
> +/**
> + * SECTION: qgraph.h
> + * @title: Qtest Driver Framework
> + * @short_description: interfaces to organize drivers and tests
> + *                     as nodes in a graph
> + *
> + * This Qgraph API provides all basic functions to create a graph
> + * and instantiate nodes representing machines, drivers and tests
> + * representing their relations with CONSUMES, PRODUCES, and CONTAINS
> + * edges.
> + *
> + * The idea is to have a framework where each test asks for a specific
> + * driver, and the framework takes care of allocating the proper devices
> + * required and passing the correct command line arguments to QEMU.
> + *
> + * A node can be of four types:
> + * - QNODE_MACHINE:   for example "arm/raspi2"
> + * - QNODE_DRIVER:    for example "generic-sdhci"
> + * - QNODE_INTERFACE: for example "sdhci" (interface for all "-sdhci" 
> drivers)
> + *                     an interface is not explicitly created, it will be 
> auto-
> + *                     matically instantiated when a node consumes or 
> produces
> + *                     it.
> + * - QNODE_TEST:      for example "sdhci-test", consumes an interface and 
> tests
> + *                    the functions provided
> + *
> + * Notes for the nodes:
> + * - QNODE_MACHINE: each machine struct must have a QGuestAllocator and
> + *                  implement get_driver to return the allocator passing
> + *                  "memory". The function can also return NULL if the
> + *                  allocator is not set.
> + * - QNODE_DRIVER:  driver names must be unique, and machines and nodes
> + *                  planned to be "consumed" by other nodes must match QEMU
> + *                  drivers name, otherwise they won't be discovered
> + *
> + * An edge relation between two nodes (drivers or machines) X and Y can be:
> + * - X CONSUMES Y: Y can be plugged into X
> + * - X PRODUCES Y: X provides the interface Y
> + * - X CONTAINS Y: Y is part of X component
> + *
> + * Basic framework steps are the following:
> + * - All nodes and edges are created in their respective
> + *   machine/driver/test files
> + * - The framework starts QEMU and asks for a list of available devices
> + *   and machines (note that only machines and "consumed" nodes are mapped
> + *   1:1 with QEMU devices)
> + * - The framework walks the graph starting from the available machines and
> + *   performs a Depth First Search for tests
> + * - Once a test is found, the path is walked again and all drivers are
> + *   allocated accordingly and the final interface is passed to the test
> + * - The test is executed
> + * - Unused objects are cleaned and the path discovery is continued
> + *
> + * Depending on the QEMU binary used, only some drivers/machines will be
> + * available and only test that are reached by them will be executed.
> + *
> + * <example>
> + *   <title>Creating new driver an its interface</title>
> + *   <programlisting>
> + #include "libqos/qgraph.h"
> +
> + struct My_driver {
> +     QOSGraphObject obj;
> +     Node_produced prod;
> +     Node_contained cont;
> + }
> +
> + static void my_destructor(QOSGraphObject *obj)
> + {
> +    g_free(obj);
> + }
> +
> + static void my_get_driver(void *object, const char *interface) {
> +    My_driver *dev = object;
> +    if (!g_strcmp0(interface, "my_interface")) {
> +        return &dev->prod;
> +    }
> +    abort();
> + }

I'd rather write that as:

   My_driver *dev = object;
   g_assert(g_str_equal((interface, "my_interface"));
   return &dev->prod;

> + static void my_get_device(void *object, const char *device) {
> +    My_driver *dev = object;
> +    if (!g_strcmp0(device, "my_driver_contained")) {
> +        return &dev->cont;
> +    }
> +    abort();
> + }

dito.


> + static void *my_driver_constructor(void *node_consumed,
> +                                    QOSGraphObject *alloc)
> + {
> +    My_driver dev = g_new(My_driver, 1);
> +    // get the node pointed by the produce edge
> +    dev->obj.get_driver = my_get_driver;
> +    // get the node pointed by the contains
> +    dev->obj.get_device = my_get_device;
> +    // free the object
> +    dev->obj.destructor = my_destructor;
> +    do_something_with_node_consumed(node_consumed);
> +    // set all fields of contained device
> +    init_contained_device(&dev->cont);
> +    return &dev->obj;
> + }
> +
> + static void register_my_driver(void)
> + {
> +     qos_node_create_driver("my_driver", my_driver_constructor);
> +     // contained drivers don't need a constructor,
> +     // they will be init by the parent.
> +     qos_node_create_driver("my_driver_contained", NULL);
> +
> +    // For the sake of this example, assume machine x86_64/pc contains
> +    // "other_node".
> +    // This relation, along with the machine and "other_node" creation,
> +    // should be defined in the x86_64_pc-machine.c file.
> +    // "my_driver" will then consume "other_node"
> +    qos_node_contains("my_driver", "my_driver_contained");
> +    qos_node_produces("my_driver", "my_interface");
> +    qos_node_consumes("my_driver", "other_node");
> + }
> + *   </programlisting>
> + * </example>
> + *
> + * In the above example, all possible types of relations are created:
> + * node "my_driver" consumes, contains and produces other nodes.
> + * more specifically:
> + * x86_64/pc -->contains--> other_node <--consumes-- my_driver
> + *                                                       |
> + *                      my_driver_contained <--contains--+
> + *                                                       |
> + *                             my_interface <--produces--+
> + *
> + * or inverting the consumes edge in consumed_by:
> + *
> + * x86_64/pc -->contains--> other_node --consumed_by--> my_driver
> + *                                                           |
> + *                          my_driver_contained <--contains--+
> + *                                                           |
> + *                                 my_interface <--produces--+
> + *
> + * <example>
> + *   <title>Creating new test</title>
> + *   <programlisting>
> + * #include "libqos/qgraph.h"
> + *
> + * static void my_test_function(void *obj, void *data)
> + * {
> + *    Node_produced *interface_to_test = obj;
> + *    // test interface_to_test
> + * }
> + *
> + * static void register_my_test(void)
> + * {
> + *    qos_add_test("my_interface", "my_test", my_test_function);
> + * }
> + *
> + * libqos_init(register_my_test);
> + *
> + *   </programlisting>
> + * </example>
> + *
> + * Here a new test is created, consuming "my_interface" node
> + * and creating a valid path from a machine to a test.
> + * Final graph will be like this:
> + * x86_64/pc -->contains--> other_node <--consumes-- my_driver
> + *                                                        |
> + *                       my_driver_contained <--contains--+
> + *                                                        |
> + *        my_test --consumes--> my_interface <--produces--+
> + *
> + * or inverting the consumes edge in consumed_by:
> + *
> + * x86_64/pc -->contains--> other_node --consumed_by--> my_driver
> + *                                                           |
> + *                          my_driver_contained <--contains--+
> + *                                                           |
> + *        my_test <--consumed_by-- my_interface <--produces--+
> + *
> + * Assuming there the binary is
> + * QTEST_QEMU_BINARY=x86_64-softmmu/qemu-system-x86_64
> + * a valid test path will be:
> + * "/x86_64/pc/other_node/my_driver/my_interface/my_test".
> + *
> + * Additional examples are also in libqos/test-qgraph.c
> + *
> + * Command line:
> + * Command line is built by using node names and optional arguments
> + * passed by the user when building the edges.
> + *
> + * There are three types of command line arguments:
> + * - in node      : created from the node name. For example, machines will
> + *                  have "-M <machine>" to its command line, while devices
> + *                  "- device <device>". It is automatically done by the

s/- device/-device/


> + *                   framework.
> + * - after node   : added as additional argument to the node name.
> + *                  This argument is added optionally when creating edges,
> + *                  by setting the parameter @after_cmd_line and
> + *                  @extra_edge_opts in #QOSGraphEdgeOptions.
> + *                  The framework automatically adds
> + *                  a comma before @extra_edge_opts,
> + *                  because it is going to add attibutes
> + *                  after the destination node pointed by
> + *                  the edge containing these options, and automatically
> + *                  adds a space before @after_cmd_line, because it
> + *                  adds an additional device, not an attribute.
> + * - before node  : added as additional argument to the node name.
> + *                  This argument is added optionally when creating edges,
> + *                  by setting the parameter @before_cmd_line in
> + *                  #QOSGraphEdgeOptions. This attribute
> + *                  is going to add attibutes before the destination node
> + *                  pointed by the edge containing these options. It is
> + *                  helpful to commands that are not node-representable,
> + *                  such as "-fdsev" or "-netdev".
> + *
> + * While adding command line in edges is always used, not all nodes names are
> + * used in every path walk: this is because the contained or produced ones
> + * are already added by QEMU, so only nodes that "consumes" will be used to
> + * build the command line. Also, nodes that will have { "abstract" : true }
> + * as QMP attribute will loose their command line, since they are not proper
> + * devices to be added in QEMU.
> + *
> + * Example:
> + *
> + QOSGraphEdgeOptions opts = {
> +     .arg = NULL,
> +     .size_arg = 0,
> +     .after_cmd_line = "-device other",
> +     .before_cmd_line = "-netdev something",
> +     .extra_edge_opts = "addr=04.0",
> + };
> + QOSGraphNode * node = qos_node_create_driver("my_node", constructor);
> + qos_node_consumes_args("my_node", "interface", &opts);
> + *
> + * Will produce the following command line:
> + * "-netdev something -device my_node,addr=04.0 -device other"
> + */
> +
> +/**
> + * Edge options to be passed to the contains/consumes *_args function.
> + */
> +struct QOSGraphEdgeOptions {
> +    void *arg;                    /*
> +                                   * optional arg that will be used by
> +                                   * dest edge
> +                                   */
> +    uint32_t size_arg;            /*
> +                                   * optional arg size that will be used by
> +                                   * dest edge
> +                                   */
> +    const char *extra_device_opts;/*
> +                                   *optional additional command line for dest
> +                                   * edge, used to add additional attributes
> +                                   * *after* the node command line, the
> +                                   * framework automatically prepends ","
> +                                   * to this argument.
> +                                   */
> +    const char *before_cmd_line;  /*
> +                                   * optional additional command line for 
> dest
> +                                   * edge, used to add additional attributes
> +                                   * *before* the node command line, usually
> +                                   * other non-node represented commands,
> +                                   * like "-fdsev synt"
> +                                   */
> +    const char *after_cmd_line;   /*
> +                                   * optional extra command line to be added
> +                                   * after the device command. This option
> +                                   * is used to add other devices
> +                                   * command line that depend on current 
> node.
> +                                   * Automatically prepends " " to this
> +                                   * argument
> +                                   */
> +    const char *edge_name;        /*
> +                                   * optional edge to differentiate multiple
> +                                   * devices with same node name
> +                                   */
> +};
> +
> +/**
> + * Test options to be passed to the test functions.
> + */
> +struct QOSGraphTestOptions {
> +    QOSGraphEdgeOptions edge;   /* edge arguments that will be used by test.
> +                                 * Note that test *does not* use edge_name,
> +                                 * and uses instead arg and size_arg as
> +                                 * data arg for its test function.
> +                                 */
> +    void *arg;                  /* passed to the .before function, or to the
> +                                 * test function if there is no .before
> +                                 * function
> +                                 */
> +    QOSBeforeTest before;       /* executed before the test. Can add
> +                                 * additional parameters to the command line
> +                              * and modify the argument to the test function.
> +                                 */
> +    bool subprocess;            /* run the test in a subprocess */
> +};
> +
> +/**
> + * Each driver, test or machine of this framework will have a
> + * QOSGraphObject as first field.
> + *
> + * This set of functions offered by QOSGraphObject are executed
> + * in different stages of the framework:
> + * - get_driver / get_device : Once a machine-to-test path has been
> + * found, the framework traverses it again and allocates all the
> + * nodes, using the provided constructor. To satisfy their relations,
> + * i.e. for produces or contains, where a struct constructor needs
> + * an external parameter represented by the previous node,
> + * the framework will call get_device (for contains) or
> + * get_driver (for produces), depending on the edge type, passing
> + * them the name of the next node to be taken and getting from them
> + * the corresponding pointer to the actual structure of the next node to
> + * be used in the path.
> + *
> + * - start_hw: This function is executed after all the path objects
> + * have been allocated, but before the test is run. It starts the hw, setting
> + * the initial configurations (*_device_enable) and making it ready for the
> + * test.
> + *
> + * - destructor: Opposite to the node constructor, destroys the object.
> + * This function is called after the test has been executed, and performs
> + * a complete cleanup of each node allocated field. In case no constuctor
> + * is provided, no destructor will be called.
> + *
> + */
> +struct QOSGraphObject {
> +    /* for produces edges, returns void * */
> +    QOSGetDriver get_driver;
> +    /* for contains edges, returns a QOSGraphObject * */
> +    QOSGetDevice get_device;
> +    /* start the hw, get ready for the test */
> +    QOSStartFunct start_hw;
> +    /* destroy this QOSGraphObject */
> +    QOSDestructorFunc destructor;
> +    /* free the memory associated to the QOSGraphObject and its contained
> +     * children */
> +    GDestroyNotify free;
> +};
> +
> +/**
> + * qos_graph_init(): initialize the framework, creates two hash
> + * tables: one for the nodes and another for the edges.
> + */
> +void qos_graph_init(void);
> +
> +/**
> + * qos_graph_destroy(): deallocates all the hash tables,
> + * freeing all nodes and edges.
> + */
> +void qos_graph_destroy(void);
> +
> +/**
> + * qos_node_destroy(): removes and frees a node from the,
> + * nodes hash table.
> + */
> +void qos_node_destroy(void *key);
> +
> +/**
> + * qos_edge_destroy(): removes and frees an edge from the,
> + * edges hash table.
> + */
> +void qos_edge_destroy(void *key);
> +
> +/**
> + * qos_add_test(): adds a test node @name to the nodes hash table.
> + *
> + * The test will consume a @interface node, and once the
> + * graph walking algorithm has found it, the @test_func will be
> + * executed. It also has the possibility to
> + * add an optional @opts (see %QOSGraphNodeOptions).
> + *
> + * For tests, opts->edge.arg and size_arg represent the arg to pass
> + * to @test_func
> + */
> +void qos_add_test(const char *name, const char *interface,
> +                  QOSTestFunc test_func,
> +                  QOSGraphTestOptions *opts);
> +
> +/**
> + * qos_node_create_machine(): creates the machine @name and
> + * adds it to the node hash table.
> + *
> + * This node will be of type QNODE_MACHINE and have @function
> + * as constructor
> + */
> +void qos_node_create_machine(const char *name, QOSCreateMachineFunc 
> function);
> +
> +/**
> + * qos_node_create_machine_args(): same as qos_node_create_machine,
> + * but with the possibility to add an optional ", @opts" after -M machine
> + * command line.
> + */
> +void qos_node_create_machine_args(const char *name,
> +                                  QOSCreateMachineFunc function,
> +                                  const char *opts);
> +
> +/**
> + * qos_node_create_driver(): creates the driver @name and
> + * adds it to the node hash table.
> + *
> + * This node will be of type QNODE_DRIVER and have @function
> + * as constructor
> + */
> +void qos_node_create_driver(const char *name, QOSCreateDriverFunc function);
> +
> +/**
> + * qos_node_contains(): creates an edge of type QEDGE_CONTAINS and
> + * adds it to the edge list mapped to @container in the
> + * edge hash table.
> + *
> + * This edge will have @container as source and @contained as destination.
> + *
> + * It also has the possibility to add optional NULL-terminated
> + * @opts parameters (see %QOSGraphEdgeOptions)
> + *
> + * This function can be useful whrn there are multiple devices

s/whrn/when/


> + * with the same node name contained in a machine/other node
> + *
> + * For example, if "arm/raspi2" contains 2 "generic-sdhci"
> + * devices, the right commands will be:
> + * qos_node_create_machine("arm/raspi2");
> + * qos_node_create_driver("generic-sdhci", constructor);
> + * //assume rest of the fields are set NULL
> + * QOSGraphEdgeOptions op1 = { .edge_name = "emmc" };
> + * QOSGraphEdgeOptions op2 = { .edge_name = "sdcard" };
> + * qos_node_contains("arm/raspi2", "generic-sdhci", &op1, &op2, NULL);
> + *
> + * Of course this also requires that the @container's get_device function
> + * should implement a case for "emmc" and "sdcard".
> + *
> + * For contains, op1.arg and op1.size_arg represent the arg to pass
> + * to @contained constructor to properly initialize it.
> + */
> +void qos_node_contains(const char *container, const char *contained, ...);
> +
> +/**
> + * qos_node_produces(): creates an edge of type QEDGE_PRODUCES and
> + * adds it to the edge list mapped to @producer in the
> + * edge hash table.
> + *
> + * This edge will have @producer as source and @interface as destination.
> + */
> +void qos_node_produces(const char *producer, const char *interface);
> +
> +/**
> + * qos_node_consumes():  creates an edge of type QEDGE_CONSUMED_BY and
> + * adds it to the edge list mapped to @interface in the
> + * edge hash table.
> + *
> + * This edge will have @interface as source and @consumer as destination.
> + * It also has the possibility to add an optional @opts
> + * (see %QOSGraphEdgeOptions)
> + */
> +void qos_node_consumes(const char *consumer, const char *interface,
> +                       QOSGraphEdgeOptions *opts);
> +
> +/**
> + * qos_invalidate_command_line(): invalidates current command line, so that
> + * qgraph framework cannot try to cache the current command line and
> + * forces QEMU to restart.
> + */
> +void qos_invalidate_command_line(void);
> +
> +/**
> + * qos_get_current_command_line(): return the command line required by the
> + * machine and driver objects.  This is the same string that was passed to
> + * the test's "before" callback, if any.
> + */
> +const char *qos_get_current_command_line(void);
> +
> +/**
> + * qos_allocate_objects():
> + * @qts: The #QTestState that will be referred to by the machine object.
> + * @alloc: Where to store the allocator for the machine object, or %NULL.
> + *
> + * Allocate driver objects for the current test
> + * path, but relative to the QTestState @qts.
> + *
> + * Returns a test object just like the one that was passed to
> + * the test function, but relative to @qts.
> + */
> +void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc);
> +
> +/**
> + * qos_object_destroy(): calls the destructor for @obj
> + */
> +void qos_object_destroy(QOSGraphObject *obj);
> +
> +/**
> + * qos_object_queue_destroy(): queue the destructor for @obj so that it is
> + * called at the end of the test
> + */
> +void qos_object_queue_destroy(QOSGraphObject *obj);
> +
> +/**
> + * qos_object_start_hw(): calls the start_hw function for @obj
> + */
> +void qos_object_start_hw(QOSGraphObject *obj);
> +
> +/**
> + * qos_machine_new(): instantiate a new machine node
> + * @node: A machine node to be instantiated
> + * @qts: The #QTestState that will be referred to by the machine object.
> + *
> + * Returns a machine object.
> + */
> +QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts);
> +
> +/**
> + * qos_machine_new(): instantiate a new driver node
> + * @node: A driver node to be instantiated
> + * @parent: A #QOSGraphObject to be consumed by the new driver node
> + * @alloc: An allocator to be used by the new driver node.
> + * @arg: The argument for the consumed-by edge to @node.
> + *
> + * Calls the constructor for the driver object.
> + */
> +QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent,
> +                               QGuestAllocator *alloc, void *arg);
> +
> +
> +#endif
> diff --git a/tests/libqos/qgraph_internal.h b/tests/libqos/qgraph_internal.h
> new file mode 100644
> index 0000000..bb4d82c
> --- /dev/null
> +++ b/tests/libqos/qgraph_internal.h
> @@ -0,0 +1,264 @@
> +/*
> + * libqos driver framework
> + *
> + * Copyright (c) 2018 Emanuele Giuseppe Esposito <address@hidden>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License version 2 as published by the Free Software Foundation.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see 
> <http://www.gnu.org/licenses/>
> + */
> +
> +#ifndef QGRAPH_EXTRA_H
> +#define QGRAPH_EXTRA_H
> +
> +/* This header is declaring additional helper functions defined in
> + * libqos/qgraph.c
> + * It should not be included in tests
> + */
> +
> +#include "libqos/qgraph.h"
> +
> +typedef struct QOSGraphMachine QOSGraphMachine;
> +typedef struct QOSGraphEdgeList QOSGraphEdgeList;
> +typedef enum QOSEdgeType QOSEdgeType;
> +typedef enum QOSNodeType QOSNodeType;
> +
> +/* callback called when the walk path algorithm found a
> + * valid path
> + */
> +typedef void (*QOSTestCallback) (QOSGraphNode *path, int len);
> +
> +/* edge types*/
> +enum QOSEdgeType {
> +    QEDGE_CONTAINS,
> +    QEDGE_PRODUCES,
> +    QEDGE_CONSUMED_BY
> +};
> +
> +/* node types*/
> +enum QOSNodeType {
> +    QNODE_MACHINE,
> +    QNODE_DRIVER,
> +    QNODE_INTERFACE,
> +    QNODE_TEST
> +};
> +
> +/* Graph Node */
> +struct QOSGraphNode {
> +    QOSNodeType type;
> +    bool available;     /* set by QEMU via QMP, used during graph walk */
> +    bool visited;       /* used during graph walk */
> +    char *name;         /* used to identify the node */
> +    char *command_line; /* used to start QEMU at test execution */
> +    union {
> +        struct {
> +            QOSCreateDriverFunc constructor;
> +        } driver;
> +        struct {
> +            QOSCreateMachineFunc constructor;
> +        } machine;
> +        struct {
> +            QOSTestFunc function;
> +            void *arg;
> +            QOSBeforeTest before;
> +            bool subprocess;
> +        } test;
> +    } u;
> +
> +    /**
> +     * only used when traversing the path, never rely on that except in the
> +     * qos_traverse_graph callback function
> +     */
> +    QOSGraphEdge *path_edge;
> +};
> +
> +/**
> + * qos_graph_get_node(): returns the node mapped to that @key.
> + * It performs an hash map search O(1)
> + *
> + * Returns: on success: the %QOSGraphNode
> + *          otherwise: #NULL
> + */
> +QOSGraphNode *qos_graph_get_node(const char *key);
> +
> +/**
> + * qos_graph_has_node(): returns #TRUE if the node
> + * has map has a node mapped to that @key.
> + */
> +bool qos_graph_has_node(const char *node);
> +
> +/**
> + * qos_graph_get_node_type(): returns the %QOSNodeType
> + * of the node @node.
> + * It performs an hash map search O(1)
> + * Returns: on success: the %QOSNodeType
> + *          otherwise: #-1
> + */
> +QOSNodeType qos_graph_get_node_type(const char *node);
> +
> +/**
> + * qos_graph_get_node_availability(): returns the availability (boolean)
> + * of the node @node.
> + */
> +bool qos_graph_get_node_availability(const char *node);
> +
> +/**
> + * qos_graph_get_edge(): returns the edge
> + * linking of the node @node with @dest.
> + *
> + * Returns: on success: the %QOSGraphEdge
> + *          otherwise: #NULL
> + */
> +QOSGraphEdge *qos_graph_get_edge(const char *node, const char *dest);
> +
> +/**
> + * qos_graph_edge_get_type(): returns the edge type
> + * of the edge @edge.
> + *
> + * Returns: on success: the %QOSEdgeType
> + *          otherwise: #-1
> + */
> +QOSEdgeType qos_graph_edge_get_type(QOSGraphEdge *edge);
> +
> +/**
> + * qos_graph_edge_get_dest(): returns the name of the node
> + * pointed as destination of edge @edge.
> + *
> + * Returns: on success: the destination
> + *          otherwise: #NULL
> + */
> +char *qos_graph_edge_get_dest(QOSGraphEdge *edge);
> +
> +/**
> + * qos_graph_has_edge(): returns #TRUE if there
> + * exists an edge from @start to @dest.
> + */
> +bool qos_graph_has_edge(const char *start, const char *dest);
> +
> +/**
> + * qos_graph_edge_get_arg(): returns the args assigned
> + * to that @edge.
> + *
> + * Returns: on success: the arg
> + *          otherwise: #NULL
> + */
> +void *qos_graph_edge_get_arg(QOSGraphEdge *edge);
> +
> +/**
> + * qos_graph_edge_get_after_cmd_line(): returns the edge
> + * command line that will be added after all the node arguments
> + * and all the before_cmd_line arguments.
> + *
> + * Returns: on success: the char* arg
> + *          otherwise: #NULL
> + */
> +char *qos_graph_edge_get_after_cmd_line(QOSGraphEdge *edge);
> +
> +/**
> + * qos_graph_edge_get_before_cmd_line(): returns the edge
> + * command line that will be added before the node command
> + * line argument.
> + *
> + * Returns: on success: the char* arg
> + *          otherwise: #NULL
> + */
> +char *qos_graph_edge_get_before_cmd_line(QOSGraphEdge *edge);
> +
> +/**
> + * qos_graph_edge_get_extra_device_opts(): returns the arg
> + * command line that will be added to the node command
> + * line argument.
> + *
> + * Returns: on success: the char* arg
> + *          otherwise: #NULL
> + */
> +char *qos_graph_edge_get_extra_device_opts(QOSGraphEdge *edge);
> +
> +/**
> + * qos_graph_edge_get_name(): returns the name
> + * assigned to the destination node (different only)
> + * if there are multiple devices with the same node name
> + * e.g. a node has two "generic-sdhci", "emmc" and "sdcard"
> + * there will be two edges with edge_name ="emmc" and "sdcard"
> + *
> + * Returns always the char* edge_name
> + */
> +char *qos_graph_edge_get_name(QOSGraphEdge *edge);
> +
> +/**
> + * qos_graph_get_machine(): returns the machine assigned
> + * to that @node name.
> + *
> + * It performs a search only trough the list of machines
> + * (i.e. the QOS_ROOT child).
> + *
> + * Returns: on success: the %QOSGraphNode
> + *          otherwise: #NULL
> + */
> +QOSGraphNode *qos_graph_get_machine(const char *node);
> +
> +/**
> + * qos_graph_has_machine(): returns #TRUE if the node
> + * has map has a node mapped to that @node.
> + */
> +bool qos_graph_has_machine(const char *node);
> +
> +
> +/**
> + * qos_print_graph(): walks the graph and prints
> + * all machine-to-test paths.
> + */
> +void qos_print_graph(void);
> +
> +/**
> + * qos_graph_foreach_test_path(): executes the Depth First search
> + * algorithm and applies @fn to all discovered paths.
> + *
> + * See qos_traverse_graph() in qgraph.c for more info on
> + * how it works.
> + */
> +void qos_graph_foreach_test_path(QOSTestCallback fn);
> +
> +/**
> + * qos_separate_arch_machine(): separate arch from machine.
> + * This function requires every machine @name to be in the form
> + * <arch>/<machine_name>, like "arm/raspi2" or "x86_64/pc".
> + *
> + * The function will split then the string in two parts,
> + * assigning @arch to point to <arch>/<machine_name>, and
> + * @machine to <machine_name>.
> + *
> + * For example, "x86_64/pc" will be split in this way:
> + * *arch = "x86_64/pc"
> + * *machine = "pc"
> + *
> + * Note that this function *does not* allocate any new string,
> + * but just sets the pointer *arch and *machine to the respective
> + * part of the string.
> + */
> +void qos_separate_arch_machine(char *name, char **arch, char **machine);

The arch parameter looks unnecessary to me, since it will be the same
pointer like "name" afterwards, and the callers already know that one,
don't they?


> +/**
> + * qos_delete_abstract_cmd_line(): if @abstract is #TRUE, delete the
> + * command line present in node mapped with key @name.
> + *
> + * This function is called when the QMP query returns a node with
> + * { "abstract" : <boolean> } attribute.
> + */
> +void qos_delete_abstract_cmd_line(const char *name, bool abstract);
> +
> +/**
> + * qos_graph_node_set_availability(): sets the node identified
> + * by @node with availability @av.
> + */
> +void qos_graph_node_set_availability(const char *node, bool av);
> +
> +#endif
> diff --git a/tests/libqtest.h b/tests/libqtest.h
> index ed88ff9..2fd1e51 100644
> --- a/tests/libqtest.h
> +++ b/tests/libqtest.h
> @@ -576,6 +576,9 @@ static inline QTestState *qtest_start(const char *args)
>   */
>  static inline void qtest_end(void)
>  {
> +    if (!global_qtest) {
> +        return;
> +    }
>      qtest_quit(global_qtest);
>      global_qtest = NULL;
>  }
> diff --git a/tests/qos-test.c b/tests/qos-test.c
> new file mode 100644
> index 0000000..d85ed71
> --- /dev/null
> +++ b/tests/qos-test.c
> @@ -0,0 +1,470 @@
> +/*
> + * libqos driver framework
> + *
> + * Copyright (c) 2018 Emanuele Giuseppe Esposito <address@hidden>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License version 2 as published by the Free Software Foundation.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see 
> <http://www.gnu.org/licenses/>
> + */
> +
> +#include <getopt.h>
> +#include "qemu/osdep.h"
> +#include "libqtest.h"
> +#include "qapi/qmp/qdict.h"
> +#include "qapi/qmp/qbool.h"
> +#include "qapi/qmp/qstring.h"
> +#include "qapi/qmp/qlist.h"
> +#include "libqos/malloc.h"
> +#include "libqos/qgraph.h"
> +#include "libqos/qgraph_internal.h"
> +
> +static char *old_path;
> +
> +/**
> + * create_machine_name(): appends the architecture to @name if
> + * @is_machine is valid.
> + */
> +static void create_machine_name(const char **name, bool is_machine)
> +{
> +    const char *arch;
> +    if (!is_machine) {
> +        return;
> +    }
> +    arch = qtest_get_arch();
> +    *name = g_strconcat(arch, "/", *name, NULL);
> +}

A void function which is returning a value via a char ** ? ... well, I'd
rather write that as:

static const char *create_machine_name(const char *name, bool is_machine)
{
    if (!is_machine) {
        return name;
    }
    return g_strconcat(qtest_get_arch(), "/", *name, NULL);
}


> +/**
> + * destroy_machine_name(): frees the given @name if
> + * @is_machine is valid.
> + */
> +static void destroy_machine_name(const char *name, bool is_machine)
> +{
> +    if (!is_machine) {
> +        return;
> +    }
> +    g_free((char *)name);
> +}
> +
> +/**
> + * apply_to_qlist(): using QMP queries QEMU for a list of
> + * machines and devices available, and sets the respective node
> + * as TRUE. If a node is found, also all its produced and contained
> + * child are marked available.
> + *
> + * See qos_graph_node_set_availability() for more info
> + */
> +static void apply_to_qlist(QList *list, bool is_machine)
> +{
> +    const QListEntry *p;
> +    const char *name;
> +    bool abstract;
> +    QDict *minfo;
> +    QObject *qobj;
> +    QString *qstr;
> +    QBool *qbol;
> +
> +    for (p = qlist_first(list); p; p = qlist_next(p)) {
> +        minfo = qobject_to(QDict, qlist_entry_obj(p));
> +        qobj = qdict_get(minfo, "name");
> +        qstr = qobject_to(QString, qobj);
> +        name = qstring_get_str(qstr);
> +
> +        create_machine_name(&name, is_machine);
> +        qos_graph_node_set_availability(name, TRUE);
> +
> +        qobj = qdict_get(minfo, "alias");
> +        if (qobj) {
> +            qstr = qobject_to(QString, qobj);
> +
> +            destroy_machine_name(name, is_machine);
> +            name = qstring_get_str(qstr);
> +
> +            create_machine_name(&name, is_machine);
> +            qos_graph_node_set_availability(name, TRUE);
> +        }
> +
> +        qobj = qdict_get(minfo, "abstract");
> +        if (qobj) {
> +            qbol = qobject_to(QBool, qobj);
> +            abstract = qbool_get_bool(qbol);
> +            qos_delete_abstract_cmd_line(name, abstract);
> +        }
> +
> +        destroy_machine_name(name, is_machine);
> +    }
> +}
> +
> +/**
> + * qos_set_machines_devices_available(): sets availability of qgraph
> + * machines and devices.
> + *
> + * This function firstly starts QEMU with "-machine none" option,
> + * and then executes the QMP protocol asking for the list of devices
> + * and machines available.
> + *
> + * for each of these items, it looks up the corresponding qgraph node,
> + * setting it as available. The list currently returns all devices that
> + * are either machines or QEDGE_CONSUMED_BY other nodes.
> + * Therefore, in order to mark all other nodes, it recursively sets
> + * all its QEDGE_CONTAINS and QEDGE_PRODUCES child as available too.
> + */
> +static void qos_set_machines_devices_available(void)
> +{
> +    QDict *response;
> +    QDict *args = qdict_new();
> +    QList *list;
> +
> +    qtest_start("-machine none");
> +    response = qmp("{ 'execute': 'query-machines' }");
> +    list = qdict_get_qlist(response, "return");
> +
> +    apply_to_qlist(list, TRUE);
> +
> +    qobject_unref(response);
> +
> +    qdict_put_bool(args, "abstract", TRUE);
> +    qdict_put_str(args, "implements", "device");
> +
> +    response = qmp("{'execute': 'qom-list-types',"
> +                   " 'arguments': %p }", args);
> +    g_assert(qdict_haskey(response, "return"));
> +    list = qdict_get_qlist(response, "return");
> +
> +    apply_to_qlist(list, FALSE);
> +
> +    qtest_end();
> +    qobject_unref(response);
> +
> +}
> +
> +static QGuestAllocator *get_machine_allocator(QOSGraphObject *obj)
> +{
> +    if (obj->get_driver) {
> +        return obj->get_driver(obj, "memory");
> +    } else {
> +        return NULL;
> +    }
> +}
> +
> +static void restart_qemu_or_continue(char *path)
> +{
> +    /* compares the current command line with the
> +     * one previously executed: if they are the same,
> +     * don't restart QEMU, if they differ, stop previous
> +     * QEMU subprocess (if active) and start over with
> +     * the new command line
> +     */
> +    if (g_strcmp0(old_path, path)) {
> +        qtest_end();
> +        qos_invalidate_command_line();
> +        old_path = g_strdup(path);
> +        qtest_start(path);
> +    } else { /* if cmd line is the same, reset the guest */
> +        qobject_unref(qmp("{ 'execute': 'system_reset' }"));
> +        qmp_eventwait("RESET");
> +    }
> +}
> +
> +void qos_invalidate_command_line(void)
> +{
> +    g_free(old_path);
> +    old_path = NULL;
> +}
> +
> +/**
> + * allocate_objects(): given an array of nodes @arg,
> + * walks the path invoking all constructors and
> + * passing the corresponding parameter in order to
> + * continue the objects allocation.
> + * Once the test is reached, return the object it consumes.
> + *
> + * Since the machine and QEDGE_CONSUMED_BY nodes allocate
> + * memory in the constructor, g_test_queue_destroy is used so
> + * that after execution they can be safely free'd.  (The test's
> + * ->before callback is also welcome to use g_test_queue_destroy).
> + *
> + * Note: as specified in walk_path() too, @arg is an array of
> + * char *, where arg[0] is a pointer to the command line
> + * string that will be used to properly start QEMU when executing
> + * the test, and the remaining elements represent the actual objects
> + * that will be allocated.
> + */
> +static void *allocate_objects(QTestState *qts, char **path, QGuestAllocator 
> **p_alloc)
> +{
> +    int current = 0;
> +    QGuestAllocator *alloc;
> +    QOSGraphObject *parent = NULL;
> +    QOSGraphEdge *edge;
> +    QOSGraphNode *node;
> +    void *edge_arg;
> +    void *obj;
> +
> +    node = qos_graph_get_node(path[current]);
> +    g_assert(node->type == QNODE_MACHINE);
> +
> +    obj = qos_machine_new(node, qts);
> +    qos_object_queue_destroy(obj);
> +
> +    alloc = get_machine_allocator(obj);
> +    if (p_alloc) {
> +        *p_alloc = alloc;
> +    }
> +
> +    for (;;) {
> +        if (node->type != QNODE_INTERFACE) {
> +            qos_object_start_hw(obj);
> +            parent = obj;
> +        }
> +
> +        /* follow edge and get object for next node constructor */
> +        current++;
> +        edge = qos_graph_get_edge(path[current - 1], path[current]);
> +        node = qos_graph_get_node(path[current]);
> +
> +        if (node->type == QNODE_TEST) {
> +            g_assert(qos_graph_edge_get_type(edge) == QEDGE_CONSUMED_BY);
> +            return obj;
> +        }
> +
> +        switch (qos_graph_edge_get_type(edge)) {
> +        case QEDGE_PRODUCES:
> +            obj = parent->get_driver(parent, path[current]);
> +            break;
> +
> +        case QEDGE_CONSUMED_BY:
> +            edge_arg = qos_graph_edge_get_arg(edge);
> +            obj = qos_driver_new(node, obj, alloc, edge_arg);
> +            qos_object_queue_destroy(obj);
> +            break;
> +
> +        case QEDGE_CONTAINS:
> +            obj = parent->get_device(parent, path[current]);
> +            break;
> +        }
> +    }
> +}
> +
> +/* The argument to run_one_test, which is the test function that is 
> registered
> + * with GTest, is a vector of strings.  The first item is the initial command
> + * line (before it is modified by the test's "before" function), the 
> remaining
> + * items are node names forming the path to the test node.
> + */
> +static char **current_path;
> +
> +const char *qos_get_current_command_line(void)
> +{
> +    return current_path[0];
> +}
> +
> +void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc)
> +{
> +    return allocate_objects(qts, current_path + 1, p_alloc);
> +}
> +
> +/**
> + * run_one_test(): given an array of nodes @arg,
> + * walks the path invoking all constructors and
> + * passing the corresponding parameter in order to
> + * continue the objects allocation.
> + * Once the test is reached, its function is executed.
> + *
> + * Since the machine and QEDGE_CONSUMED_BY nodes allocate
> + * memory in the constructor, g_test_queue_destroy is used so
> + * that after execution they can be safely free'd.  The test's
> + * ->before callback is also welcome to use g_test_queue_destroy.
> + *
> + * Note: as specified in walk_path() too, @arg is an array of
> + * char *, where arg[0] is a pointer to the command line
> + * string that will be used to properly start QEMU when executing
> + * the test, and the remaining elements represent the actual objects
> + * that will be allocated.
> + *
> + * The order of execution is the following:
> + * 1) @before test function as defined in the given QOSGraphTestOptions
> + * 2) start QEMU
> + * 3) call all nodes constructor and get_driver/get_device depending on edge,
> + *    start the hardware (*_device_enable functions)
> + * 4) start test
> + */
> +static void run_one_test(const void *arg)
> +{
> +    QOSGraphNode *test_node;
> +    QGuestAllocator *alloc = NULL;
> +    void *obj;
> +    char **path = (char **) arg;
> +    GString *cmd_line = g_string_new(path[0]);
> +    void *test_arg;
> +
> +    /* Before test */
> +    current_path = path;
> +    test_node = qos_graph_get_node(path[(g_strv_length(path) - 1)]);
> +    test_arg = test_node->u.test.arg;
> +    if (test_node->u.test.before) {
> +        test_arg = test_node->u.test.before(cmd_line, test_arg);
> +    }
> +
> +    restart_qemu_or_continue(cmd_line->str);
> +    g_string_free(cmd_line, TRUE);
> +
> +    obj = qos_allocate_objects(global_qtest, &alloc);
> +    test_node->u.test.function(obj, test_arg, alloc);
> +}
> +
> +static void subprocess_run_one_test(const void *arg)
> +{
> +    const gchar *path = arg;
> +    g_test_trap_subprocess(path, 0, 0);
> +    g_test_trap_assert_passed();
> +}
> +
> +/*
> + * in this function, 2 path will be built:
> + * path_str, a one-string path (ex "pc/i440FX-pcihost/...")
> + * path_vec, a string-array path (ex [0] = "pc", [1] = "i440FX-pcihost").
> + *
> + * path_str will be only used to build the test name, and won't need the
> + * architecture name at beginning, since it will be added by 
> qtest_add_func().
> + *
> + * path_vec is used to allocate all constructors of the path nodes.
> + * Each name in this array except position 0 must correspond to a valid
> + * QOSGraphNode name.
> + * Position 0 is special, initially contains just the <machine> name of
> + * the node, (ex for "x86_64/pc" it will be "pc"), used to build the test
> + * path (see below). After it will contain the command line used to start
> + * qemu with all required devices.
> + *
> + * Note that the machine node name must be with format <arch>/<machine>
> + * (ex "x86_64/pc"), because it will identify the node "x86_64/pc"
> + * and start QEMU with "-M pc". For this reason,
> + * when building path_str, path_vec
> + * initially contains the <machine> at position 0 ("pc"),
> + * and the node name at position 1 (<arch>/<machine>)
> + * ("x86_64/pc"), followed by the rest of the nodes.
> + */
> +static void walk_path(QOSGraphNode *orig_path, int len)
> +{
> +    QOSGraphNode *path;
> +    QOSGraphEdge *edge;
> +
> +    /* etype set to QEDGE_CONSUMED_BY so that machine can add to the command 
> line */
> +    QOSEdgeType etype = QEDGE_CONSUMED_BY;
> +
> +    /* twice QOS_PATH_MAX_ELEMENT_SIZE since each edge can have its arg */
> +    char **path_vec = g_new0(char *, (QOS_PATH_MAX_ELEMENT_SIZE * 2));
> +    int path_vec_size = 0;
> +
> +    char *machine = NULL, *arch = NULL;
> +    char *after_cmd = NULL, *before_cmd = NULL, *after_device = NULL;
> +    char *node_name = orig_path->name, *path_str;
> +
> +    GString *cmd_line = g_string_new("");
> +    GString *cmd_line2 = g_string_new("");
> +
> +    path = qos_graph_get_node(node_name); /* root */
> +    node_name = qos_graph_edge_get_dest(path->path_edge); /* machine name */
> +
> +    qos_separate_arch_machine(node_name, &arch, &machine);
> +    path_vec[path_vec_size++] = arch;
> +    path_vec[path_vec_size++] = machine;
> +
> +    for (;;) {
> +        path = qos_graph_get_node(node_name);
> +        if (!path->path_edge) {
> +            break;
> +        }
> +
> +        node_name = qos_graph_edge_get_dest(path->path_edge);
> +
> +        /* append node command line + previous edge command line */
> +        if (path->command_line && etype == QEDGE_CONSUMED_BY) {
> +            g_string_append(cmd_line, path->command_line);
> +            if (after_device) {
> +                g_string_append(cmd_line, after_device);
> +            }
> +        }
> +
> +        path_vec[path_vec_size++] = qos_graph_edge_get_name(path->path_edge);
> +        /* detect if edge has command line args */
> +        after_cmd = qos_graph_edge_get_after_cmd_line(path->path_edge);
> +        after_device = qos_graph_edge_get_extra_device_opts(path->path_edge);
> +        before_cmd = qos_graph_edge_get_before_cmd_line(path->path_edge);
> +        edge = qos_graph_get_edge(path->name, node_name);
> +        etype = qos_graph_edge_get_type(edge);
> +
> +        if (before_cmd) {
> +            g_string_append(cmd_line, before_cmd);
> +        }
> +        if (after_cmd) {
> +            g_string_append(cmd_line2, after_cmd);
> +        }
> +    }
> +
> +    path_vec[path_vec_size++] = NULL;
> +    if (after_device) {
> +        g_string_append(cmd_line, after_device);
> +    }
> +    g_string_append(cmd_line, cmd_line2->str);
> +    g_string_free(cmd_line2, TRUE);
> +
> +    /* here position 0 has <arch>/<machine>, position 1 has <machine>.
> +     * The path must not have the <arch>
> +     */
> +    path_str = g_strjoinv("/", path_vec + 1);
> +
> +    /* put arch/machine in position 1 so run_one_test can do its work
> +     * and add the command line at position 0.
> +     */
> +    path_vec[0] = g_string_free(cmd_line, FALSE);
> +    path_vec[1] = arch;
> +
> +    if (path->u.test.subprocess) {
> +        gchar *subprocess_path = g_strdup_printf("/%s/%s/subprocess",
> +                                                 qtest_get_arch(), path_str);
> +        qtest_add_data_func(path_str, subprocess_path, 
> subprocess_run_one_test);
> +        g_test_add_data_func(subprocess_path, path_vec, run_one_test);
> +    } else {
> +        qtest_add_data_func(path_str, path_vec, run_one_test);
> +    }
> +
> +    g_free(path_str);
> +}
> +
> +
> +
> +/**
> + * main(): heart of the qgraph framework.
> + *
> + * - Initializes the glib test framework
> + * - Creates the graph by invoking the various _init constructors
> + * - Starts QEMU to mark the available devices
> + * - Walks the graph, and each path is added to
> + *   the glib test framework (walk_path)
> + * - Runs the tests, calling allocate_object() and allocating the
> + *   machine/drivers/test objects
> + * - Cleans up everything
> + */
> +int main(int argc, char **argv)
> +{
> +    g_test_init(&argc, &argv, NULL);
> +    qos_graph_init();
> +    module_call_init(MODULE_INIT_QOM);
> +    module_call_init(MODULE_INIT_LIBQOS);
> +    qos_set_machines_devices_available();
> +
> +    qos_graph_foreach_test_path(walk_path);
> +    g_test_run();
> +    qtest_end();
> +    qos_graph_destroy();
> +    g_free(old_path);
> +    return 0;
> +}
[...]

I did not do a very detailed review, but from a quick glance, this looks
quite good to me already (apart from the few minor issues that I've
mentioned above)

 Thomas



reply via email to

[Prev in Thread] Current Thread [Next in Thread]