[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
- [Qemu-devel] [PATCH 22/71] tests/libqos: sdhci driver and interface nodes, (continued)
- [Qemu-devel] [PATCH 22/71] tests/libqos: sdhci driver and interface nodes, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 21/71] tests/libqos: x86_64/pc machine node, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 23/71] tests/libqos: arm/raspi2 machine node, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 24/71] tests/libqos: arm/smdkc210 machine node, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 20/71] tests/libqos: pci-pc driver and interface nodes, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 28/71] qos-test: sdhci test node, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 30/71] tests/libqos: pci-spapr driver and interface nodes, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 19/71] tests: qgraph API for the qtest driver framework, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 25/71] tests/libqos: arm/sabrelite machine node, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 27/71] tests/libqos: aarch64/xlnx-zcu102 machine node, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 26/71] tests/libqos: arm/xilinx-zynq-a9 machine node, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 29/71] tests/qgraph: add generic PCI testcases, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 31/71] tests/qgraph: ppc64/pseries machine node, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 32/71] tests/libqos: has_buggy_msi flag, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 33/71] tests/libqos: e1000e driver and interface nodes, Paolo Bonzini, 2018/12/03
- [Qemu-devel] [PATCH 35/71] tests/libqos: virtio-pci driver and interface nodes, Paolo Bonzini, 2018/12/03