On 2018-12-03 16:32, Paolo Bonzini wrote: > From: Emanuele Giuseppe Esposito <e.emanuelegiuse...@gmail.com> > > 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 <e.emanuelegiuse...@gmail.com> > [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 > <e.emanuelegiuse...@gmail.com> > + * > + * 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 > <e.emanuelegiuse...@gmail.com> > + * > + * 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 > <e.emanuelegiuse...@gmail.com> > + * > + * 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 > <e.emanuelegiuse...@gmail.com> > + * > + * 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