|
@@ -0,0 +1,338 @@
|
|
|
|
+.. _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, for example::
|
|
|
|
+
|
|
|
|
+ $ export PYTHONPATH=../python:../tests/functional
|
|
|
|
+ $ export QEMU_TEST_QEMU_BINARY=$PWD/qemu-system-x86_64
|
|
|
|
+ $ python3 ../tests/functional/test_file.py
|
|
|
|
+
|
|
|
|
+Overview
|
|
|
|
+--------
|
|
|
|
+
|
|
|
|
+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`` class 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.
|
|
|
|
+
|
|
|
|
+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 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::
|
|
|
|
+
|
|
|
|
+ ASSET_somename = (
|
|
|
|
+ ('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.
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+.. _unittest: https://docs.python.org/3/library/unittest.html
|