QTest 驱动框架

普通 libqos 测试必须自行选择正确的机器和设备启动 QEMU。 因此,每个测试往往固定于某种配置,限制了覆盖范围。

例如,x86_64 和 Arm 开发板都支持 sdhci,通用测试应覆盖所有支持它的机器和驱动。 仅使用 libqos 时,需要手动处理每种组合并构造命令行。

这也会带来兼容性问题:设备或驱动的命令行名称一变,所有相关测试都需要调整。

qgraph 将驱动、机器和测试组织为图。 测试只需声明它要测试的设备类型,即接口, 框架负责选择机器和设备、构造命令行,覆盖支持的设备类型和机器架构。

上例接口为 sdhci,测试只需将自身节点连接到该接口。 如果某个驱动的命令行变化,只需修改对应驱动节点。

QGraph 概念

节点表示机器、驱动、测试等对象,边表示 CONSUMES、PRODUCES、CONTAINS 关系。

节点

有四种节点:

  • QNODE_MACHINE:例如 arm/raspi2b。

  • QNODE_DRIVER:例如 generic-sdhci。

  • QNODE_INTERFACE:例如 sdhci,抽象全部 sdhci 驱动。 不需要显式创建,某节点消费或提供它时自动实例化。 接口是一个结构体,为同类设备的不同驱动提供统一抽象, 向消费接口的节点提供 API,由提供接口的驱动实现。

  • QNODE_TEST:例如 sdhci-test,消费接口并测试其功能。

注意:

  • QNODE_MACHINE:机器结构体必须包含 QGuestAllocator, 并实现 get_driver(),通过 memory 接口返回分配器;未设置时也可返回 NULL。

  • QNODE_DRIVER:名称必须唯一。 机器及将被其他节点消费的节点名称必须匹配 QEMU 驱动名称,否则无法发现。

边

节点 X 与 Y 之间可以有以下关系:

  • X CONSUMES Y:Y 可以接入 X。

  • X PRODUCES Y:X 提供接口 Y。

  • X CONTAINS Y:Y 是 X 的组成部分。

执行步骤

框架基本流程如下:

  • 各机器、驱动和测试文件创建自己的节点及边。

  • 启动 QEMU,查询可用机器和设备;只有机器和被消费的节点与 QEMU 设备一一对应。

  • 从可用机器出发,深度优先搜索测试。

  • 找到测试后,重新遍历路径,分配各驱动,并将最终接口传给测试。

  • 执行测试。

  • 清理不再需要的对象,继续发现其他路径。

不同 QEMU 程序只提供部分驱动和机器,只有从它们可达的测试才会运行。

命令行

命令行由节点名称,以及创建边时用户提供的可选参数组成。

参数分三类:

  • in node:根据节点名自动生成,例如机器使用 -M <machine>, 设备使用 -device <device>。

  • after node:创建边时,通过 QOSGraphEdgeOptions 的 after_cmd_line 和 extra_edge_opts 添加。 extra_edge_opts 是目标节点的属性,框架自动在其前加逗号; after_cmd_line 用于增加设备而非属性,因此前面自动加空格。

  • before node:通过 QOSGraphEdgeOptions 的 before_cmd_line 添加, 放在边所指目标节点之前,适合 -fdsev、-netdev 等无法用节点表示的命令。

边上的参数总会加入,但并非所有节点名称都参与构造命令行。 被包含或被提供的节点已由 QEMU 创建,因此只使用与消费关系相关的节点。 QMP 属性为 { "abstract" : true } 的节点也不会生成命令行, 因为它们不是可直接添加的实际设备。

示例::

QOSGraphEdgeOptions opts = {
    .before_cmd_line = "-drive id=drv0,if=none,file=null-co://,"
                       "file.read-zeroes=on,format=raw",
    .after_cmd_line = "-device scsi-hd,bus=vs0.0,drive=drv0",

    opts.extra_device_opts = "id=vs0";
};

qos_node_create_driver("virtio-scsi-device",
                        virtio_scsi_device_create);
qos_node_consumes("virtio-scsi-device", "virtio-bus", &opts);

生成命令行: -drive id=drv0,if=none,file=null-co://, -device virtio-scsi-device,id=vs0 -device scsi-hd,bus=vs0.0,drive=drv0

排查不可用测试

如果从可用机器没有路径到达某测试,该测试就不可用,不会执行。 原因可能是节点配置错误,也可能是所需机器或设备没有编入 QEMU,或因其他原因不可用。

可以运行以下命令排查::

$ QTEST_QEMU_BINARY=build/qemu-system-x86_64 build/tests/qtest/qos-test --verbose
# ALL QGRAPH EDGES: {
#   src='virtio-net'
#      |-> dest='virtio-net-tests/vhost-user/multiqueue' type=2 (node=0x559142109e30)
#      |-> dest='virtio-net-tests/vhost-user/migrate' type=2 (node=0x559142109d00)
#   src='virtio-net-pci'
#      |-> dest='virtio-net' type=1 (node=0x55914210d740)
#   src='pci-bus'
#      |-> dest='virtio-net-pci' type=2 (node=0x55914210d880)
#   src='pci-bus-pc'
#      |-> dest='pci-bus' type=1 (node=0x559142103f40)
#   src='i440FX-pcihost'
#      |-> dest='pci-bus-pc' type=0 (node=0x55914210ac70)
#   src='x86_64/pc'
#      |-> dest='i440FX-pcihost' type=0 (node=0x5591421117f0)
#   src=''
#      |-> dest='x86_64/pc' type=0 (node=0x559142111600)
#      |-> dest='arm/raspi2b' type=0 (node=0x559142110740)
...
# }
# ALL QGRAPH NODES: {
#   name='virtio-net-tests/announce-self' type=3 cmd_line='(null)' [available]
#   name='arm/raspi2b' type=0 cmd_line='-M raspi2b ' [UNAVAILABLE]
...
# }

输出中,virtio-net-tests/announce-self 在 ALL QGRAPH NODES 中标为 available, 表示会运行。其 ALL QGRAPH EDGES 路径为 '' -> 'x86_64/pc' -> 'i440FX-pcihost' -> 'pci-bus-pc' -> 'pci-bus' -> 'virtio-net-pci' -> 'virtio-net'。 根节点为 '',深度优先搜索从这里开始。

arm/raspi2b 虽然通过 '' -> 'arm/raspi2b' 可达,却标为 UNAVAILABLE, 因为查询时 QEMU 没有报告该机器。 本例使用 qemu-system-x86_64,不支持 Arm 机器,因此符合预期。

如果测试意外不可用,先检查 ALL QGRAPH EDGES 中是否存在从根到测试的连接。 不存在则说明驱动或测试代码没有正确建立图; 若存在,再逐个检查路径节点在 ALL QGRAPH NODES 中的可用性。 第一个不可用节点就是原因,通常是程序缺少相应机器或设备支持。

创建驱动及其接口

继续以 sdhci 为例:

  • sdhci-test 测试 sdhci 驱动提供的 read[q,w]、writeq。

  • x86_64/pc 和 Arm 都支持 sdhci,本例 Arm 使用 arm-raspi2b。

  • QEMU 提供 Arm 的 QSDHCI_MemoryMapped 和 x86_64/pc 的 QSDHCI_PCI, 两者都实现上述操作。

测试开发者需要:

  • 创建 x86_64/pc 机器节点。它使用 PCI 总线,包含 pci-bus-pc 驱动,实际路径为:

    x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc --produces--> pci-bus。

    本例不讨论 PCI 接口的具体实现。

  • 创建代表 QSDHCI_PCI 的 sdhci-pci 驱动节点。 它使用 PCI 总线及其 API,因此消费抽象全部 PCI 驱动的通用 pci-bus 接口:

    sdhci-pci --consumes--> pci-bus

  • 创建 arm/raspi2b 机器节点,包含代表 QSDHCI_MemoryMapped 的 generic-sdhci 内存映射驱动:

    arm/raspi2b --contains--> generic-sdhci

  • 创建 sdhci 接口节点,提供全部 sdhci 设备共有的函数。 接口由平台专用驱动 sdhci-pci 和 generic-sdhci 提供:

    sdhci-pci --produces--> sdhci

    generic-sdhci --produces--> sdhci

  • 创建 sdhci-test,消费接口并使用其 API,无需了解具体机器或驱动:

    sdhci-test --consumes--> sdhci

arm-raspi2b 机器的简化实现,来自 tests/qtest/libqos/arm-raspi2-machine.c::

#include "qgraph.h"

struct QRaspi2Machine {
    QOSGraphObject obj;
    QGuestAllocator alloc;
    QSDHCI_MemoryMapped sdhci;
};

static void *raspi2_get_driver(void *object, const char *interface)
{
    QRaspi2Machine *machine = object;
    if (!g_strcmp0(interface, "memory")) {
        return &machine->alloc;
    }

    fprintf(stderr, "%s not present in arm/raspi2b\n", interface);
    g_assert_not_reached();
}

static QOSGraphObject *raspi2_get_device(void *obj,
                                            const char *device)
{
    QRaspi2Machine *machine = obj;
    if (!g_strcmp0(device, "generic-sdhci")) {
        return &machine->sdhci.obj;
    }

    fprintf(stderr, "%s not present in arm/raspi2b\n", device);
    g_assert_not_reached();
}

static void *qos_create_machine_arm_raspi2(QTestState *qts)
{
    QRaspi2Machine *machine = g_new0(QRaspi2Machine, 1);

    alloc_init(&machine->alloc, ...);

    /* Get node(s) contained inside (CONTAINS) */
    machine->obj.get_device = raspi2_get_device;

    /* Get node(s) produced (PRODUCES) */
    machine->obj.get_driver = raspi2_get_driver;

    /* free the object */
    machine->obj.destructor = raspi2_destructor;
    qos_init_sdhci_mm(&machine->sdhci, ...);
    return &machine->obj;
}

static void raspi2_register_nodes(void)
{
    /* arm/raspi2b --contains--> generic-sdhci */
    qos_node_create_machine("arm/raspi2b",
                             qos_create_machine_arm_raspi2);
    qos_node_contains("arm/raspi2b", "generic-sdhci", NULL);
}

libqos_init(raspi2_register_nodes);

x86_64/pc 的简化实现,来自 tests/qtest/libqos/x86_64_pc-machine.c::

#include "qgraph.h"

struct i440FX_pcihost {
    QOSGraphObject obj;
    QPCIBusPC pci;
};

struct QX86PCMachine {
    QOSGraphObject obj;
    QGuestAllocator alloc;
    i440FX_pcihost bridge;
};

/* i440FX_pcihost */

static QOSGraphObject *i440FX_host_get_device(void *obj,
                                            const char *device)
{
    i440FX_pcihost *host = obj;
    if (!g_strcmp0(device, "pci-bus-pc")) {
        return &host->pci.obj;
    }
    fprintf(stderr, "%s not present in i440FX-pcihost\n", device);
    g_assert_not_reached();
}

/* x86_64/pc machine */

static void *pc_get_driver(void *object, const char *interface)
{
    QX86PCMachine *machine = object;
    if (!g_strcmp0(interface, "memory")) {
        return &machine->alloc;
    }

    fprintf(stderr, "%s not present in x86_64/pc\n", interface);
    g_assert_not_reached();
}

static QOSGraphObject *pc_get_device(void *obj, const char *device)
{
    QX86PCMachine *machine = obj;
    if (!g_strcmp0(device, "i440FX-pcihost")) {
        return &machine->bridge.obj;
    }

    fprintf(stderr, "%s not present in x86_64/pc\n", device);
    g_assert_not_reached();
}

static void *qos_create_machine_pc(QTestState *qts)
{
    QX86PCMachine *machine = g_new0(QX86PCMachine, 1);

    /* Get node(s) contained inside (CONTAINS) */
    machine->obj.get_device = pc_get_device;

    /* Get node(s) produced (PRODUCES) */
    machine->obj.get_driver = pc_get_driver;

    /* free the object */
    machine->obj.destructor = pc_destructor;
    pc_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS);

    /* Get node(s) contained inside (CONTAINS) */
    machine->bridge.obj.get_device = i440FX_host_get_device;

    return &machine->obj;
}

static void pc_machine_register_nodes(void)
{
    /* x86_64/pc --contains--> 1440FX-pcihost --contains-->
     * pci-bus-pc [--produces--> pci-bus (in pci.h)] */
    qos_node_create_machine("x86_64/pc", qos_create_machine_pc);
    qos_node_contains("x86_64/pc", "i440FX-pcihost", NULL);

    /* contained drivers don't need a constructor,
     * they will be init by the parent */
    qos_node_create_driver("i440FX-pcihost", NULL);
    qos_node_contains("i440FX-pcihost", "pci-bus-pc", NULL);
}

libqos_init(pc_machine_register_nodes);

sdhci,来自 tests/qtest/libqos/sdhci.c::

/* Interface node, offers the sdhci API */
struct QSDHCI {
    uint16_t (*readw)(QSDHCI *s, uint32_t reg);
    uint64_t (*readq)(QSDHCI *s, uint32_t reg);
    void (*writeq)(QSDHCI *s, uint32_t reg, uint64_t val);
    /* other fields */
};

/* Memory Mapped implementation of QSDHCI */
struct QSDHCI_MemoryMapped {
    QOSGraphObject obj;
    QSDHCI sdhci;
    /* other driver-specific fields */
};

/* PCI implementation of QSDHCI */
struct QSDHCI_PCI {
    QOSGraphObject obj;
    QSDHCI sdhci;
    /* other driver-specific fields */
};

/* Memory mapped implementation of QSDHCI */

static void *sdhci_mm_get_driver(void *obj, const char *interface)
{
    QSDHCI_MemoryMapped *smm = obj;
    if (!g_strcmp0(interface, "sdhci")) {
        return &smm->sdhci;
    }
    fprintf(stderr, "%s not present in generic-sdhci\n", interface);
    g_assert_not_reached();
}

void qos_init_sdhci_mm(QSDHCI_MemoryMapped *sdhci, QTestState *qts,
                    uint32_t addr, QSDHCIProperties *common)
{
    /* Get node contained inside (CONTAINS) */
    sdhci->obj.get_driver = sdhci_mm_get_driver;

    /* SDHCI interface API */
    sdhci->sdhci.readw = sdhci_mm_readw;
    sdhci->sdhci.readq = sdhci_mm_readq;
    sdhci->sdhci.writeq = sdhci_mm_writeq;
    sdhci->qts = qts;
}

/* PCI implementation of QSDHCI */

static void *sdhci_pci_get_driver(void *object,
                                  const char *interface)
{
    QSDHCI_PCI *spci = object;
    if (!g_strcmp0(interface, "sdhci")) {
        return &spci->sdhci;
    }

    fprintf(stderr, "%s not present in sdhci-pci\n", interface);
    g_assert_not_reached();
}

static void *sdhci_pci_create(void *pci_bus,
                              QGuestAllocator *alloc,
                              void *addr)
{
    QSDHCI_PCI *spci = g_new0(QSDHCI_PCI, 1);
    QPCIBus *bus = pci_bus;
    uint64_t barsize;

    qpci_device_init(&spci->dev, bus, addr);

    /* SDHCI interface API */
    spci->sdhci.readw = sdhci_pci_readw;
    spci->sdhci.readq = sdhci_pci_readq;
    spci->sdhci.writeq = sdhci_pci_writeq;

    /* Get node(s) produced (PRODUCES) */
    spci->obj.get_driver = sdhci_pci_get_driver;

    spci->obj.start_hw = sdhci_pci_start_hw;
    spci->obj.destructor = sdhci_destructor;
    return &spci->obj;
}

static void qsdhci_register_nodes(void)
{
    QOSGraphEdgeOptions opts = {
        .extra_device_opts = "addr=04.0",
    };

    /* generic-sdhci */
    /* generic-sdhci --produces--> sdhci */
    qos_node_create_driver("generic-sdhci", NULL);
    qos_node_produces("generic-sdhci", "sdhci");

    /* sdhci-pci */
    /* sdhci-pci --produces--> sdhci
     * sdhci-pci --consumes--> pci-bus */
    qos_node_create_driver("sdhci-pci", sdhci_pci_create);
    qos_node_produces("sdhci-pci", "sdhci");
    qos_node_consumes("sdhci-pci", "pci-bus", &opts);
}

libqos_init(qsdhci_register_nodes);

上述示例创建了全部类型的关系::

x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
                                                          |
             sdhci-pci --consumes--> pci-bus <--produces--+
                |
                +--produces--+
                             |
                             v
                           sdhci
                             ^
                             |
                             +--produces-- +
                                           |
             arm/raspi2b --contains--> generic-sdhci

将 consumes 反向表示为 consumed_by 后::

x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
                                                          |
          sdhci-pci <--consumed by-- pci-bus <--produces--+
              |
              +--produces--+
                           |
                           v
                          sdhci
                           ^
                           |
                           +--produces-- +
                                         |
          arm/raspi2b --contains--> generic-sdhci

添加测试

有了上述结构,添加测试很简单。 sdhci-test 来自 tests/qtest/sdhci-test.c::

static void check_capab_sdma(QSDHCI *s, bool supported)
{
    uint64_t capab, capab_sdma;

    capab = s->readq(s, SDHC_CAPAB);
    capab_sdma = FIELD_EX64(capab, SDHC_CAPAB, SDMA);
    g_assert_cmpuint(capab_sdma, ==, supported);
}

static void test_registers(void *obj, void *data,
                            QGuestAllocator *alloc)
{
    QSDHCI *s = obj;

    /* example test */
    check_capab_sdma(s, s->props.capab.sdma);
}

static void register_sdhci_test(void)
{
    /* sdhci-test --consumes--> sdhci */
    qos_add_test("registers", "sdhci", test_registers, NULL);
}

libqos_init(register_sdhci_test);

它消费 sdhci 接口,使两种机器都存在通向测试的有效路径。最终图如下::

x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
                                                          |
             sdhci-pci --consumes--> pci-bus <--produces--+
                |
                +--produces--+
                             |
                             v
                           sdhci <--consumes-- sdhci-test
                             ^
                             |
                             +--produces-- +
                                           |
             arm/raspi2b --contains--> generic-sdhci

将 consumes 反向表示为 consumed_by 后::

x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
                                                          |
          sdhci-pci <--consumed by-- pci-bus <--produces--+
              |
              +--produces--+
                           |
                           v
                          sdhci --consumed by--> sdhci-test
                           ^
                           |
                           +--produces-- +
                                         |
          arm/raspi2b --contains--> generic-sdhci

使用 QTEST_QEMU_BINARY=./qemu-system-x86_64 时,有效路径为: /x86_64/pc/1440FX-pcihost/pci-bus-pc/pci-bus/sdhci-pc/sdhci/sdhci-test。

使用 QTEST_QEMU_BINARY=./qemu-system-arm 时为:

/arm/raspi2b/generic-sdhci/sdhci/sdhci-test

更多示例见 test-qgraph.c。

Qgraph API 参考

struct QOSGraphEdgeOptions

Edge options to be passed to the contains/consumes *_args function.

Definition:

struct QOSGraphEdgeOptions {
    void *arg;
    uint32_t size_arg;
    const char *extra_device_opts;
    const char *before_cmd_line;
    const char *after_cmd_line;
    const char *edge_name;
};

Members

arg

optional arg that will be used by dest edge

size_arg

arg size that will be used by dest edge

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.

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"

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

edge_name

optional edge to differentiate multiple devices with same node name

struct QOSGraphTestOptions

Test options to be passed to the test functions.

Definition:

struct QOSGraphTestOptions {
    QOSGraphEdgeOptions edge;
    void *arg;
    QOSBeforeTest before;
    bool subprocess;
};

Members

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.

arg

if before is non-NULL, pass arg there. Otherwise pass it to the test function.

before

executed before the test. Used to add additional parameters to the command line and modify the argument to the test function.

subprocess

run the test in a subprocess.

struct QOSGraphObject

Each driver, test or machine of this framework will have a QOSGraphObject as first field.

Definition:

struct QOSGraphObject {
    QOSGetDriver get_driver;
    QOSGetDevice get_device;
    QOSStartFunct start_hw;
    QOSDestructorFunc destructor;
    GDestroyNotify free;
};

Members

get_driver

see get_device

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 constructor is provided, no destructor will be called.

free

free the memory associated to the QOSGraphObject and its contained children

Description

This set of functions offered by QOSGraphObject are executed in different stages of the framework:

void qos_graph_init(void)

initialize the framework, creates two hash tables: one for the nodes and another for the edges.

Parameters

void

no arguments

void qos_graph_destroy(void)

deallocates all the hash tables, freeing all nodes and edges.

Parameters

void

no arguments

void qos_node_destroy(void *key)

removes and frees a node from the nodes hash table.

Parameters

void *key

Name of the node

void qos_edge_destroy(void *key)

removes and frees an edge from the edges hash table.

Parameters

void *key

Name of the node

void qos_add_test(const char *name, const char *interface, QOSTestFunc test_func, QOSGraphTestOptions *opts)

adds a test node name to the nodes hash table.

Parameters

const char *name

Name of the test

const char *interface

Name of the interface node it consumes

QOSTestFunc test_func

Actual test to perform

QOSGraphTestOptions *opts

Facultative options (see QOSGraphTestOptions)

Description

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 QOSGraphTestOptions).

For tests, opts->edge.arg and size_arg represent the arg to pass to test_func

void qos_node_create_machine(const char *name, QOSCreateMachineFunc function)

creates the machine name and adds it to the node hash table.

Parameters

const char *name

Name of the machine

QOSCreateMachineFunc function

Machine constructor

Description

This node will be of type QNODE_MACHINE and have function as constructor

void qos_node_create_machine_args(const char *name, QOSCreateMachineFunc function, const char *opts)

same as qos_node_create_machine, but with the possibility to add an optional ", opts" after -M machine command line.

Parameters

const char *name

Name of the machine

QOSCreateMachineFunc function

Machine constructor

const char *opts

Optional additional command line

void qos_node_create_driver(const char *name, QOSCreateDriverFunc function)

creates the driver name and adds it to the node hash table.

Parameters

const char *name

Name of the driver

QOSCreateDriverFunc function

Driver constructor

Description

This node will be of type QNODE_DRIVER and have function as constructor

void qos_node_create_driver_named(const char *name, const char *qemu_name, QOSCreateDriverFunc function)

behaves as qos_node_create_driver() with the extension of allowing to specify a different node name vs. associated QEMU device name.

Parameters

const char *name

Custom, unique name of the node to be created

const char *qemu_name

Actual (official) QEMU driver name the node shall be associated with

QOSCreateDriverFunc function

Driver constructor

Description

Use this function instead of qos_node_create_driver() if you need to create several instances of the same QEMU device. You are free to choose a custom node name, however the chosen node name must always be unique.

void qos_node_contains(const char *container, const char *contained, QOSGraphEdgeOptions *opts, ...)

creates one or more edges of type QEDGE_CONTAINS and adds them to the edge list mapped to container in the edge hash table.

Parameters

const char *container

Source node that "contains"

const char *contained

Destination node that "is contained"

QOSGraphEdgeOptions *opts

Facultative options (see QOSGraphEdgeOptions)

...

variable arguments

Description

The edges will have container as source and contained as destination.

If opts is NULL, a single edge will be added with no options. If opts is non-NULL, the arguments after contained represent a NULL-terminated list of QOSGraphEdgeOptions structs, and an edge will be added for each of them.

This function can be useful when there are multiple devices with the same node name contained in a machine/other node

For example, if arm/raspi2b contains 2 generic-sdhci devices, the right commands will be:

qos_node_create_machine("arm/raspi2b");
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/raspi2b", "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_produces(const char *producer, const char *interface)

creates an edge of type QEDGE_PRODUCES and adds it to the edge list mapped to producer in the edge hash table.

Parameters

const char *producer

Source node that "produces"

const char *interface

Interface node that "is produced"

Description

This edge will have producer as source and interface as destination.

void qos_node_consumes(const char *consumer, const char *interface, QOSGraphEdgeOptions *opts)

creates an edge of type QEDGE_CONSUMED_BY and adds it to the edge list mapped to interface in the edge hash table.

Parameters

const char *consumer

Node that "consumes"

const char *interface

Interface node that "is consumed by"

QOSGraphEdgeOptions *opts

Facultative options (see QOSGraphEdgeOptions)

Description

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_invalidate_command_line(void)

invalidates current command line, so that qgraph framework cannot try to cache the current command line and forces QEMU to restart.

Parameters

void

no arguments

const char *qos_get_current_command_line(void)

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.

Parameters

void

no arguments

void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc)

Parameters

QTestState *qts

The QTestState that will be referred to by the machine object.

QGuestAllocator **p_alloc

Where to store the allocator for the machine object, or NULL.

Description

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_object_destroy(QOSGraphObject *obj)

calls the destructor for obj

Parameters

QOSGraphObject *obj

A QOSGraphObject to destroy

void qos_object_queue_destroy(QOSGraphObject *obj)

queue the destructor for obj so that it is called at the end of the test

Parameters

QOSGraphObject *obj

A QOSGraphObject to destroy

void qos_object_start_hw(QOSGraphObject *obj)

calls the start_hw function for obj

Parameters

QOSGraphObject *obj

A QOSGraphObject containing the start_hw function

QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts)

instantiate a new machine node

Parameters

QOSGraphNode *node

Machine node to be instantiated

QTestState *qts

A QTestState that will be referred to by the machine object.

Description

Returns a machine object.

QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent, QGuestAllocator *alloc, void *arg)

instantiate a new driver node

Parameters

QOSGraphNode *node

A driver node to be instantiated

QOSGraphObject *parent

A QOSGraphObject to be consumed by the new driver node

QGuestAllocator *alloc

An allocator to be used by the new driver node.

void *arg

The argument for the consumed-by edge to node.

Description

Calls the constructor for the driver object.

void qos_dump_graph(void)

prints all currently existing nodes and edges to stdout. Just for debugging purposes.

Parameters

void

no arguments

Description

All qtests add themselves to the overall qos graph by calling qgraph functions that add device nodes and edges between the individual graph nodes for tests. As the actual graph is assmbled at runtime by the qos subsystem, it is sometimes not obvious how the overall graph looks like. E.g. when writing new tests it may happen that those new tests are simply ignored by the qtest framework.

This function allows to identify problems in the created qgraph. Keep in mind: only tests with a path down from the actual test case node (leaf) up to the graph's root node are actually executed by the qtest framework. And the qtest framework uses QMP to automatically check which QEMU drivers are actually currently available, and accordingly qos marks certain paths as 'unavailable' in such cases (e.g. when QEMU was compiled without support for a certain feature).