| 1 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | # |
| 3 | # Decorators useful in functional tests |
| 4 | |
| 5 | import importlib |
| 6 | import os |
| 7 | import platform |
| 8 | import resource |
| 9 | import subprocess |
| 10 | from unittest import skipIf, skipUnless |
| 11 | |
| 12 | from .cmd import which |
| 13 | |
| 14 | |
| 15 | def skipIfMissingEnv(*vars_): |
| 16 | ''' |
| 17 | Decorator to skip execution of a test if the provided |
| 18 | environment variables are not set. |
| 19 | Example: |
| 20 | |
| 21 | @skipIfMissingEnv("QEMU_ENV_VAR0", "QEMU_ENV_VAR1") |
| 22 | ''' |
| 23 | missing_vars = [] |
| 24 | for var in vars_: |
| 25 | if os.getenv(var) is None: |
| 26 | missing_vars.append(var) |
| 27 | |
| 28 | has_vars = len(missing_vars) == 0 |
| 29 | |
| 30 | return skipUnless(has_vars, f"Missing env var(s): {', '.join(missing_vars)}") |
| 31 | |
| 32 | def skipIfMissingCommands(*args): |
| 33 | ''' |
| 34 | Decorator to skip execution of a test if the list |
| 35 | of command binaries is not available in $PATH. |
| 36 | Example: |
| 37 | |
| 38 | @skipIfMissingCommands("mkisofs", "losetup") |
| 39 | ''' |
| 40 | has_cmds = True |
| 41 | for cmd in args: |
| 42 | if not which(cmd): |
| 43 | has_cmds = False |
| 44 | break |
| 45 | |
| 46 | return skipUnless(has_cmds, 'required command(s) "%s" not installed' % |
| 47 | ", ".join(args)) |
| 48 | |
| 49 | def skipIfOperatingSystem(*args): |
| 50 | ''' |
| 51 | Decorator to skip execution of a test if the current host |
| 52 | operating system does match one of the prohibited ones. |
| 53 | Example: |
| 54 | |
| 55 | @skipIfOperatingSystem("Linux", "Darwin") |
| 56 | ''' |
| 57 | return skipIf(platform.system() in args, |
| 58 | 'running on an OS (%s) that is not able to run this test' % |
| 59 | ", ".join(args)) |
| 60 | |
| 61 | def skipUnlessOperatingSystem(*args): |
| 62 | ''' |
| 63 | Decorator to skip execution of a test if the current host |
| 64 | operating system does not match one of the allowed ones. |
| 65 | Example: |
| 66 | |
| 67 | @skipUnlessOperatingSystem("Linux", "Darwin") |
| 68 | ''' |
| 69 | return skipUnless(platform.system() in args, |
| 70 | 'not running on one of the required operating systems (%s)' % |
| 71 | ", ".join(args)) |
| 72 | |
| 73 | def skipIfNotMachine(*args): |
| 74 | ''' |
| 75 | Decorator to skip execution of a test if the current |
| 76 | host machine does not match one of the permitted machines. |
| 77 | Example: |
| 78 | |
| 79 | @skipIfNotMachine("x86_64", "aarch64") |
| 80 | ''' |
| 81 | return skipUnless(platform.machine() in args, |
| 82 | 'not running on one of the required machine(s) "%s"' % |
| 83 | ", ".join(args)) |
| 84 | |
| 85 | def skipFlakyTest(bug_url): |
| 86 | ''' |
| 87 | Decorator to skip execution of flaky tests, unless |
| 88 | the $QEMU_TEST_FLAKY_TESTS environment variable is set. |
| 89 | A bug URL must be provided that documents the observed |
| 90 | failure behaviour, so it can be tracked & re-evaluated |
| 91 | in future. |
| 92 | |
| 93 | Historical tests may be providing "None" as the bug_url |
| 94 | but this should not be done for new test. |
| 95 | |
| 96 | Example: |
| 97 | |
| 98 | @skipFlakyTest("https://gitlab.com/qemu-project/qemu/-/issues/NNN") |
| 99 | ''' |
| 100 | if bug_url is None: |
| 101 | bug_url = "FIXME: reproduce flaky test and file bug report or remove" |
| 102 | return skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), |
| 103 | f'Test is unstable: {bug_url}') |
| 104 | |
| 105 | def skipUntrustedTest(): |
| 106 | ''' |
| 107 | Decorator to skip execution of tests which are likely |
| 108 | to execute untrusted commands on the host, or commands |
| 109 | which process untrusted code, unless the |
| 110 | $QEMU_TEST_ALLOW_UNTRUSTED_CODE env var is set. |
| 111 | Example: |
| 112 | |
| 113 | @skipUntrustedTest() |
| 114 | ''' |
| 115 | return skipUnless(os.getenv('QEMU_TEST_ALLOW_UNTRUSTED_CODE'), |
| 116 | 'Test runs untrusted code / processes untrusted data') |
| 117 | |
| 118 | def skipBigDataTest(): |
| 119 | ''' |
| 120 | Decorator to skip execution of tests which need large |
| 121 | data storage (over around 500MB-1GB mark) on the host, |
| 122 | unless the $QEMU_TEST_ALLOW_LARGE_STORAGE environment |
| 123 | variable is set |
| 124 | |
| 125 | Example: |
| 126 | |
| 127 | @skipBigDataTest() |
| 128 | ''' |
| 129 | return skipUnless(os.getenv('QEMU_TEST_ALLOW_LARGE_STORAGE'), |
| 130 | 'Test requires large host storage space') |
| 131 | |
| 132 | def skipSlowTest(): |
| 133 | ''' |
| 134 | Decorator to skip execution of tests which have a really long |
| 135 | runtime (and might e.g. time out if QEMU has been compiled with |
| 136 | debugging enabled) unless the $QEMU_TEST_ALLOW_SLOW |
| 137 | environment variable is set |
| 138 | |
| 139 | Example: |
| 140 | |
| 141 | @skipSlowTest() |
| 142 | ''' |
| 143 | return skipUnless(os.getenv('QEMU_TEST_ALLOW_SLOW'), |
| 144 | 'Test has a very long runtime and might time out') |
| 145 | |
| 146 | def skipIfMissingImports(*args): |
| 147 | ''' |
| 148 | Decorator to skip execution of a test if the list |
| 149 | of python imports is not available. |
| 150 | Example: |
| 151 | |
| 152 | @skipIfMissingImports("numpy", "cv2") |
| 153 | ''' |
| 154 | has_imports = True |
| 155 | for impname in args: |
| 156 | try: |
| 157 | importlib.import_module(impname) |
| 158 | except ImportError: |
| 159 | has_imports = False |
| 160 | break |
| 161 | |
| 162 | return skipUnless(has_imports, 'required import(s) "%s" not installed' % |
| 163 | ", ".join(args)) |
| 164 | |
| 165 | def skipLockedMemoryTest(locked_memory): |
| 166 | ''' |
| 167 | Decorator to skip execution of a test if the system's |
| 168 | locked memory limit is below the required threshold. |
| 169 | Takes required locked memory threshold in kB. |
| 170 | Example: |
| 171 | |
| 172 | @skipLockedMemoryTest(2_097_152) |
| 173 | ''' |
| 174 | # get memlock hard limit in bytes |
| 175 | _, ulimit_memory = resource.getrlimit(resource.RLIMIT_MEMLOCK) |
| 176 | |
| 177 | return skipUnless( |
| 178 | ulimit_memory == resource.RLIM_INFINITY or ulimit_memory >= locked_memory * 1024, |
| 179 | f'Test required {locked_memory} kB of available locked memory', |
| 180 | ) |
| 181 | |
| 182 | ''' |
| 183 | Decorator to skip execution of a test if passwordless |
| 184 | sudo command is not available. |
| 185 | ''' |
| 186 | def skipWithoutSudo(): |
| 187 | proc = subprocess.run(["sudo", "-n", "/bin/true"], |
| 188 | stdin=subprocess.PIPE, |
| 189 | stdout=subprocess.PIPE, |
| 190 | stderr=subprocess.STDOUT, |
| 191 | universal_newlines=True, |
| 192 | check=False) |
| 193 | |
| 194 | return skipUnless(proc.returncode == 0, |
| 195 | f'requires password-less sudo access: {proc.stdout}') |