| .. _qgraph: | 
 |  | 
 | Qtest Driver Framework | 
 | ====================== | 
 |  | 
 | In order to test a specific driver, plain libqos tests need to | 
 | take care of booting QEMU with the right machine and devices. | 
 | This makes each test "hardcoded" for a specific configuration, reducing | 
 | the possible coverage that it can reach. | 
 |  | 
 | For example, the sdhci device is supported on both x86_64 and ARM boards, | 
 | therefore a generic sdhci test should test all machines and drivers that | 
 | support that device. | 
 | Using only libqos APIs, the test has to manually take care of | 
 | covering all the setups, and build the correct command line. | 
 |  | 
 | This also introduces backward compatibility issues: if a device/driver command | 
 | line name is changed, all tests that use that will not work | 
 | properly anymore and need to be adjusted. | 
 |  | 
 | The aim of qgraph is to create a graph of drivers, machines and tests such that | 
 | a test aimed to a certain driver does not have to care of | 
 | booting the right QEMU machine, pick the right device, build the command line | 
 | and so on. Instead, it only defines what type of device it is testing | 
 | (interface in qgraph terms) and the framework takes care of | 
 | covering all supported types of devices and machine architectures. | 
 |  | 
 | Following the above example, an interface would be ``sdhci``, | 
 | so the sdhci-test should only care of linking its qgraph node with | 
 | that interface. In this way, if the command line of a sdhci driver | 
 | is changed, only the respective qgraph driver node has to be adjusted. | 
 |  | 
 | QGraph concepts | 
 | --------------- | 
 |  | 
 | The graph is composed by nodes that represent machines, drivers, tests | 
 | and edges that define the relationships between them (``CONSUMES``, ``PRODUCES``, and | 
 | ``CONTAINS``). | 
 |  | 
 | Nodes | 
 | ~~~~~ | 
 |  | 
 | A node can be of four types: | 
 |  | 
 | - **QNODE_MACHINE**:   for example ``arm/raspi2b`` | 
 | - **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 automatically | 
 |   instantiated when a node consumes or produces it. | 
 |   An interface is simply a struct that abstracts the various drivers | 
 |   for the same type of device, and offers an API to the nodes that | 
 |   use it ("consume" relation in qgraph terms) that is implemented/backed up by the drivers that implement it ("produce" relation in qgraph terms). | 
 | - **QNODE_TEST**:      for example ``sdhci-test``. A test consumes an interface | 
 |   and tests the functions provided by it. | 
 |  | 
 | Notes for the nodes: | 
 |  | 
 | - QNODE_MACHINE: each machine struct must have a ``QGuestAllocator`` and | 
 |   implement ``get_driver()`` to return the allocator mapped to the interface | 
 |   "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 | 
 |  | 
 | Edges | 
 | ~~~~~ | 
 |  | 
 | 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 | 
 |  | 
 | Execution steps | 
 | ~~~~~~~~~~~~~~~ | 
 |  | 
 | The 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. | 
 |  | 
 | 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 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 attributes | 
 |   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 attributes 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 = { | 
 |         .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); | 
 |  | 
 | Will produce the following command line: | 
 | ``-drive id=drv0,if=none,file=null-co://, -device virtio-scsi-device,id=vs0 -device scsi-hd,bus=vs0.0,drive=drv0`` | 
 |  | 
 | Troubleshooting unavailable tests | 
 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 
 |  | 
 | If there is no path from an available machine to a test then that test will be | 
 | unavailable and won't execute. This can happen if a test or driver did not set | 
 | up its qgraph node correctly. It can also happen if the necessary machine type | 
 | or device is missing from the QEMU binary because it was compiled out or | 
 | otherwise. | 
 |  | 
 | It is possible to troubleshoot unavailable tests by running:: | 
 |  | 
 |   $ 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] | 
 |   ... | 
 |   # } | 
 |  | 
 | The ``virtio-net-tests/announce-self`` test is listed as "available" in the | 
 | "ALL QGRAPH NODES" output. This means the test will execute. We can follow the | 
 | qgraph path in the "ALL QGRAPH EDGES" output as follows: '' -> 'x86_64/pc' -> | 
 | 'i440FX-pcihost' -> 'pci-bus-pc' -> 'pci-bus' -> 'virtio-net-pci' -> | 
 | 'virtio-net'. The root of the qgraph is '' and the depth first search begins | 
 | there. | 
 |  | 
 | The ``arm/raspi2b`` machine node is listed as "UNAVAILABLE". Although it is | 
 | reachable from the root via '' -> 'arm/raspi2b' the node is unavailable because | 
 | the QEMU binary did not list it when queried by the framework. This is expected | 
 | because we used the ``qemu-system-x86_64`` binary which does not support ARM | 
 | machine types. | 
 |  | 
 | If a test is unexpectedly listed as "UNAVAILABLE", first check that the "ALL | 
 | QGRAPH EDGES" output reports edge connectivity from the root ('') to the test. | 
 | If there is no connectivity then the qgraph nodes were not set up correctly and | 
 | the driver or test code is incorrect. If there is connectivity, check the | 
 | availability of each node in the path in the "ALL QGRAPH NODES" output. The | 
 | first unavailable node in the path is the reason why the test is unavailable. | 
 | Typically this is because the QEMU binary lacks support for the necessary | 
 | machine type or device. | 
 |  | 
 | Creating a new driver and its interface | 
 | --------------------------------------- | 
 |  | 
 | Here we continue the ``sdhci`` use case, with the following scenario: | 
 |  | 
 | - ``sdhci-test`` aims to test the ``read[q,w], writeq`` functions | 
 |   offered by the ``sdhci`` drivers. | 
 | - The current ``sdhci`` device is supported by both ``x86_64/pc`` and ``ARM`` | 
 |   (in this example we focus on the ``arm-raspi2b``) machines. | 
 | - QEMU offers 2 types of drivers: ``QSDHCI_MemoryMapped`` for ``ARM`` and | 
 |   ``QSDHCI_PCI`` for ``x86_64/pc``. Both implement the | 
 |   ``read[q,w], writeq`` functions. | 
 |  | 
 | In order to implement such scenario in qgraph, the test developer needs to: | 
 |  | 
 | - Create the ``x86_64/pc`` machine node. This machine uses the | 
 |   ``pci-bus`` architecture so it ``contains`` a PCI driver, | 
 |   ``pci-bus-pc``. The actual path is | 
 |  | 
 |   ``x86_64/pc --contains--> 1440FX-pcihost --contains--> | 
 |   pci-bus-pc --produces--> pci-bus``. | 
 |  | 
 |   For the sake of this example, | 
 |   we do not focus on the PCI interface implementation. | 
 | - Create the ``sdhci-pci`` driver node, representing ``QSDHCI_PCI``. | 
 |   The driver uses the PCI bus (and its API), | 
 |   so it must ``consume`` the ``pci-bus`` generic interface (which abstracts | 
 |   all the pci drivers available) | 
 |  | 
 |   ``sdhci-pci --consumes--> pci-bus`` | 
 | - Create an ``arm/raspi2b`` machine node. This machine ``contains`` | 
 |   a ``generic-sdhci`` memory mapped ``sdhci`` driver node, representing | 
 |   ``QSDHCI_MemoryMapped``. | 
 |  | 
 |   ``arm/raspi2b --contains--> generic-sdhci`` | 
 | - Create the ``sdhci`` interface node. This interface offers the | 
 |   functions that are shared by all ``sdhci`` devices. | 
 |   The interface is produced by ``sdhci-pci`` and ``generic-sdhci``, | 
 |   the available architecture-specific drivers. | 
 |  | 
 |   ``sdhci-pci --produces--> sdhci`` | 
 |  | 
 |   ``generic-sdhci --produces--> sdhci`` | 
 | - Create the ``sdhci-test`` test node. The test ``consumes`` the | 
 |   ``sdhci`` interface, using its API. It doesn't need to look at | 
 |   the supported machines or drivers. | 
 |  | 
 |   ``sdhci-test --consumes--> sdhci`` | 
 |  | 
 | ``arm-raspi2b`` machine, simplified from | 
 | ``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`` machine, simplified from | 
 | ``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`` taken from ``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); | 
 |  | 
 | In the above example, all possible types of relations are created:: | 
 |  | 
 |   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 | 
 |  | 
 | or inverting the consumes edge in 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 | 
 |  | 
 | Adding a new test | 
 | ----------------- | 
 |  | 
 | Given the above setup, adding a new test is very simple. | 
 | ``sdhci-test``, taken from ``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); | 
 |  | 
 | Here a new test is created, consuming ``sdhci`` interface node | 
 | and creating a valid path from both machines to a test. | 
 | Final graph will be like this:: | 
 |  | 
 |   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 | 
 |  | 
 | or inverting the consumes edge in 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 | 
 |  | 
 | Assuming there the binary is | 
 | ``QTEST_QEMU_BINARY=./qemu-system-x86_64`` | 
 | a valid test path will be: | 
 | ``/x86_64/pc/1440FX-pcihost/pci-bus-pc/pci-bus/sdhci-pc/sdhci/sdhci-test`` | 
 |  | 
 | and for the binary ``QTEST_QEMU_BINARY=./qemu-system-arm``: | 
 |  | 
 | ``/arm/raspi2b/generic-sdhci/sdhci/sdhci-test`` | 
 |  | 
 | Additional examples are also in ``test-qgraph.c`` | 
 |  | 
 | Qgraph API reference | 
 | -------------------- | 
 |  | 
 | .. kernel-doc:: tests/qtest/libqos/qgraph.h |