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