123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- .. _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
|