1
0
Fork 0

Adding upstream version 1:10.0.2+ds.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
Daniel Baumann 2025-06-22 14:27:05 +02:00
parent bf2768bd0f
commit ea34ddeea6
Signed by: daniel.baumann
GPG key ID: BCC918A2ABD66424
37998 changed files with 9510514 additions and 0 deletions

View file

@ -0,0 +1,155 @@
==================================
ACPI/SMBIOS testing using biosbits
==================================
************
Introduction
************
Biosbits is a software written by Josh Triplett that can be downloaded
from https://biosbits.org/. The github codebase can be found
`here <https://github.com/biosbits/bits/tree/master>`__. It is a software that
executes the bios components such as acpi and smbios tables directly through
acpica bios interpreter (a freely available C based library written by Intel,
downloadable from https://acpica.org/ and is included with biosbits) without an
operating system getting involved in between. Bios-bits has python integration
with grub so actual routines that executes bios components can be written in
python instead of bash-ish (grub's native scripting language).
There are several advantages to directly testing the bios in a real physical
machine or in a VM as opposed to indirectly discovering bios issues through the
operating system (the OS). Operating systems tend to bypass bios problems and
hide them from the end user. We have more control of what we wanted to test and
how by being as close to the bios on a running system as possible without a
complicated software component such as an operating system coming in between.
Another issue is that we cannot exercise bios components such as ACPI and
SMBIOS without being in the highest hardware privilege level, ring 0 for
example in case of x86. Since the OS executes from ring 0 whereas normal user
land software resides in unprivileged ring 3, operating system must be modified
in order to write our test routines that exercise and test the bios. This is
not possible in all cases. Lastly, test frameworks and routines are preferably
written using a high level scripting language such as python. OSes and
OS modules are generally written using low level languages such as C and
low level assembly machine language. Writing test routines in a low level
language makes things more cumbersome. These and other reasons makes using
bios-bits very attractive for testing bioses. More details on the inspiration
for developing biosbits and its real life uses were presented `at Plumbers
in 2011 <Plumbers_>`__ and `at Linux.conf.au in 2012 <Linux.conf.au_>`__.
For QEMU, we maintain a fork of bios bits in `gitlab`_, along with all
the dependent submodules. This fork contains numerous fixes, a newer
acpica and changes specific to running these functional QEMU tests using
bits. The author of this document is the current maintainer of the QEMU
fork of bios bits repository. For more information, please see `the
author's FOSDEM presentation <FOSDEM_>`__ on this bios-bits based test framework.
.. _Plumbers: https://blog.linuxplumbersconf.org/2011/ocw/system/presentations/867/original/bits.pdf
.. _Linux.conf.au: https://www.youtube.com/watch?v=36QIepyUuhg
.. _gitlab: https://gitlab.com/qemu-project/biosbits-bits
.. _FOSDEM: https://fosdem.org/2024/schedule/event/fosdem-2024-2262-exercising-qemu-generated-acpi-smbios-tables-using-biosbits-from-within-a-guest-vm-/
*********************************
Description of the test framework
*********************************
Under the directory ``tests/functional/``, ``test_acpi_bits.py`` is a QEMU
functional test that drives all this.
A brief description of the various test files follows.
Under ``tests/functional/`` as the root we have:
::
├── acpi-bits
│ ├── bits-config
│ │ └── bits-cfg.txt
│ ├── bits-tests
│ ├── smbios.py2
│ ├── testacpi.py2
│ └── testcpuid.py2
├── test_acpi_bits.py
* ``tests/functional``:
``test_acpi_bits.py``:
This is the main python functional test script that generates a
biosbits iso. It then spawns a QEMU VM with it, collects the log and reports
test failures. This is the script one would be interested in if they wanted
to add or change some component of the log parsing, add a new command line
to alter how QEMU is spawned etc. Test writers typically would not need to
modify this script unless they wanted to enhance or change the log parsing
for their tests. In order to enable debugging, you can set **V=1**
environment variable. This enables verbose mode for the test and also dumps
the entire log from bios bits and more information in case failure happens.
You can also set **BITS_DEBUG=1** to turn on debug mode. It will enable
verbose logs and also retain the temporary work directory the test used for
you to inspect and run the specific commands manually.
In order to run this test, please perform the following steps from the QEMU
build directory (assuming that the sources are in ".."):
::
$ export PYTHONPATH=../python:../tests/functional
$ export QEMU_TEST_QEMU_BINARY=$PWD/qemu-system-x86_64
$ python3 ../tests/functional/test_acpi_bits.py
The above will run all acpi-bits functional tests (producing output in
tap format).
You can inspect the log files in tests/functional/x86_64/test_acpi_bits.*/
for more information about the run or in order to diagnoze issues.
If you pass V=1 in the environment, more diagnostic logs will be put into
the test log.
* ``tests/functional/acpi-bits/bits-config``:
This location contains biosbits configuration files that determine how the
software runs the tests.
``bits-config.txt``:
This is the biosbits config file that determines what tests
or actions are performed by bits. The description of the config options are
provided in the file itself.
* ``tests/functional/acpi-bits/bits-tests``:
This directory contains biosbits python based tests that are run from within
the biosbits environment in the spawned VM. New additions of test cases can
be made in the appropriate test file. For example, new acpi tests can go
into testacpi.py2 and one would call testsuite.add_test() to register the new
test so that it gets executed as a part of the ACPI tests.
It might be occasionally necessary to disable some subtests or add a new
test that belongs to a test suite not already present in this directory. To
do this, please clone the bits source from
https://gitlab.com/qemu-project/biosbits-bits/-/tree/qemu-bits.
Note that this is the "qemu-bits" branch and not the "bits" branch of the
repository. "qemu-bits" is the branch where we have made all the QEMU
specific enhancements and we must use the source from this branch only.
Copy the test suite/script that needs modification (addition of new tests
or disabling them) from python directory into this directory. For
example, in order to change cpuid related tests, copy the following
file into this directory and rename it with .py2 extension:
https://gitlab.com/qemu-project/biosbits-bits/-/blob/qemu-bits/python/testcpuid.py
Then make your additions and changes here. Therefore, the steps are:
(a) Copy unmodified test script to this directory from bits source.
(b) Add a SPDX license header.
(c) Perform modifications to the test.
Commits (a), (b) and (c) preferably should go under separate commits so that
the original test script and the changes we have made are separated and
clear. (a) and (b) can sometimes be combined into a single step.
The test framework will then use your modified test script to run the test.
No further changes would be needed. Please check the logs to make sure that
appropriate changes have taken effect.
The tests have an extension .py2 in order to indicate that:
(a) They are python2.7 based scripts and not python 3 scripts.
(b) They are run from within the bios bits VM and is not subjected to QEMU
build/test python script maintenance and dependency resolutions.
(c) They need not be loaded by the test framework by accident when running
tests.
Author: Ani Sinha <anisinha@redhat.com>

View file

@ -0,0 +1,581 @@
.. _checkavocado-ref:
Integration testing with Avocado
================================
The ``tests/avocado`` directory hosts integration tests. They're usually
higher level tests, and may interact with external resources and with
various guest operating systems.
These tests are written using the Avocado Testing Framework (which must be
installed separately) in conjunction with a the ``avocado_qemu.QemuSystemTest``
class, implemented at ``tests/avocado/avocado_qemu``.
Tests based on ``avocado_qemu.QemuSystemTest`` can easily:
* Customize the command line arguments given to the convenience
``self.vm`` attribute (a QEMUMachine instance)
* Interact with the QEMU monitor, send QMP commands and check
their results
* Interact with the guest OS, using the convenience console device
(which may be useful to assert the effectiveness and correctness of
command line arguments or QMP commands)
* Interact with external data files that accompany the test itself
(see ``self.get_data()``)
* Download (and cache) remote data files, such as firmware and kernel
images
* Have access to a library of guest OS images (by means of the
``avocado.utils.vmimage`` library)
* Make use of various other test related utilities available at the
test class itself and at the utility library:
- http://avocado-framework.readthedocs.io/en/latest/api/test/avocado.html#avocado.Test
- http://avocado-framework.readthedocs.io/en/latest/api/utils/avocado.utils.html
Running tests
-------------
You can run the avocado tests simply by executing:
.. code::
make check-avocado
This involves the automatic installation, from PyPI, of all the
necessary avocado-framework dependencies into the QEMU venv within the
build tree (at ``./pyvenv``). Test results are also saved within the
build tree (at ``tests/results``).
Note: the build environment must be using a Python 3 stack, and have
the ``venv`` and ``pip`` packages installed. If necessary, make sure
``configure`` is called with ``--python=`` and that those modules are
available. On Debian and Ubuntu based systems, depending on the
specific version, they may be on packages named ``python3-venv`` and
``python3-pip``.
It is also possible to run tests based on tags using the
``make check-avocado`` command and the ``AVOCADO_TAGS`` environment
variable:
.. code::
make check-avocado AVOCADO_TAGS=quick
Note that tags separated with commas have an AND behavior, while tags
separated by spaces have an OR behavior. For more information on Avocado
tags, see:
https://avocado-framework.readthedocs.io/en/latest/guides/user/chapters/tags.html
To run a single test file, a couple of them, or a test within a file
using the ``make check-avocado`` command, set the ``AVOCADO_TESTS``
environment variable with the test files or test names. To run all
tests from a single file, use:
.. code::
make check-avocado AVOCADO_TESTS=$FILEPATH
The same is valid to run tests from multiple test files:
.. code::
make check-avocado AVOCADO_TESTS='$FILEPATH1 $FILEPATH2'
To run a single test within a file, use:
.. code::
make check-avocado AVOCADO_TESTS=$FILEPATH:$TESTCLASS.$TESTNAME
The same is valid to run single tests from multiple test files:
.. code::
make check-avocado AVOCADO_TESTS='$FILEPATH1:$TESTCLASS1.$TESTNAME1 $FILEPATH2:$TESTCLASS2.$TESTNAME2'
The scripts installed inside the virtual environment may be used
without an "activation". For instance, the Avocado test runner
may be invoked by running:
.. code::
pyvenv/bin/avocado run $OPTION1 $OPTION2 tests/avocado/
Note that if ``make check-avocado`` was not executed before, it is
possible to create the Python virtual environment with the dependencies
needed running:
.. code::
make check-venv
It is also possible to run tests from a single file or a single test within
a test file. To run tests from a single file within the build tree, use:
.. code::
pyvenv/bin/avocado run tests/avocado/$TESTFILE
To run a single test within a test file, use:
.. code::
pyvenv/bin/avocado run tests/avocado/$TESTFILE:$TESTCLASS.$TESTNAME
Valid test names are visible in the output from any previous execution
of Avocado or ``make check-avocado``, and can also be queried using:
.. code::
pyvenv/bin/avocado list tests/avocado
Manual Installation
-------------------
To manually install Avocado and its dependencies, run:
.. code::
pip install --user avocado-framework
Alternatively, follow the instructions on this link:
https://avocado-framework.readthedocs.io/en/latest/guides/user/chapters/installing.html
Overview
--------
The ``tests/avocado/avocado_qemu`` directory provides the
``avocado_qemu`` Python module, containing the ``avocado_qemu.QemuSystemTest``
class. Here's a simple usage example:
.. code::
from avocado_qemu import QemuSystemTest
class Version(QemuSystemTest):
"""
:avocado: tags=quick
"""
def test_qmp_human_info_version(self):
self.vm.launch()
res = self.vm.cmd('human-monitor-command',
command_line='info version')
self.assertRegex(res, r'^(\d+\.\d+\.\d)')
To execute your test, run:
.. code::
avocado run version.py
Tests may be classified according to a convention by using docstring
directives such as ``:avocado: tags=TAG1,TAG2``. To run all tests
in the current directory, tagged as "quick", run:
.. code::
avocado run -t quick .
The ``avocado_qemu.QemuSystemTest`` base test class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``avocado_qemu.QemuSystemTest`` class has a number of characteristics
that are worth being mentioned right away.
First of all, it attempts to give each test a ready to use QEMUMachine
instance, available at ``self.vm``. Because many tests will tweak the
QEMU command line, launching the QEMUMachine (by using ``self.vm.launch()``)
is left to the test writer.
The base test class has also support for tests with more than one
QEMUMachine. The way to get machines is through the ``self.get_vm()``
method which will return a QEMUMachine instance. The ``self.get_vm()``
method accepts arguments that will be passed to the QEMUMachine creation
and also an optional ``name`` attribute so you can identify a specific
machine and get it more than once through the tests methods. A simple
and hypothetical example follows:
.. code::
from avocado_qemu import QemuSystemTest
class MultipleMachines(QemuSystemTest):
def test_multiple_machines(self):
first_machine = self.get_vm()
second_machine = self.get_vm()
self.get_vm(name='third_machine').launch()
first_machine.launch()
second_machine.launch()
first_res = first_machine.cmd(
'human-monitor-command',
command_line='info version')
second_res = second_machine.cmd(
'human-monitor-command',
command_line='info version')
third_res = self.get_vm(name='third_machine').cmd(
'human-monitor-command',
command_line='info version')
self.assertEqual(first_res, second_res, third_res)
At test "tear down", ``avocado_qemu.QemuSystemTest`` handles all the
QEMUMachines shutdown.
The ``avocado_qemu.LinuxTest`` base test class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``avocado_qemu.LinuxTest`` is further specialization of the
``avocado_qemu.QemuSystemTest`` class, so it contains all the characteristics
of the later plus some extra features.
First of all, this base class is intended for tests that need to
interact with a fully booted and operational Linux guest. At this
time, it uses a Fedora 31 guest image. The most basic example looks
like this:
.. code::
from avocado_qemu import LinuxTest
class SomeTest(LinuxTest):
def test(self):
self.launch_and_wait()
self.ssh_command('some_command_to_be_run_in_the_guest')
Please refer to tests that use ``avocado_qemu.LinuxTest`` under
``tests/avocado`` for more examples.
QEMUMachine
-----------
The QEMUMachine API is already widely used in the Python iotests,
device-crash-test and other Python scripts. It's a wrapper around the
execution of a QEMU binary, giving its users:
* the ability to set command line arguments to be given to the QEMU
binary
* a ready to use QMP connection and interface, which can be used to
send commands and inspect its results, as well as asynchronous
events
* convenience methods to set commonly used command line arguments in
a more succinct and intuitive way
QEMU binary selection
^^^^^^^^^^^^^^^^^^^^^
The QEMU binary used for the ``self.vm`` QEMUMachine instance will
primarily depend on the value of the ``qemu_bin`` parameter. If it's
not explicitly set, its default value will be the result of a dynamic
probe in the same source tree. A suitable binary will be one that
targets the architecture matching host machine.
Based on this description, test writers will usually rely on one of
the following approaches:
1) Set ``qemu_bin``, and use the given binary
2) Do not set ``qemu_bin``, and use a QEMU binary named like
"qemu-system-${arch}", either in the current
working directory, or in the current source tree.
The resulting ``qemu_bin`` value will be preserved in the
``avocado_qemu.QemuSystemTest`` as an attribute with the same name.
Attribute reference
-------------------
Test
^^^^
Besides the attributes and methods that are part of the base
``avocado.Test`` class, the following attributes are available on any
``avocado_qemu.QemuSystemTest`` instance.
vm
""
A QEMUMachine instance, initially configured according to the given
``qemu_bin`` parameter.
arch
""""
The architecture can be used on different levels of the stack, e.g. by
the framework or by the test itself. At the framework level, it will
currently influence the selection of a QEMU binary (when one is not
explicitly given).
Tests are also free to use this attribute value, for their own needs.
A test may, for instance, use the same value when selecting the
architecture of a kernel or disk image to boot a VM with.
The ``arch`` attribute will be set to the test parameter of the same
name. If one is not given explicitly, it will either be set to
``None``, or, if the test is tagged with one (and only one)
``:avocado: tags=arch:VALUE`` tag, it will be set to ``VALUE``.
cpu
"""
The cpu model that will be set to all QEMUMachine instances created
by the test.
The ``cpu`` attribute will be set to the test parameter of the same
name. If one is not given explicitly, it will either be set to
``None ``, or, if the test is tagged with one (and only one)
``:avocado: tags=cpu:VALUE`` tag, it will be set to ``VALUE``.
machine
"""""""
The machine type that will be set to all QEMUMachine instances created
by the test.
The ``machine`` attribute will be set to the test parameter of the same
name. If one is not given explicitly, it will either be set to
``None``, or, if the test is tagged with one (and only one)
``:avocado: tags=machine:VALUE`` tag, it will be set to ``VALUE``.
qemu_bin
""""""""
The preserved value of the ``qemu_bin`` parameter or the result of the
dynamic probe for a QEMU binary in the current working directory or
source tree.
LinuxTest
^^^^^^^^^
Besides the attributes present on the ``avocado_qemu.QemuSystemTest`` base
class, the ``avocado_qemu.LinuxTest`` adds the following attributes:
distro
""""""
The name of the Linux distribution used as the guest image for the
test. The name should match the **Provider** column on the list
of images supported by the avocado.utils.vmimage library:
https://avocado-framework.readthedocs.io/en/latest/guides/writer/libs/vmimage.html#supported-images
distro_version
""""""""""""""
The version of the Linux distribution as the guest image for the
test. The name should match the **Version** column on the list
of images supported by the avocado.utils.vmimage library:
https://avocado-framework.readthedocs.io/en/latest/guides/writer/libs/vmimage.html#supported-images
distro_checksum
"""""""""""""""
The sha256 hash of the guest image file used for the test.
If this value is not set in the code or by a test parameter (with the
same name), no validation on the integrity of the image will be
performed.
Parameter reference
-------------------
To understand how Avocado parameters are accessed by tests, and how
they can be passed to tests, please refer to::
https://avocado-framework.readthedocs.io/en/latest/guides/writer/chapters/writing.html#accessing-test-parameters
Parameter values can be easily seen in the log files, and will look
like the following:
.. code::
PARAMS (key=qemu_bin, path=*, default=./qemu-system-x86_64) => './qemu-system-x86_64
Test
^^^^
arch
""""
The architecture that will influence the selection of a QEMU binary
(when one is not explicitly given).
Tests are also free to use this parameter value, for their own needs.
A test may, for instance, use the same value when selecting the
architecture of a kernel or disk image to boot a VM with.
This parameter has a direct relation with the ``arch`` attribute. If
not given, it will default to None.
cpu
"""
The cpu model that will be set to all QEMUMachine instances created
by the test.
machine
"""""""
The machine type that will be set to all QEMUMachine instances created
by the test.
qemu_bin
""""""""
The exact QEMU binary to be used on QEMUMachine.
LinuxTest
^^^^^^^^^
Besides the parameters present on the ``avocado_qemu.QemuSystemTest`` base
class, the ``avocado_qemu.LinuxTest`` adds the following parameters:
distro
""""""
The name of the Linux distribution used as the guest image for the
test. The name should match the **Provider** column on the list
of images supported by the avocado.utils.vmimage library:
https://avocado-framework.readthedocs.io/en/latest/guides/writer/libs/vmimage.html#supported-images
distro_version
""""""""""""""
The version of the Linux distribution as the guest image for the
test. The name should match the **Version** column on the list
of images supported by the avocado.utils.vmimage library:
https://avocado-framework.readthedocs.io/en/latest/guides/writer/libs/vmimage.html#supported-images
distro_checksum
"""""""""""""""
The sha256 hash of the guest image file used for the test.
If this value is not set in the code or by this parameter no
validation on the integrity of the image will be performed.
Skipping tests
--------------
The Avocado framework provides Python decorators which allow for easily skip
tests running under certain conditions. For example, on the lack of a binary
on the test system or when the running environment is a CI system. For further
information about those decorators, please refer to::
https://avocado-framework.readthedocs.io/en/latest/guides/writer/chapters/writing.html#skipping-tests
While the conditions for skipping tests are often specifics of each one, there
are recurring scenarios identified by the QEMU developers and the use of
environment variables became a kind of standard way to enable/disable tests.
Here is a list of the most used variables:
AVOCADO_ALLOW_LARGE_STORAGE
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tests which are going to fetch or produce assets considered *large* are not
going to run unless that ``AVOCADO_ALLOW_LARGE_STORAGE=1`` is exported on
the environment.
The definition of *large* is a bit arbitrary here, but it usually means an
asset which occupies at least 1GB of size on disk when uncompressed.
SPEED
^^^^^
Tests which have a long runtime will not be run unless ``SPEED=slow`` is
exported on the environment.
The definition of *long* is a bit arbitrary here, and it depends on the
usefulness of the test too. A unique test is worth spending more time on,
small variations on existing tests perhaps less so. As a rough guide,
a test or set of similar tests which take more than 100 seconds to
complete.
AVOCADO_ALLOW_UNTRUSTED_CODE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are tests which will boot a kernel image or firmware that can be
considered not safe to run on the developer's workstation, thus they are
skipped by default. The definition of *not safe* is also arbitrary but
usually it means a blob which either its source or build process aren't
public available.
You should export ``AVOCADO_ALLOW_UNTRUSTED_CODE=1`` on the environment in
order to allow tests which make use of those kind of assets.
AVOCADO_TIMEOUT_EXPECTED
^^^^^^^^^^^^^^^^^^^^^^^^
The Avocado framework has a timeout mechanism which interrupts tests to avoid the
test suite of getting stuck. The timeout value can be set via test parameter or
property defined in the test class, for further details::
https://avocado-framework.readthedocs.io/en/latest/guides/writer/chapters/writing.html#setting-a-test-timeout
Even though the timeout can be set by the test developer, there are some tests
that may not have a well-defined limit of time to finish under certain
conditions. For example, tests that take longer to execute when QEMU is
compiled with debug flags. Therefore, the ``AVOCADO_TIMEOUT_EXPECTED`` variable
has been used to determine whether those tests should run or not.
QEMU_TEST_FLAKY_TESTS
^^^^^^^^^^^^^^^^^^^^^
Some tests are not working reliably and thus are disabled by default.
This includes tests that don't run reliably on GitLab's CI which
usually expose real issues that are rarely seen on developer machines
due to the constraints of the CI environment. If you encounter a
similar situation then raise a bug and then mark the test as shown on
the code snippet below:
.. code::
# See https://gitlab.com/qemu-project/qemu/-/issues/nnnn
@skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab')
def test(self):
do_something()
You can also add ``:avocado: tags=flaky`` to the test meta-data so
only the flaky tests can be run as a group:
.. code::
env QEMU_TEST_FLAKY_TESTS=1 ./pyvenv/bin/avocado \
run tests/avocado -filter-by-tags=flaky
Tests should not live in this state forever and should either be fixed
or eventually removed.
Uninstalling Avocado
--------------------
If you've followed the manual installation instructions above, you can
easily uninstall Avocado. Start by listing the packages you have
installed::
pip list --user
And remove any package you want with::
pip uninstall <package_name>
If you've used ``make check-avocado``, the Python virtual environment where
Avocado is installed will be cleaned up as part of ``make check-clean``.

View file

@ -0,0 +1,177 @@
Block I/O error injection using ``blkdebug``
============================================
..
Copyright (C) 2014-2015 Red Hat Inc
This work is licensed under the terms of the GNU GPL, version 2 or later. See
the COPYING file in the top-level directory.
The ``blkdebug`` block driver is a rule-based error injection engine. It can be
used to exercise error code paths in block drivers including ``ENOSPC`` (out of
space) and ``EIO``.
This document gives an overview of the features available in ``blkdebug``.
Background
----------
Block drivers have many error code paths that handle I/O errors. Image formats
are especially complex since metadata I/O errors during cluster allocation or
while updating tables happen halfway through request processing and require
discipline to keep image files consistent.
Error injection allows test cases to trigger I/O errors at specific points.
This way, all error paths can be tested to make sure they are correct.
Rules
-----
The ``blkdebug`` block driver takes a list of "rules" that tell the error injection
engine when to fail an I/O request.
Each I/O request is evaluated against the rules. If a rule matches the request
then its "action" is executed.
Rules can be placed in a configuration file; the configuration file
follows the same .ini-like format used by QEMU's ``-readconfig`` option, and
each section of the file represents a rule.
The following configuration file defines a single rule::
$ cat blkdebug.conf
[inject-error]
event = "read_aio"
errno = "28"
This rule fails all aio read requests with ``ENOSPC`` (28). Note that the errno
value depends on the host. On Linux, see
``/usr/include/asm-generic/errno-base.h`` for errno values.
Invoke QEMU as follows::
$ qemu-system-x86_64
-drive if=none,cache=none,file=blkdebug:blkdebug.conf:test.img,id=drive0 \
-device virtio-blk-pci,drive=drive0,id=virtio-blk-pci0
Rules support the following attributes:
``event``
which type of operation to match (e.g. ``read_aio``, ``write_aio``,
``flush_to_os``, ``flush_to_disk``). See `Events`_ for
information on events.
``state``
(optional) the engine must be in this state number in order for this
rule to match. See `State transitions`_ for information
on states.
``errno``
the numeric errno value to return when a request matches this rule.
The errno values depend on the host since the numeric values are not
standardized in the POSIX specification.
``sector``
(optional) a sector number that the request must overlap in order to
match this rule
``once``
(optional, default ``off``) only execute this action on the first
matching request
``immediately``
(optional, default ``off``) return a NULL ``BlockAIOCB``
pointer and fail without an errno instead. This
exercises the code path where ``BlockAIOCB`` fails and the
caller's ``BlockCompletionFunc`` is not invoked.
Events
------
Block drivers provide information about the type of I/O request they are about
to make so rules can match specific types of requests. For example, the ``qcow2``
block driver tells ``blkdebug`` when it accesses the L1 table so rules can match
only L1 table accesses and not other metadata or guest data requests.
The core events are:
``read_aio``
guest data read
``write_aio``
guest data write
``flush_to_os``
write out unwritten block driver state (e.g. cached metadata)
``flush_to_disk``
flush the host block device's disk cache
See ``qapi/block-core.json:BlkdebugEvent`` for the full list of events.
You may need to grep block driver source code to understand the
meaning of specific events.
State transitions
-----------------
There are cases where more power is needed to match a particular I/O request in
a longer sequence of requests. For example::
write_aio
flush_to_disk
write_aio
How do we match the 2nd ``write_aio`` but not the first? This is where state
transitions come in.
The error injection engine has an integer called the "state" that always starts
initialized to 1. The state integer is internal to ``blkdebug`` and cannot be
observed from outside but rules can interact with it for powerful matching
behavior.
Rules can be conditional on the current state and they can transition to a new
state.
When a rule's "state" attribute is non-zero then the current state must equal
the attribute in order for the rule to match.
For example, to match the 2nd write_aio::
[set-state]
event = "write_aio"
state = "1"
new_state = "2"
[inject-error]
event = "write_aio"
state = "2"
errno = "5"
The first ``write_aio`` request matches the ``set-state`` rule and transitions from
state 1 to state 2. Once state 2 has been entered, the ``set-state`` rule no
longer matches since it requires state 1. But the ``inject-error`` rule now
matches the next ``write_aio`` request and injects ``EIO`` (5).
State transition rules support the following attributes:
``event``
which type of operation to match (e.g. ``read_aio``, ``write_aio``,
``flush_to_os`, ``flush_to_disk``). See `Events`_ for
information on events.
``state``
(optional) the engine must be in this state number in order for this
rule to match
``new_state``
transition to this state number
Suspend and resume
------------------
Exercising code paths in block drivers may require specific ordering amongst
concurrent requests. The "breakpoint" feature allows requests to be halted on
a ``blkdebug`` event and resumed later. This makes it possible to achieve
deterministic ordering when multiple requests are in flight.
Breakpoints on ``blkdebug`` events are associated with a user-defined ``tag`` string.
This tag serves as an identifier by which the request can be resumed at a later
point.
See the ``qemu-io(1)`` ``break``, ``resume``, ``remove_break``, and ``wait_break``
commands for details.

View file

@ -0,0 +1,73 @@
Block driver correctness testing with ``blkverify``
===================================================
Introduction
------------
This document describes how to use the ``blkverify`` protocol to test that a block
driver is operating correctly.
It is difficult to test and debug block drivers against real guests. Often
processes inside the guest will crash because corrupt sectors were read as part
of the executable. Other times obscure errors are raised by a program inside
the guest. These issues are extremely hard to trace back to bugs in the block
driver.
``blkverify`` solves this problem by catching data corruption inside QEMU the first
time bad data is read and reporting the disk sector that is corrupted.
How it works
------------
The ``blkverify`` protocol has two child block devices, the "test" device and the
"raw" device. Read/write operations are mirrored to both devices so their
state should always be in sync.
The "raw" device is a raw image, a flat file, that has identical starting
contents to the "test" image. The idea is that the "raw" device will handle
read/write operations correctly and not corrupt data. It can be used as a
reference for comparison against the "test" device.
After a mirrored read operation completes, ``blkverify`` will compare the data and
raise an error if it is not identical. This makes it possible to catch the
first instance where corrupt data is read.
Example
-------
Imagine raw.img has 0xcd repeated throughout its first sector::
$ ./qemu-io -c 'read -v 0 512' raw.img
00000000: cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................
00000010: cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................
[...]
000001e0: cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................
000001f0: cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................
read 512/512 bytes at offset 0
512.000000 bytes, 1 ops; 0.0000 sec (97.656 MiB/sec and 200000.0000 ops/sec)
And test.img is corrupt, its first sector is zeroed when it shouldn't be::
$ ./qemu-io -c 'read -v 0 512' test.img
00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
[...]
000001e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
read 512/512 bytes at offset 0
512.000000 bytes, 1 ops; 0.0000 sec (81.380 MiB/sec and 166666.6667 ops/sec)
This error is caught by ``blkverify``::
$ ./qemu-io -c 'read 0 512' blkverify:a.img:b.img
blkverify: read sector_num=0 nb_sectors=4 contents mismatch in sector 0
A more realistic scenario is verifying the installation of a guest OS::
$ ./qemu-img create raw.img 16G
$ ./qemu-img create -f qcow2 test.qcow2 16G
$ ./qemu-system-x86_64 -cdrom debian.iso \
-drive file=blkverify:raw.img:test.qcow2
If the installation is aborted when ``blkverify`` detects corruption, use ``qemu-io``
to explore the contents of the disk image at the sector in question.

View file

@ -0,0 +1,121 @@
Definition of terms
===================
This section defines the terms used in this document and correlates them with
what is currently used on QEMU.
Automated tests
---------------
An automated test is written on a test framework using its generic test
functions/classes. The test framework can run the tests and report their
success or failure [1]_.
An automated test has essentially three parts:
1. The test initialization of the parameters, where the expected parameters,
like inputs and expected results, are set up;
2. The call to the code that should be tested;
3. An assertion, comparing the result from the previous call with the expected
result set during the initialization of the parameters. If the result
matches the expected result, the test has been successful; otherwise, it has
failed.
Unit testing
------------
A unit test is responsible for exercising individual software components as a
unit, like interfaces, data structures, and functionality, uncovering errors
within the boundaries of a component. The verification effort is in the
smallest software unit and focuses on the internal processing logic and data
structures. A test case of unit tests should be designed to uncover errors due
to erroneous computations, incorrect comparisons, or improper control flow [2]_.
On QEMU, unit testing is represented by the 'check-unit' target from 'make'.
Functional testing
------------------
A functional test focuses on the functional requirement of the software.
Deriving sets of input conditions, the functional tests should fully exercise
all the functional requirements for a program. Functional testing is
complementary to other testing techniques, attempting to find errors like
incorrect or missing functions, interface errors, behavior errors, and
initialization and termination errors [3]_.
On QEMU, functional testing is represented by the 'check-qtest' target from
'make'.
System testing
--------------
System tests ensure all application elements mesh properly while the overall
functionality and performance are achieved [4]_. Some or all system components
are integrated to create a complete system to be tested as a whole. System
testing ensures that components are compatible, interact correctly, and
transfer the right data at the right time across their interfaces. As system
testing focuses on interactions, use case-based testing is a practical approach
to system testing [5]_. Note that, in some cases, system testing may require
interaction with third-party software, like operating system images, databases,
networks, and so on.
On QEMU, system testing is represented by the 'check-avocado' target from
'make'.
Flaky tests
-----------
A flaky test is defined as a test that exhibits both a passing and a failing
result with the same code on different runs. Some usual reasons for an
intermittent/flaky test are async wait, concurrency, and test order dependency
[6]_.
Gating
------
A gate restricts the move of code from one stage to another on a
test/deployment pipeline. The step move is granted with approval. The approval
can be a manual intervention or a set of tests succeeding [7]_.
On QEMU, the gating process happens during the pull request. The approval is
done by the project leader running its own set of tests. The pull request gets
merged when the tests succeed.
Continuous Integration (CI)
---------------------------
Continuous integration (CI) requires the builds of the entire application and
the execution of a comprehensive set of automated tests every time there is a
need to commit any set of changes [8]_. The automated tests can be composed of
the unit, functional, system, and other tests.
Keynotes about continuous integration (CI) [9]_:
1. System tests may depend on external software (operating system images,
firmware, database, network).
2. It may take a long time to build and test. It may be impractical to build
the system being developed several times per day.
3. If the development platform is different from the target platform, it may
not be possible to run system tests in the developers private workspace.
There may be differences in hardware, operating system, or installed
software. Therefore, more time is required for testing the system.
References
----------
.. [1] Sommerville, Ian (2016). Software Engineering. p. 233.
.. [2] Pressman, Roger S. & Maxim, Bruce R. (2020). Software Engineering,
A Practitioners Approach. p. 48, 376, 378, 381.
.. [3] Pressman, Roger S. & Maxim, Bruce R. (2020). Software Engineering,
A Practitioners Approach. p. 388.
.. [4] Pressman, Roger S. & Maxim, Bruce R. (2020). Software Engineering,
A Practitioners Approach. Software Engineering, p. 377.
.. [5] Sommerville, Ian (2016). Software Engineering. p. 59, 232, 240.
.. [6] Luo, Qingzhou, et al. An empirical analysis of flaky tests.
Proceedings of the 22nd ACM SIGSOFT International Symposium on
Foundations of Software Engineering. 2014.
.. [7] Humble, Jez & Farley, David (2010). Continuous Delivery:
Reliable Software Releases Through Build, Test, and Deployment, p. 122.
.. [8] Humble, Jez & Farley, David (2010). Continuous Delivery:
Reliable Software Releases Through Build, Test, and Deployment, p. 55.
.. [9] Sommerville, Ian (2016). Software Engineering. p. 743.

View file

@ -0,0 +1,190 @@
.. _ci_var:
Custom CI/CD variables
======================
QEMU CI pipelines can be tuned by setting some CI environment variables.
Set variable globally in the user's CI namespace
------------------------------------------------
Variables can be set globally in the user's CI namespace setting.
For further information about how to set these variables, please refer to::
https://docs.gitlab.com/ee/ci/variables/#add-a-cicd-variable-to-a-project
Set variable manually when pushing a branch or tag to the user's repository
---------------------------------------------------------------------------
Variables can be set manually when pushing a branch or tag, using
git-push command line arguments.
Example setting the QEMU_CI_EXAMPLE_VAR variable:
.. code::
git push -o ci.variable="QEMU_CI_EXAMPLE_VAR=value" myrepo mybranch
For further information about how to set these variables, please refer to::
https://docs.gitlab.com/ee/user/project/push_options.html#push-options-for-gitlab-cicd
Setting aliases in your git config
----------------------------------
You can use aliases to make it easier to push branches with different
CI configurations. For example define an alias for triggering CI:
.. code::
git config --local alias.push-ci "push -o ci.variable=QEMU_CI=1"
git config --local alias.push-ci-now "push -o ci.variable=QEMU_CI=2"
Which lets you run:
.. code::
git push-ci
to create the pipeline, or:
.. code::
git push-ci-now
to create and run the pipeline
Variable naming and grouping
----------------------------
The variables used by QEMU's CI configuration are grouped together
in a handful of namespaces
* QEMU_JOB_nnnn - variables to be defined in individual jobs
or templates, to influence the shared rules defined in the
.base_job_template.
* QEMU_CI_nnn - variables to be set by contributors in their
repository CI settings, or as git push variables, to influence
which jobs get run in a pipeline
* QEMU_CI_CONTAINER_TAG - the tag used to publish containers
in stage 1, for use by build jobs in stage 2. Defaults to
'latest', but if running pipelines for different branches
concurrently, it should be overridden per pipeline.
* QEMU_CI_UPSTREAM - gitlab namespace that is considered to be
the 'upstream'. This defaults to 'qemu-project'. Contributors
may choose to override this if they are modifying rules in
base.yml and need to validate how they will operate when in
an upstream context, as opposed to their fork context.
* nnn - other misc variables not falling into the above
categories, or using different names for historical reasons
and not yet converted.
Maintainer controlled job variables
-----------------------------------
The following variables may be set when defining a job in the
CI configuration file.
QEMU_JOB_CIRRUS
~~~~~~~~~~~~~~~
The job makes use of Cirrus CI infrastructure, requiring the
configuration setup for cirrus-run to be present in the repository
QEMU_JOB_OPTIONAL
~~~~~~~~~~~~~~~~~
The job is expected to be successful in general, but is not run
by default due to need to conserve limited CI resources. It is
available to be started manually by the contributor in the CI
pipelines UI.
QEMU_JOB_ONLY_FORKS
~~~~~~~~~~~~~~~~~~~
The job results are only of interest to contributors prior to
submitting code. They are not required as part of the gating
CI pipeline.
QEMU_JOB_SKIPPED
~~~~~~~~~~~~~~~~
The job is not reliably successful in general, so is not
currently suitable to be run by default. Ideally this should
be a temporary marker until the problems can be addressed, or
the job permanently removed.
QEMU_JOB_PUBLISH
~~~~~~~~~~~~~~~~
The job is for publishing content after a branch has been
merged into the upstream default branch.
QEMU_JOB_AVOCADO
~~~~~~~~~~~~~~~~
The job runs the Avocado integration test suite
Contributor controlled runtime variables
----------------------------------------
The following variables may be set by contributors to control
job execution
QEMU_CI
~~~~~~~
By default, no pipelines will be created on contributor forks
in order to preserve CI credits
Set this variable to 1 to create the pipelines, but leave all
the jobs to be manually started from the UI
Set this variable to 2 to create the pipelines and run all
the jobs immediately, as was the historical behaviour
QEMU_CI_AVOCADO_TESTING
~~~~~~~~~~~~~~~~~~~~~~~
By default, tests using the Avocado framework are not run automatically in
the pipelines (because multiple artifacts have to be downloaded, and if
these artifacts are not already cached, downloading them make the jobs
reach the timeout limit). Set this variable to have the tests using the
Avocado framework run automatically.
Other misc variables
--------------------
These variables are primarily to control execution of jobs on
private runners
AARCH64_RUNNER_AVAILABLE
~~~~~~~~~~~~~~~~~~~~~~~~
If you've got access to an aarch64 host that can be used as a gitlab-CI
runner, you can set this variable to enable the tests that require this
kind of host. The runner should be tagged with "aarch64".
AARCH32_RUNNER_AVAILABLE
~~~~~~~~~~~~~~~~~~~~~~~~
If you've got access to an armhf host or an arch64 host that can run
aarch32 EL0 code to be used as a gitlab-CI runner, you can set this
variable to enable the tests that require this kind of host. The
runner should be tagged with "aarch32".
S390X_RUNNER_AVAILABLE
~~~~~~~~~~~~~~~~~~~~~~
If you've got access to an IBM Z host that can be used as a gitlab-CI
runner, you can set this variable to enable the tests that require this
kind of host. The runner should be tagged with "s390x".
CCACHE_DISABLE
~~~~~~~~~~~~~~
The jobs are configured to use "ccache" by default since this typically
reduces compilation time, at the cost of increased storage. If the
use of "ccache" is suspected to be hurting the overall job execution
time, setting the "CCACHE_DISABLE=1" env variable to disable it.

View file

@ -0,0 +1,116 @@
Jobs on Custom Runners
======================
Besides the jobs run under the various CI systems listed before, there
are a number additional jobs that will run before an actual merge.
These use the same GitLab CI's service/framework already used for all
other GitLab based CI jobs, but rely on additional systems, not the
ones provided by GitLab as "shared runners".
The architecture of GitLab's CI service allows different machines to
be set up with GitLab's "agent", called gitlab-runner, which will take
care of running jobs created by events such as a push to a branch.
Here, the combination of a machine, properly configured with GitLab's
gitlab-runner, is called a "custom runner".
The GitLab CI jobs definition for the custom runners are located under::
.gitlab-ci.d/custom-runners.yml
Custom runners entail custom machines. To see a list of the machines
currently deployed in the QEMU GitLab CI and their maintainers, please
refer to the QEMU `wiki <https://wiki.qemu.org/AdminContacts>`__.
Machine Setup Howto
-------------------
For all Linux based systems, the setup can be mostly automated by the
execution of two Ansible playbooks. Create an ``inventory`` file
under ``scripts/ci/setup``, such as this::
fully.qualified.domain
other.machine.hostname
You may need to set some variables in the inventory file itself. One
very common need is to tell Ansible to use a Python 3 interpreter on
those hosts. This would look like::
fully.qualified.domain ansible_python_interpreter=/usr/bin/python3
other.machine.hostname ansible_python_interpreter=/usr/bin/python3
Build environment
~~~~~~~~~~~~~~~~~
The ``scripts/ci/setup/$DISTRO/build-environment.yml`` Ansible
playbook will set up machines with the environment needed to perform
builds and run QEMU tests. This playbook consists on the installation
of various required packages (and a general package update while at
it).
The minimum required version of Ansible successfully tested in this
playbook is 2.8.0 (a version check is embedded within the playbook
itself). To run the playbook, execute::
cd scripts/ci/setup
ansible-playbook -i inventory $DISTRO/build-environment.yml
Please note that most of the tasks in the playbook require superuser
privileges, such as those from the ``root`` account or those obtained
by ``sudo``. If necessary, please refer to ``ansible-playbook``
options such as ``--become``, ``--become-method``, ``--become-user``
and ``--ask-become-pass``.
gitlab-runner setup and registration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The gitlab-runner agent needs to be installed on each machine that
will run jobs. The association between a machine and a GitLab project
happens with a registration token. To find the registration token for
your repository/project, navigate on GitLab's web UI to:
* Settings (the gears-like icon at the bottom of the left hand side
vertical toolbar), then
* CI/CD, then
* Runners, and click on the "Expand" button, then
* Under "Set up a specific Runner manually", look for the value under
"And this registration token:"
Copy the ``scripts/ci/setup/vars.yml.template`` file to
``scripts/ci/setup/vars.yml``. Then, set the
``gitlab_runner_registration_token`` variable to the value obtained
earlier.
To run the playbook, execute::
cd scripts/ci/setup
ansible-playbook -i inventory gitlab-runner.yml
Following the registration, it's necessary to configure the runner tags,
and optionally other configurations on the GitLab UI. Navigate to:
* Settings (the gears like icon), then
* CI/CD, then
* Runners, and click on the "Expand" button, then
* "Runners activated for this project", then
* Click on the "Edit" icon (next to the "Lock" Icon)
Tags are very important as they are used to route specific jobs to
specific types of runners, so it's a good idea to double check that
the automatically created tags are consistent with the OS and
architecture. For instance, an Ubuntu 20.04 aarch64 system should
have tags set as::
ubuntu_20.04,aarch64
Because the job definition at ``.gitlab-ci.d/custom-runners.yml``
would contain::
ubuntu-20.04-aarch64-all:
tags:
- ubuntu_20.04
- aarch64
It's also recommended to:
* increase the "Maximum job timeout" to something like ``2h``
* give it a better Description

14
docs/devel/testing/ci.rst Normal file
View file

@ -0,0 +1,14 @@
.. _ci:
==
CI
==
Most of QEMU's CI is run on GitLab's infrastructure although a number
of other CI services are used for specialised purposes. The most up to
date information about them and their status can be found on the
`project wiki testing page <https://wiki.qemu.org/Testing/CI>`_.
.. include:: ci-definitions.rst.inc
.. include:: ci-jobs.rst.inc
.. include:: ci-runners.rst.inc

View file

@ -0,0 +1,383 @@
.. _checkfunctional-ref:
Functional testing with Python
==============================
The ``tests/functional`` directory hosts functional tests written in
Python. They are usually higher level tests, and may interact with
external resources and with various guest operating systems.
The functional tests have initially evolved from the Avocado tests, so there
is a lot of similarity to those tests here (see :ref:`checkavocado-ref` for
details about the Avocado tests).
The tests should be written in the style of the Python `unittest`_ framework,
using stdio for the TAP protocol. The folder ``tests/functional/qemu_test``
provides classes (e.g. the ``QemuBaseTest``, ``QemuUserTest`` and the
``QemuSystemTest`` classes) and utility functions that help to get your test
into the right shape, e.g. by replacing the 'stdout' python object to redirect
the normal output of your test to stderr instead.
Note that if you don't use one of the QemuBaseTest based classes for your
test, or if you spawn subprocesses from your test, you have to make sure
that there is no TAP-incompatible output written to stdio, e.g. either by
prefixing every line with a "# " to mark the output as a TAP comment, or
e.g. by capturing the stdout output of subprocesses (redirecting it to
stderr is OK).
Tests based on ``qemu_test.QemuSystemTest`` can easily:
* Customize the command line arguments given to the convenience
``self.vm`` attribute (a QEMUMachine instance)
* Interact with the QEMU monitor, send QMP commands and check
their results
* Interact with the guest OS, using the convenience console device
(which may be useful to assert the effectiveness and correctness of
command line arguments or QMP commands)
* Download (and cache) remote data files, such as firmware and kernel
images
Running tests
-------------
You can run the functional tests simply by executing:
.. code::
make check-functional
It is also possible to run tests for a certain target only, for example
the following line will only run the tests for the x86_64 target:
.. code::
make check-functional-x86_64
To run a single test file without the meson test runner, you can also
execute the file directly by specifying two environment variables first,
the PYTHONPATH that has to include the python folder and the tests/functional
folder of the source tree, and QEMU_TEST_QEMU_BINARY that has to point
to the QEMU binary that should be used for the test. The current working
directory should be your build folder. For example::
$ export PYTHONPATH=../python:../tests/functional
$ export QEMU_TEST_QEMU_BINARY=$PWD/qemu-system-x86_64
$ pyvenv/bin/python3 ../tests/functional/test_file.py
The test framework will automatically purge any scratch files created during
the tests. If needing to debug a failed test, it is possible to keep these
files around on disk by setting ```QEMU_TEST_KEEP_SCRATCH=1``` as an env
variable. Any preserved files will be deleted the next time the test is run
without this variable set.
Logging
-------
The framework collects log files for each test in the build directory
in the following subfolder::
<builddir>/tests/functional/<arch>/<fileid>.<classid>.<testname>/
There are usually three log files:
* ``base.log`` contains the generic logging information that is written
by the calls to the logging functions in the test code (e.g. by calling
the ``self.log.info()`` or ``self.log.debug()`` functions).
* ``console.log`` contains the output of the serial console of the guest.
* ``default.log`` contains the output of QEMU. This file could be named
differently if the test chooses to use a different identifier for
the guest VM (e.g. when the test spins up multiple VMs).
Introduction to writing tests
-----------------------------
The ``tests/functional/qemu_test`` directory provides the ``qemu_test``
Python module, containing the ``qemu_test.QemuSystemTest`` class.
Here is a simple usage example:
.. code::
#!/usr/bin/env python3
from qemu_test import QemuSystemTest
class Version(QemuSystemTest):
def test_qmp_human_info_version(self):
self.vm.launch()
res = self.vm.cmd('human-monitor-command',
command_line='info version')
self.assertRegex(res, r'^(\d+\.\d+\.\d)')
if __name__ == '__main__':
QemuSystemTest.main()
By providing the "hash bang" line at the beginning of the script, marking
the file as executable and by calling into QemuSystemTest.main(), the test
can also be run stand-alone, without a test runner. OTOH when run via a test
runner, the QemuSystemTest.main() function takes care of running the test
functions in the right fassion (e.g. with TAP output that is required by the
meson test runner).
The ``qemu_test.QemuSystemTest`` base test class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``qemu_test.QemuSystemTest`` class has a number of characteristics
that are worth being mentioned.
First of all, it attempts to give each test a ready to use QEMUMachine
instance, available at ``self.vm``. Because many tests will tweak the
QEMU command line, launching the QEMUMachine (by using ``self.vm.launch()``)
is left to the test writer.
The base test class has also support for tests with more than one
QEMUMachine. The way to get machines is through the ``self.get_vm()``
method which will return a QEMUMachine instance. The ``self.get_vm()``
method accepts arguments that will be passed to the QEMUMachine creation
and also an optional ``name`` attribute so you can identify a specific
machine and get it more than once through the tests methods. A simple
and hypothetical example follows:
.. code::
from qemu_test import QemuSystemTest
class MultipleMachines(QemuSystemTest):
def test_multiple_machines(self):
first_machine = self.get_vm()
second_machine = self.get_vm()
self.get_vm(name='third_machine').launch()
first_machine.launch()
second_machine.launch()
first_res = first_machine.cmd(
'human-monitor-command',
command_line='info version')
second_res = second_machine.cmd(
'human-monitor-command',
command_line='info version')
third_res = self.get_vm(name='third_machine').cmd(
'human-monitor-command',
command_line='info version')
self.assertEqual(first_res, second_res, third_res)
At test "tear down", ``qemu_test.QemuSystemTest`` handles all the QEMUMachines
shutdown.
QEMUMachine
-----------
The QEMUMachine API is already widely used in the Python iotests,
device-crash-test and other Python scripts. It's a wrapper around the
execution of a QEMU binary, giving its users:
* the ability to set command line arguments to be given to the QEMU
binary
* a ready to use QMP connection and interface, which can be used to
send commands and inspect its results, as well as asynchronous
events
* convenience methods to set commonly used command line arguments in
a more succinct and intuitive way
QEMU binary selection
^^^^^^^^^^^^^^^^^^^^^
The QEMU binary used for the ``self.vm`` QEMUMachine instance will
primarily depend on the value of the ``qemu_bin`` instance attribute.
If it is not explicitly set by the test code, its default value will
be the result the QEMU_TEST_QEMU_BINARY environment variable.
Debugging hung QEMU
^^^^^^^^^^^^^^^^^^^
When test cases go wrong it may be helpful to debug a stalled QEMU
process. While the QEMUMachine class owns the primary QMP monitor
socket, it is possible to request a second QMP monitor be created
by setting the ``QEMU_TEST_QMP_BACKDOOR`` env variable to refer
to a UNIX socket name. The ``qmp-shell`` command can then be
attached to the stalled QEMU to examine its live state.
Attribute reference
-------------------
QemuBaseTest
^^^^^^^^^^^^
The following attributes are available on any ``qemu_test.QemuBaseTest``
instance.
arch
""""
The target architecture of the QEMU binary.
Tests are also free to use this attribute value, for their own needs.
A test may, for instance, use this value when selecting the architecture
of a kernel or disk image to boot a VM with.
qemu_bin
""""""""
The preserved value of the ``QEMU_TEST_QEMU_BINARY`` environment
variable.
QemuUserTest
^^^^^^^^^^^^
The QemuUserTest class can be used for running an executable via the
usermode emulation binaries.
QemuSystemTest
^^^^^^^^^^^^^^
The QemuSystemTest class can be used for running tests via one of the
qemu-system-* binaries.
vm
""
A QEMUMachine instance, initially configured according to the given
``qemu_bin`` parameter.
cpu
"""
The cpu model that will be set to all QEMUMachine instances created
by the test.
machine
"""""""
The machine type that will be set to all QEMUMachine instances created
by the test. By using the set_machine() function of the QemuSystemTest
class to set this attribute, you can automatically check whether the
machine is available to skip the test in case it is not built into the
QEMU binary.
Asset handling
--------------
Many functional tests download assets (e.g. Linux kernels, initrds,
firmware images, etc.) from the internet to be able to run tests with
them. This imposes additional challenges to the test framework.
First there is the problem that some people might not have an
unconstrained internet connection, so such tests should not be run by
default when running ``make check``. To accomplish this situation,
the tests that download files should only be added to the "thorough"
speed mode in the meson.build file, while the "quick" speed mode is
fine for functional tests that can be run without downloading files.
``make check`` then only runs the quick functional tests along with
the other quick tests from the other test suites. If you choose to
run only run ``make check-functional``, the "thorough" tests will be
executed, too. And to run all functional tests along with the others,
you can use something like::
make -j$(nproc) check SPEED=thorough
The second problem with downloading files from the internet are time
constraints. The time for downloading files should not be taken into
account when the test is running and the timeout of the test is ticking
(since downloading can be very slow, depending on the network bandwidth).
This problem is solved by downloading the assets ahead of time, before
the tests are run. This pre-caching is done with the qemu_test.Asset
class. To use it in your test, declare an asset in your test class with
its URL and SHA256 checksum like this::
from qemu_test import Asset
ASSET_somename = Asset(
('https://www.qemu.org/assets/images/qemu_head_200.png'),
'34b74cad46ea28a2966c1d04e102510daf1fd73e6582b6b74523940d5da029dd')
In your test function, you can then get the file name of the cached
asset like this::
def test_function(self):
file_path = self.ASSET_somename.fetch()
The pre-caching will be done automatically when running
``make check-functional`` (but not when running e.g.
``make check-functional-<target>``). In case you just want to download
the assets without running the tests, you can do so by running::
make precache-functional
The cache is populated in the ``~/.cache/qemu/download`` directory by
default, but the location can be changed by setting the
``QEMU_TEST_CACHE_DIR`` environment variable.
Skipping tests
--------------
Since the test framework is based on the common Python unittest framework,
you can use the usual Python decorators which allow for easily skipping
tests running under certain conditions, for example, on the lack of a binary
on the test system or when the running environment is a CI system. For further
information about those decorators, please refer to:
https://docs.python.org/3/library/unittest.html#skipping-tests-and-expected-failures
While the conditions for skipping tests are often specifics of each one, there
are recurring scenarios identified by the QEMU developers and the use of
environment variables became a kind of standard way to enable/disable tests.
Here is a list of the most used variables:
QEMU_TEST_ALLOW_LARGE_STORAGE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tests which are going to fetch or produce assets considered *large* are not
going to run unless that ``QEMU_TEST_ALLOW_LARGE_STORAGE=1`` is exported on
the environment.
The definition of *large* is a bit arbitrary here, but it usually means an
asset which occupies at least 1GB of size on disk when uncompressed.
QEMU_TEST_ALLOW_UNTRUSTED_CODE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are tests which will boot a kernel image or firmware that can be
considered not safe to run on the developer's workstation, thus they are
skipped by default. The definition of *not safe* is also arbitrary but
usually it means a blob which either its source or build process aren't
public available.
You should export ``QEMU_TEST_ALLOW_UNTRUSTED_CODE=1`` on the environment in
order to allow tests which make use of those kind of assets.
QEMU_TEST_FLAKY_TESTS
^^^^^^^^^^^^^^^^^^^^^
Some tests are not working reliably and thus are disabled by default.
This includes tests that don't run reliably on GitLab's CI which
usually expose real issues that are rarely seen on developer machines
due to the constraints of the CI environment. If you encounter a
similar situation then raise a bug and then mark the test as shown on
the code snippet below:
.. code::
# See https://gitlab.com/qemu-project/qemu/-/issues/nnnn
@skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab')
def test(self):
do_something()
Tests should not live in this state forever and should either be fixed
or eventually removed.
QEMU_TEST_ALLOW_SLOW
^^^^^^^^^^^^^^^^^^^^
Tests that have a very long runtime and might run into timeout issues
e.g. if the QEMU binary has been compiled with debugging options enabled.
To avoid these timeout issues by default and to save some precious CPU
cycles during normal testing, such tests are disabled by default unless
the QEMU_TEST_ALLOW_SLOW environment variable has been set.
.. _unittest: https://docs.python.org/3/library/unittest.html

View file

@ -0,0 +1,305 @@
========
Fuzzing
========
This document describes the virtual-device fuzzing infrastructure in QEMU and
how to use it to implement additional fuzzers.
Basics
------
Fuzzing operates by passing inputs to an entry point/target function. The
fuzzer tracks the code coverage triggered by the input. Based on these
findings, the fuzzer mutates the input and repeats the fuzzing.
To fuzz QEMU, we rely on libfuzzer. Unlike other fuzzers such as AFL, libfuzzer
is an *in-process* fuzzer. For the developer, this means that it is their
responsibility to ensure that state is reset between fuzzing-runs.
Building the fuzzers
--------------------
To build the fuzzers, install a recent version of clang:
Configure with (substitute the clang binaries with the version you installed).
Here, enable-asan and enable-ubsan are optional but they allow us to reliably
detect bugs such as out-of-bounds accesses, uses-after-free, double-frees
etc.::
CC=clang-8 CXX=clang++-8 /path/to/configure \
--enable-fuzzing --enable-asan --enable-ubsan
Fuzz targets are built similarly to system targets::
make qemu-fuzz-i386
This builds ``./qemu-fuzz-i386``
The first option to this command is: ``--fuzz-target=FUZZ_NAME``
To list all of the available fuzzers run ``qemu-fuzz-i386`` with no arguments.
For example::
./qemu-fuzz-i386 --fuzz-target=virtio-scsi-fuzz
Internally, libfuzzer parses all arguments that do not begin with ``"--"``.
Information about these is available by passing ``-help=1``
Now the only thing left to do is wait for the fuzzer to trigger potential
crashes.
Useful libFuzzer flags
----------------------
As mentioned above, libFuzzer accepts some arguments. Passing ``-help=1`` will
list the available arguments. In particular, these arguments might be helpful:
* ``CORPUS_DIR/`` : Specify a directory as the last argument to libFuzzer.
libFuzzer stores each "interesting" input in this corpus directory. The next
time you run libFuzzer, it will read all of the inputs from the corpus, and
continue fuzzing from there. You can also specify multiple directories.
libFuzzer loads existing inputs from all specified directories, but will only
write new ones to the first one specified.
* ``-max_len=4096`` : specify the maximum byte-length of the inputs libFuzzer
will generate.
* ``-close_fd_mask={1,2,3}`` : close, stderr, or both. Useful for targets that
trigger many debug/error messages, or create output on the serial console.
* ``-jobs=4 -workers=4`` : These arguments configure libFuzzer to run 4 fuzzers in
parallel (4 fuzzing jobs in 4 worker processes). Alternatively, with only
``-jobs=N``, libFuzzer automatically spawns a number of workers less than or equal
to half the available CPU cores. Replace 4 with a number appropriate for your
machine. Make sure to specify a ``CORPUS_DIR``, which will allow the parallel
fuzzers to share information about the interesting inputs they find.
* ``-use_value_profile=1`` : For each comparison operation, libFuzzer computes
``(caller_pc&4095) | (popcnt(Arg1 ^ Arg2) << 12)`` and places this in the
coverage table. Useful for targets with "magic" constants. If Arg1 came from
the fuzzer's input and Arg2 is a magic constant, then each time the Hamming
distance between Arg1 and Arg2 decreases, libFuzzer adds the input to the
corpus.
* ``-shrink=1`` : Tries to make elements of the corpus "smaller". Might lead to
better coverage performance, depending on the target.
Note that libFuzzer's exact behavior will depend on the version of
clang and libFuzzer used to build the device fuzzers.
Generating Coverage Reports
---------------------------
Code coverage is a crucial metric for evaluating a fuzzer's performance.
libFuzzer's output provides a "cov: " column that provides a total number of
unique blocks/edges covered. To examine coverage on a line-by-line basis we
can use Clang coverage:
1. Configure libFuzzer to store a corpus of all interesting inputs (see
CORPUS_DIR above)
2. ``./configure`` the QEMU build with ::
--enable-fuzzing \
--extra-cflags="-fprofile-instr-generate -fcoverage-mapping"
3. Re-run the fuzzer. Specify $CORPUS_DIR/* as an argument, telling libfuzzer
to execute all of the inputs in $CORPUS_DIR and exit. Once the process
exits, you should find a file, "default.profraw" in the working directory.
4. Execute these commands to generate a detailed HTML coverage-report::
llvm-profdata merge -output=default.profdata default.profraw
llvm-cov show ./path/to/qemu-fuzz-i386 -instr-profile=default.profdata \
--format html -output-dir=/path/to/output/report
Adding a new fuzzer
-------------------
Coverage over virtual devices can be improved by adding additional fuzzers.
Fuzzers are kept in ``tests/qtest/fuzz/`` and should be added to
``tests/qtest/fuzz/meson.build``
Fuzzers can rely on both qtest and libqos to communicate with virtual devices.
1. Create a new source file. For example ``tests/qtest/fuzz/foo-device-fuzz.c``.
2. Write the fuzzing code using the libqtest/libqos API. See existing fuzzers
for reference.
3. Add the fuzzer to ``tests/qtest/fuzz/meson.build``.
Fuzzers can be more-or-less thought of as special qtest programs which can
modify the qtest commands and/or qtest command arguments based on inputs
provided by libfuzzer. Libfuzzer passes a byte array and length. Commonly the
fuzzer loops over the byte-array interpreting it as a list of qtest commands,
addresses, or values.
The Generic Fuzzer
------------------
Writing a fuzz target can be a lot of effort (especially if a device driver has
not be built-out within libqos). Many devices can be fuzzed to some degree,
without any device-specific code, using the generic-fuzz target.
The generic-fuzz target is capable of fuzzing devices over their PIO, MMIO,
and DMA input-spaces. To apply the generic-fuzz to a device, we need to define
two env-variables, at minimum:
* ``QEMU_FUZZ_ARGS=`` is the set of QEMU arguments used to configure a machine, with
the device attached. For example, if we want to fuzz the virtio-net device
attached to a pc-i440fx machine, we can specify::
QEMU_FUZZ_ARGS="-M pc -nodefaults -netdev user,id=user0 \
-device virtio-net,netdev=user0"
* ``QEMU_FUZZ_OBJECTS=`` is a set of space-delimited strings used to identify
the MemoryRegions that will be fuzzed. These strings are compared against
MemoryRegion names and MemoryRegion owner names, to decide whether each
MemoryRegion should be fuzzed. These strings support globbing. For the
virtio-net example, we could use one of ::
QEMU_FUZZ_OBJECTS='virtio-net'
QEMU_FUZZ_OBJECTS='virtio*'
QEMU_FUZZ_OBJECTS='virtio* pcspk' # Fuzz the virtio devices and the speaker
QEMU_FUZZ_OBJECTS='*' # Fuzz the whole machine``
The ``"info mtree"`` and ``"info qom-tree"`` monitor commands can be especially
useful for identifying the ``MemoryRegion`` and ``Object`` names used for
matching.
As a generic rule-of-thumb, the more ``MemoryRegions``/Devices we match, the
greater the input-space, and the smaller the probability of finding crashing
inputs for individual devices. As such, it is usually a good idea to limit the
fuzzer to only a few ``MemoryRegions``.
To ensure that these env variables have been configured correctly, we can use::
./qemu-fuzz-i386 --fuzz-target=generic-fuzz -runs=0
The output should contain a complete list of matched MemoryRegions.
OSS-Fuzz
--------
QEMU is continuously fuzzed on `OSS-Fuzz
<https://github.com/google/oss-fuzz>`_. By default, the OSS-Fuzz build
will try to fuzz every fuzz-target. Since the generic-fuzz target
requires additional information provided in environment variables, we
pre-define some generic-fuzz configs in
``tests/qtest/fuzz/generic_fuzz_configs.h``. Each config must specify:
- ``.name``: To identify the fuzzer config
- ``.args`` OR ``.argfunc``: A string or pointer to a function returning a
string. These strings are used to specify the ``QEMU_FUZZ_ARGS``
environment variable. ``argfunc`` is useful when the config relies on e.g.
a dynamically created temp directory, or a free tcp/udp port.
- ``.objects``: A string that specifies the ``QEMU_FUZZ_OBJECTS`` environment
variable.
To fuzz additional devices/device configuration on OSS-Fuzz, send patches for
either a new device-specific fuzzer or a new generic-fuzz config.
Build details:
- The Dockerfile that sets up the environment for building QEMU's
fuzzers on OSS-Fuzz can be fund in the OSS-Fuzz repository
__(https://github.com/google/oss-fuzz/blob/master/projects/qemu/Dockerfile)
- The script responsible for building the fuzzers can be found in the
QEMU source tree at ``scripts/oss-fuzz/build.sh``
Building Crash Reproducers
-----------------------------------------
When we find a crash, we should try to create an independent reproducer, that
can be used on a non-fuzzer build of QEMU. This filters out any potential
false-positives, and improves the debugging experience for developers.
Here are the steps for building a reproducer for a crash found by the
generic-fuzz target.
- Ensure the crash reproduces::
qemu-fuzz-i386 --fuzz-target... ./crash-...
- Gather the QTest output for the crash::
QEMU_FUZZ_TIMEOUT=0 QTEST_LOG=1 FUZZ_SERIALIZE_QTEST=1 \
qemu-fuzz-i386 --fuzz-target... ./crash-... &> /tmp/trace
- Reorder and clean-up the resulting trace::
scripts/oss-fuzz/reorder_fuzzer_qtest_trace.py /tmp/trace > /tmp/reproducer
- Get the arguments needed to start qemu, and provide a path to qemu::
less /tmp/trace # The args should be logged at the top of this file
export QEMU_ARGS="-machine ..."
export QEMU_PATH="path/to/qemu-system"
- Ensure the crash reproduces in qemu-system::
$QEMU_PATH $QEMU_ARGS -qtest stdio < /tmp/reproducer
- From the crash output, obtain some string that identifies the crash. This
can be a line in the stack-trace, for example::
export CRASH_TOKEN="hw/usb/hcd-xhci.c:1865"
- Minimize the reproducer::
scripts/oss-fuzz/minimize_qtest_trace.py -M1 -M2 \
/tmp/reproducer /tmp/reproducer-minimized
- Confirm that the minimized reproducer still crashes::
$QEMU_PATH $QEMU_ARGS -qtest stdio < /tmp/reproducer-minimized
- Create a one-liner reproducer that can be sent over email::
./scripts/oss-fuzz/output_reproducer.py -bash /tmp/reproducer-minimized
- Output the C source code for a test case that will reproduce the bug::
./scripts/oss-fuzz/output_reproducer.py -owner "John Smith <john@smith.com>"\
-name "test_function_name" /tmp/reproducer-minimized
- Report the bug and send a patch with the C reproducer upstream
Implementation Details / Fuzzer Lifecycle
-----------------------------------------
The fuzzer has two entrypoints that libfuzzer calls. libfuzzer provides it's
own ``main()``, which performs some setup, and calls the entrypoints:
``LLVMFuzzerInitialize``: called prior to fuzzing. Used to initialize all of the
necessary state
``LLVMFuzzerTestOneInput``: called for each fuzzing run. Processes the input and
resets the state at the end of each run.
In more detail:
``LLVMFuzzerInitialize`` parses the arguments to the fuzzer (must start with two
dashes, so they are ignored by libfuzzer ``main()``). Currently, the arguments
select the fuzz target. Then, the qtest client is initialized. If the target
requires qos, qgraph is set up and the QOM/LIBQOS modules are initialized.
Then the QGraph is walked and the QEMU cmd_line is determined and saved.
After this, the ``vl.c:main`` is called to set up the guest. There are
target-specific hooks that can be called before and after main, for
additional setup(e.g. PCI setup, or VM snapshotting).
``LLVMFuzzerTestOneInput``: Uses qtest/qos functions to act based on the fuzz
input. It is also responsible for manually calling ``main_loop_wait`` to ensure
that bottom halves are executed and any cleanup required before the next input.
Since the same process is reused for many fuzzing runs, QEMU state needs to
be reset at the end of each run. For example, this can be done by rebooting the
VM, after each run.
- *Pros*: Straightforward and fast for simple fuzz targets.
- *Cons*: Depending on the device, does not reset all device state. If the
device requires some initialization prior to being ready for fuzzing (common
for QOS-based targets), this initialization needs to be done after each
reboot.
- *Example target*: ``i440fx-qtest-reboot-fuzz``

View file

@ -0,0 +1,18 @@
Testing QEMU
------------
Details about how to test QEMU and how it is integrated into our CI
testing infrastructure.
.. toctree::
:maxdepth: 3
main
qtest
functional
avocado
acpi-bits
ci
fuzzing
blkdebug
blkverify

1025
docs/devel/testing/main.rst Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,628 @@
.. _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

View file

@ -0,0 +1,93 @@
.. _qtest:
========================================
QTest Device Emulation Testing Framework
========================================
.. toctree::
qgraph
QTest is a device emulation testing framework. It can be very useful to test
device models; it could also control certain aspects of QEMU (such as virtual
clock stepping), with a special purpose "qtest" protocol. Refer to
:ref:`qtest-protocol` for more details of the protocol.
QTest cases can be executed with
.. code::
make check-qtest
The QTest library is implemented by ``tests/qtest/libqtest.c`` and the API is
defined in ``tests/qtest/libqtest.h``.
Consider adding a new QTest case when you are introducing a new virtual
hardware, or extending one if you are adding functionalities to an existing
virtual device.
On top of libqtest, a higher level library, ``libqos``, was created to
encapsulate common tasks of device drivers, such as memory management and
communicating with system buses or devices. Many virtual device tests use
libqos instead of directly calling into libqtest.
Libqos also offers the Qgraph API to increase each test coverage and
automate QEMU command line arguments and devices setup.
Refer to :ref:`qgraph` for Qgraph explanation and API.
Steps to add a new QTest case are:
1. Create a new source file for the test. (More than one file can be added as
necessary.) For example, ``tests/qtest/foo-test.c``.
2. Write the test code with the glib and libqtest/libqos API. See also existing
tests and the library headers for reference.
3. Register the new test in ``tests/qtest/meson.build``. Add the test
executable name to an appropriate ``qtests_*`` variable. There is
one variable per architecture, plus ``qtests_generic`` for tests
that can be run for all architectures. For example::
qtests_generic = [
...
'foo-test',
...
]
4. If the test has more than one source file or needs to be linked with any
dependency other than ``qemuutil`` and ``qos``, list them in the ``qtests``
dictionary. For example a test that needs to use the ``QIO`` library
will have an entry like::
{
...
'foo-test': [io],
...
}
Debugging a QTest failure is slightly harder than the unit test because the
tests look up QEMU program names in the environment variables, such as
``QTEST_QEMU_BINARY`` and ``QTEST_QEMU_IMG``, and also because it is not easy
to attach gdb to the QEMU process spawned from the test. But manual invoking
and using gdb on the test is still simple to do: find out the actual command
from the output of
.. code::
make check-qtest V=1
which you can run manually.
.. _qtest-protocol:
QTest Protocol
--------------
.. kernel-doc:: system/qtest.c
:doc: QTest Protocol
libqtest API reference
----------------------
.. kernel-doc:: tests/qtest/libqtest.h