提交 fc281c80 编写于 作者: E Emanuele Giuseppe Esposito 提交者: Paolo Bonzini

tests: qgraph API for the qtest driver framework

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: NEmanuele Giuseppe Esposito <e.emanuelegiuseppe@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)]
Signed-off-by: NPaolo Bonzini <pbonzini@redhat.com>
上级 eb5937ba
......@@ -7647,7 +7647,7 @@ fi
# tests might fail. Prefer to keep the relevant files in their own
# directory and symlink the directory instead.
DIRS="tests tests/tcg tests/tcg/cris tests/tcg/lm32 tests/libqos tests/qapi-schema tests/tcg/xtensa tests/qemu-iotests tests/vm"
DIRS="$DIRS tests/fp"
DIRS="$DIRS tests/fp tests/qgraph"
DIRS="$DIRS docs docs/interop fsdev scsi"
DIRS="$DIRS pc-bios/optionrom pc-bios/spapr-rtas pc-bios/s390-ccw"
DIRS="$DIRS roms/seabios roms/vgabios"
......
......@@ -45,6 +45,7 @@ typedef enum {
MODULE_INIT_QOM,
MODULE_INIT_TRACE,
MODULE_INIT_XEN_BACKEND,
MODULE_INIT_LIBQOS,
MODULE_INIT_MAX
} module_init_type;
......@@ -54,6 +55,7 @@ typedef enum {
#define trace_init(function) module_init(function, MODULE_INIT_TRACE)
#define xen_backend_init(function) module_init(function, \
MODULE_INIT_XEN_BACKEND)
#define libqos_init(function) module_init(function, MODULE_INIT_LIBQOS)
#define block_module_load_one(lib) module_load_one("block-", lib)
#define ui_module_load_one(lib) module_load_one("ui-", lib)
......
......@@ -732,7 +732,10 @@ tests/test-crypto-ivgen$(EXESUF): tests/test-crypto-ivgen.o $(test-crypto-obj-y)
tests/test-crypto-afsplit$(EXESUF): tests/test-crypto-afsplit.o $(test-crypto-obj-y)
tests/test-crypto-block$(EXESUF): tests/test-crypto-block.o $(test-crypto-obj-y)
libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
libqgraph-obj-y = tests/libqos/qgraph.o
libqos-obj-y = $(libqgraph-obj-y) tests/libqos/pci.o tests/libqos/fw_cfg.o
libqos-obj-y += tests/libqos/malloc.o tests/libqos/malloc-generic.o
libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
libqos-spapr-obj-y = $(libqos-obj-y) tests/libqos/malloc-spapr.o
libqos-spapr-obj-y += tests/libqos/libqos-spapr.o
......@@ -744,7 +747,13 @@ libqos-pc-obj-y += tests/libqos/ahci.o
libqos-omap-obj-y = $(libqos-obj-y) tests/libqos/i2c-omap.o
libqos-imx-obj-y = $(libqos-obj-y) tests/libqos/i2c-imx.o
libqos-usb-obj-y = $(libqos-spapr-obj-y) $(libqos-pc-obj-y) tests/libqos/usb.o
libqos-virtio-obj-y = $(libqos-spapr-obj-y) $(libqos-pc-obj-y) tests/libqos/virtio.o tests/libqos/virtio-pci.o tests/libqos/virtio-mmio.o tests/libqos/malloc-generic.o
libqos-virtio-obj-y = $(libqos-spapr-obj-y) $(libqos-pc-obj-y) tests/libqos/virtio.o tests/libqos/virtio-pci.o tests/libqos/virtio-mmio.o
check-unit-y += tests/test-qgraph$(EXESUF)
tests/test-qgraph$(EXESUF): tests/test-qgraph.o $(libqgraph-obj-y)
check-qtest-generic-y += tests/qos-test$(EXESUF)
tests/qos-test$(EXESUF): tests/qos-test.o $(libqgraph-obj-y)
tests/qmp-test$(EXESUF): tests/qmp-test.o
tests/qmp-cmd-test$(EXESUF): tests/qmp-cmd-test.o
......
/*
* libqos driver framework
*
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@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;
};
typedef QSLIST_HEAD(, QOSGraphEdge) QOSGraphEdgeList;
/**
* 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) {
fprintf(stderr, "Error: Node %s is not an interface\n", node);
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 *machine = qos_get_machine_type(node->name);
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];
}
/* 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) {
fprintf(stderr, "node %s in %s -> %s does not exist\n",
e->dest, v->name, e->dest);
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);
return e != NULL;
}
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);
}
}
char *qos_get_machine_type(char *name)
{
while (*name != '\0' && *name != '/') {
name++;
}
if (!*name || !name[1]) {
fprintf(stderr, "Machine name has to be of the form <arch>/<machine>\n");
abort();
}
return name + 1;
}
void qos_delete_cmd_line(const char *name)
{
QOSGraphNode *node = search_node(name);
if (node) {
g_free(node->command_line);
node->command_line = NULL;
}
}
此差异已折叠。
/*
* libqos driver framework
*
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@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 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_get_machine_type(): return QEMU machine type for a machine node.
* This function requires every machine @name to be in the form
* <arch>/<machine_name>, like "arm/raspi2" or "x86_64/pc".
*
* The function will validate the format and return a pointer to
* @machine to <machine_name>. For example, when passed "x86_64/pc"
* it will return "pc".
*
* Note that this function *does not* allocate any new string.
*/
char *qos_get_machine_type(char *name);
/**
* qos_delete_cmd_line(): delete the
* command line present in node mapped with key @name.
*
* This function is called when the QMP query returns a node with
* { "abstract" : true } attribute.
*/
void qos_delete_cmd_line(const char *name);
/**
* 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
......@@ -598,6 +598,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;
}
......
/*
* libqos driver framework
*
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@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;
static void apply_to_node(const char *name, bool is_machine, bool is_abstract)
{
char *machine_name = NULL;
if (is_machine) {
const char *arch = qtest_get_arch();
machine_name = g_strconcat(arch, "/", name, NULL);
name = machine_name;
}
qos_graph_node_set_availability(name, true);
if (is_abstract) {
qos_delete_cmd_line(name);
}
g_free(machine_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 *qbool;
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);
qobj = qdict_get(minfo, "abstract");
if (qobj) {
qbool = qobject_to(QBool, qobj);
abstract = qbool_get_bool(qbool);
} else {
abstract = false;
}
apply_to_node(name, is_machine, abstract);
qobj = qdict_get(minfo, "alias");
if (qobj) {
qstr = qobject_to(QString, qobj);
name = qstring_get_str(qstr);
apply_to_node(name, is_machine, abstract);
}
}
}
/**
* 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)
{
return obj->get_driver(obj, "memory");
}
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 *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 */
path_vec[path_vec_size++] = node_name;
path_vec[path_vec_size++] = qos_get_machine_type(node_name);
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>, qtest_add_data_func adds it.
*/
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[1] = path_vec[0];
path_vec[0] = g_string_free(cmd_line, false);
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;
}
/*
* libqos driver framework
*
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@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 "libqos/qgraph.h"
#include "libqos/qgraph_internal.h"
#define MACHINE_PC "x86_64/pc"
#define MACHINE_RASPI2 "arm/raspi2"
#define I440FX "i440FX-pcihost"
#define PCIBUS_PC "pcibus-pc"
#define SDHCI "sdhci"
#define PCIBUS "pci-bus"
#define SDHCI_PCI "sdhci-pci"
#define SDHCI_MM "generic-sdhci"
#define REGISTER_TEST "register-test"
int npath;
static void *machinefunct(QTestState *qts)
{
return NULL;
}
static void *driverfunct(void *obj, QGuestAllocator *machine, void *arg)
{
return NULL;
}
static void testfunct(void *obj, void *arg, QGuestAllocator *alloc)
{
return;
}
static void check_interface(const char *interface)
{
g_assert_cmpint(qos_graph_has_machine(interface), ==, FALSE);
g_assert_nonnull(qos_graph_get_node(interface));
g_assert_cmpint(qos_graph_has_node(interface), ==, TRUE);
g_assert_cmpint(qos_graph_get_node_type(interface), ==, QNODE_INTERFACE);
qos_graph_node_set_availability(interface, TRUE);
g_assert_cmpint(qos_graph_get_node_availability(interface), ==, TRUE);
}
static void check_machine(const char *machine)
{
qos_node_create_machine(machine, machinefunct);
g_assert_nonnull(qos_graph_get_machine(machine));
g_assert_cmpint(qos_graph_has_machine(machine), ==, TRUE);
g_assert_nonnull(qos_graph_get_node(machine));
g_assert_cmpint(qos_graph_get_node_availability(machine), ==, FALSE);
qos_graph_node_set_availability(machine, TRUE);
g_assert_cmpint(qos_graph_get_node_availability(machine), ==, TRUE);
g_assert_cmpint(qos_graph_has_node(machine), ==, TRUE);
g_assert_cmpint(qos_graph_get_node_type(machine), ==, QNODE_MACHINE);
}
static void check_contains(const char *machine, const char *driver)
{
QOSGraphEdge *edge;
qos_node_contains(machine, driver, NULL);
edge = qos_graph_get_edge(machine, driver);
g_assert_nonnull(edge);
g_assert_cmpint(qos_graph_edge_get_type(edge), ==, QEDGE_CONTAINS);
g_assert_cmpint(qos_graph_has_edge(machine, driver), ==, TRUE);
}
static void check_produces(const char *machine, const char *interface)
{
QOSGraphEdge *edge;
qos_node_produces(machine, interface);
check_interface(interface);
edge = qos_graph_get_edge(machine, interface);
g_assert_nonnull(edge);
g_assert_cmpint(qos_graph_edge_get_type(edge), ==,
QEDGE_PRODUCES);
g_assert_cmpint(qos_graph_has_edge(machine, interface), ==, TRUE);
}
static void check_consumes(const char *driver, const char *interface)
{
QOSGraphEdge *edge;
qos_node_consumes(driver, interface, NULL);
check_interface(interface);
edge = qos_graph_get_edge(interface, driver);
g_assert_nonnull(edge);
g_assert_cmpint(qos_graph_edge_get_type(edge), ==, QEDGE_CONSUMED_BY);
g_assert_cmpint(qos_graph_has_edge(interface, driver), ==, TRUE);
}
static void check_driver(const char *driver)
{
qos_node_create_driver(driver, driverfunct);
g_assert_cmpint(qos_graph_has_machine(driver), ==, FALSE);
g_assert_nonnull(qos_graph_get_node(driver));
g_assert_cmpint(qos_graph_has_node(driver), ==, TRUE);
g_assert_cmpint(qos_graph_get_node_type(driver), ==, QNODE_DRIVER);
g_assert_cmpint(qos_graph_get_node_availability(driver), ==, FALSE);
qos_graph_node_set_availability(driver, TRUE);
g_assert_cmpint(qos_graph_get_node_availability(driver), ==, TRUE);
}
static void check_test(const char *test, const char *interface)
{
QOSGraphEdge *edge;
const char *full_name = g_strdup_printf("%s-tests/%s", interface, test);
qos_add_test(test, interface, testfunct, NULL);
g_assert_cmpint(qos_graph_has_machine(test), ==, FALSE);
g_assert_cmpint(qos_graph_has_machine(full_name), ==, FALSE);
g_assert_nonnull(qos_graph_get_node(full_name));
g_assert_cmpint(qos_graph_has_node(full_name), ==, TRUE);
g_assert_cmpint(qos_graph_get_node_type(full_name), ==, QNODE_TEST);
edge = qos_graph_get_edge(interface, full_name);
g_assert_nonnull(edge);
g_assert_cmpint(qos_graph_edge_get_type(edge), ==,
QEDGE_CONSUMED_BY);
g_assert_cmpint(qos_graph_has_edge(interface, full_name), ==, TRUE);
g_assert_cmpint(qos_graph_get_node_availability(full_name), ==, TRUE);
qos_graph_node_set_availability(full_name, FALSE);
g_assert_cmpint(qos_graph_get_node_availability(full_name), ==, FALSE);
}
static void count_each_test(QOSGraphNode *path, int len)
{
npath++;
}
static void check_leaf_discovered(int n)
{
npath = 0;
qos_graph_foreach_test_path(count_each_test);
g_assert_cmpint(n, ==, npath);
}
/* G_Test functions */
static void init_nop(void)
{
qos_graph_init();
qos_graph_destroy();
}
static void test_machine(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
qos_graph_destroy();
}
static void test_contains(void)
{
qos_graph_init();
check_contains(MACHINE_PC, I440FX);
g_assert_null(qos_graph_get_machine(MACHINE_PC));
g_assert_null(qos_graph_get_machine(I440FX));
g_assert_null(qos_graph_get_node(MACHINE_PC));
g_assert_null(qos_graph_get_node(I440FX));
qos_graph_destroy();
}
static void test_multiple_contains(void)
{
qos_graph_init();
check_contains(MACHINE_PC, I440FX);
check_contains(MACHINE_PC, PCIBUS_PC);
qos_graph_destroy();
}
static void test_produces(void)
{
qos_graph_init();
check_produces(MACHINE_PC, I440FX);
g_assert_null(qos_graph_get_machine(MACHINE_PC));
g_assert_null(qos_graph_get_machine(I440FX));
g_assert_null(qos_graph_get_node(MACHINE_PC));
g_assert_nonnull(qos_graph_get_node(I440FX));
qos_graph_destroy();
}
static void test_multiple_produces(void)
{
qos_graph_init();
check_produces(MACHINE_PC, I440FX);
check_produces(MACHINE_PC, PCIBUS_PC);
qos_graph_destroy();
}
static void test_consumes(void)
{
qos_graph_init();
check_consumes(I440FX, SDHCI);
g_assert_null(qos_graph_get_machine(I440FX));
g_assert_null(qos_graph_get_machine(SDHCI));
g_assert_null(qos_graph_get_node(I440FX));
g_assert_nonnull(qos_graph_get_node(SDHCI));
qos_graph_destroy();
}
static void test_multiple_consumes(void)
{
qos_graph_init();
check_consumes(I440FX, SDHCI);
check_consumes(PCIBUS_PC, SDHCI);
qos_graph_destroy();
}
static void test_driver(void)
{
qos_graph_init();
check_driver(I440FX);
qos_graph_destroy();
}
static void test_test(void)
{
qos_graph_init();
check_test(REGISTER_TEST, SDHCI);
qos_graph_destroy();
}
static void test_machine_contains_driver(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
check_driver(I440FX);
check_contains(MACHINE_PC, I440FX);
qos_graph_destroy();
}
static void test_driver_contains_driver(void)
{
qos_graph_init();
check_driver(PCIBUS_PC);
check_driver(I440FX);
check_contains(PCIBUS_PC, I440FX);
qos_graph_destroy();
}
static void test_machine_produces_interface(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
check_produces(MACHINE_PC, SDHCI);
qos_graph_destroy();
}
static void test_driver_produces_interface(void)
{
qos_graph_init();
check_driver(I440FX);
check_produces(I440FX, SDHCI);
qos_graph_destroy();
}
static void test_machine_consumes_interface(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
check_consumes(MACHINE_PC, SDHCI);
qos_graph_destroy();
}
static void test_driver_consumes_interface(void)
{
qos_graph_init();
check_driver(I440FX);
check_consumes(I440FX, SDHCI);
qos_graph_destroy();
}
static void test_test_consumes_interface(void)
{
qos_graph_init();
check_test(REGISTER_TEST, SDHCI);
qos_graph_destroy();
}
static void test_full_sample(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
check_contains(MACHINE_PC, I440FX);
check_driver(I440FX);
check_driver(PCIBUS_PC);
check_contains(I440FX, PCIBUS_PC);
check_produces(PCIBUS_PC, PCIBUS);
check_driver(SDHCI_PCI);
qos_node_consumes(SDHCI_PCI, PCIBUS, NULL);
check_produces(SDHCI_PCI, SDHCI);
check_driver(SDHCI_MM);
check_produces(SDHCI_MM, SDHCI);
qos_add_test(REGISTER_TEST, SDHCI, testfunct, NULL);
check_leaf_discovered(1);
qos_print_graph();
qos_graph_destroy();
}
static void test_full_sample_raspi(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
check_contains(MACHINE_PC, I440FX);
check_driver(I440FX);
check_driver(PCIBUS_PC);
check_contains(I440FX, PCIBUS_PC);
check_produces(PCIBUS_PC, PCIBUS);
check_driver(SDHCI_PCI);
qos_node_consumes(SDHCI_PCI, PCIBUS, NULL);
check_produces(SDHCI_PCI, SDHCI);
check_machine(MACHINE_RASPI2);
check_contains(MACHINE_RASPI2, SDHCI_MM);
check_driver(SDHCI_MM);
check_produces(SDHCI_MM, SDHCI);
qos_add_test(REGISTER_TEST, SDHCI, testfunct, NULL);
qos_print_graph();
check_leaf_discovered(2);
qos_graph_destroy();
}
static void test_cycle(void)
{
qos_graph_init();
check_machine(MACHINE_RASPI2);
check_driver("B");
check_driver("C");
check_driver("D");
check_contains(MACHINE_RASPI2, "B");
check_contains("B", "C");
check_contains("C", "D");
check_contains("D", MACHINE_RASPI2);
check_leaf_discovered(0);
qos_print_graph();
qos_graph_destroy();
}
static void test_two_test_same_interface(void)
{
qos_graph_init();
check_machine(MACHINE_RASPI2);
check_produces(MACHINE_RASPI2, "B");
qos_add_test("C", "B", testfunct, NULL);
qos_add_test("D", "B", testfunct, NULL);
check_contains(MACHINE_RASPI2, "B");
check_leaf_discovered(4);
qos_print_graph();
qos_graph_destroy();
}
static void test_test_in_path(void)
{
qos_graph_init();
check_machine(MACHINE_RASPI2);
check_produces(MACHINE_RASPI2, "B");
qos_add_test("C", "B", testfunct, NULL);
check_driver("D");
check_consumes("D", "B");
check_produces("D", "E");
qos_add_test("F", "E", testfunct, NULL);
check_leaf_discovered(2);
qos_print_graph();
qos_graph_destroy();
}
static void test_double_edge(void)
{
qos_graph_init();
check_machine(MACHINE_RASPI2);
check_produces("B", "C");
qos_node_consumes("C", "B", NULL);
qos_add_test("D", "C", testfunct, NULL);
check_contains(MACHINE_RASPI2, "B");
qos_print_graph();
qos_graph_destroy();
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qgraph/init_nop", init_nop);
g_test_add_func("/qgraph/test_machine", test_machine);
g_test_add_func("/qgraph/test_contains", test_contains);
g_test_add_func("/qgraph/test_multiple_contains", test_multiple_contains);
g_test_add_func("/qgraph/test_produces", test_produces);
g_test_add_func("/qgraph/test_multiple_produces", test_multiple_produces);
g_test_add_func("/qgraph/test_consumes", test_consumes);
g_test_add_func("/qgraph/test_multiple_consumes",
test_multiple_consumes);
g_test_add_func("/qgraph/test_driver", test_driver);
g_test_add_func("/qgraph/test_test", test_test);
g_test_add_func("/qgraph/test_machine_contains_driver",
test_machine_contains_driver);
g_test_add_func("/qgraph/test_driver_contains_driver",
test_driver_contains_driver);
g_test_add_func("/qgraph/test_machine_produces_interface",
test_machine_produces_interface);
g_test_add_func("/qgraph/test_driver_produces_interface",
test_driver_produces_interface);
g_test_add_func("/qgraph/test_machine_consumes_interface",
test_machine_consumes_interface);
g_test_add_func("/qgraph/test_driver_consumes_interface",
test_driver_consumes_interface);
g_test_add_func("/qgraph/test_test_consumes_interface",
test_test_consumes_interface);
g_test_add_func("/qgraph/test_full_sample", test_full_sample);
g_test_add_func("/qgraph/test_full_sample_raspi", test_full_sample_raspi);
g_test_add_func("/qgraph/test_cycle", test_cycle);
g_test_add_func("/qgraph/test_two_test_same_interface",
test_two_test_same_interface);
g_test_add_func("/qgraph/test_test_in_path", test_test_in_path);
g_test_add_func("/qgraph/test_double_edge", test_double_edge);
g_test_run();
return 0;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册