Klement Sekera | b23ffd7 | 2021-05-31 16:08:53 +0200 | [diff] [blame] | 1 | import argparse |
| 2 | import os |
| 3 | import psutil |
| 4 | import textwrap |
| 5 | import time |
| 6 | |
| 7 | |
| 8 | def positive_int_or_default(default): |
| 9 | def positive_integer(v): |
| 10 | if v is None or v == "": |
| 11 | return default |
Andrew Yourtchenko | f56b007 | 2022-03-18 17:05:53 +0000 | [diff] [blame] | 12 | if int(v) <= 0: |
| 13 | raise ValueError("value must be positive") |
Klement Sekera | b23ffd7 | 2021-05-31 16:08:53 +0200 | [diff] [blame] | 14 | return int(v) |
| 15 | return positive_integer |
| 16 | |
| 17 | |
Andrew Yourtchenko | f56b007 | 2022-03-18 17:05:53 +0000 | [diff] [blame] | 18 | def positive_float_or_default(default): |
| 19 | def positive_float(v): |
| 20 | if v is None or v == "": |
| 21 | return default |
| 22 | if float(v) <= 0: |
| 23 | raise ValueError("value must be positive") |
| 24 | return float(v) |
| 25 | return positive_float |
| 26 | |
| 27 | |
Klement Sekera | b23ffd7 | 2021-05-31 16:08:53 +0200 | [diff] [blame] | 28 | def positive_int_or_auto(v): |
| 29 | if v is None or v in ("", "auto"): |
| 30 | return "auto" |
| 31 | if int(v) <= 0: |
| 32 | raise ValueError("value must be positive or auto") |
| 33 | return int(v) |
| 34 | |
| 35 | |
| 36 | def int_or_auto(v): |
| 37 | if v is None or v in ("", "auto"): |
| 38 | return "auto" |
| 39 | if int(v) < 0: |
| 40 | raise ValueError("value must be positive or auto") |
| 41 | return int(v) |
| 42 | |
| 43 | |
| 44 | def int_choice_or_default(options, default): |
| 45 | assert default in options |
| 46 | |
| 47 | def choice(v): |
| 48 | if v is None or v == "": |
| 49 | return default |
| 50 | if int(v) in options: |
| 51 | return int(v) |
| 52 | raise ValueError("invalid choice") |
| 53 | return choice |
| 54 | |
| 55 | |
| 56 | def worker_config(v): |
| 57 | if v is None or v == "": |
| 58 | return 0 |
| 59 | if v.startswith("workers "): |
| 60 | return(int(v.split(" ")[1])) |
| 61 | return int(v) |
| 62 | |
| 63 | |
| 64 | def directory(v): |
| 65 | if not os.path.isdir(v): |
| 66 | raise ValueError(f"provided path '{v}' doesn't exist " |
| 67 | "or is not a directory") |
| 68 | return v |
| 69 | |
| 70 | |
| 71 | def directory_verify_or_create(v): |
| 72 | if not os.path.isdir(v): |
| 73 | os.mkdir(v) |
| 74 | return v |
| 75 | |
| 76 | |
| 77 | parser = argparse.ArgumentParser(description="VPP unit tests", |
| 78 | formatter_class=argparse.RawTextHelpFormatter) |
| 79 | |
| 80 | parser.add_argument("--failfast", action="store_true", |
| 81 | help="stop running tests on first failure") |
| 82 | |
| 83 | parser.add_argument("--test-src-dir", action="append", type=directory, |
| 84 | help="directory containing test files " |
| 85 | "(may be specified multiple times) " |
| 86 | "(VPP_WS_DIR/test is added automatically to the set)") |
| 87 | |
| 88 | default_verbose = 0 |
| 89 | |
| 90 | parser.add_argument("--verbose", action="store", default=default_verbose, |
| 91 | type=int_choice_or_default((0, 1, 2), default_verbose), |
| 92 | help="verbosity setting - 0 - least verbose, " |
| 93 | "2 - most verbose (default: 0)") |
| 94 | |
| 95 | default_test_run_timeout = 600 |
| 96 | |
| 97 | parser.add_argument("--timeout", action="store", |
| 98 | type=positive_int_or_default(default_test_run_timeout), |
| 99 | default=default_test_run_timeout, |
| 100 | metavar="TEST_RUN_TIMEOUT", |
| 101 | help="test run timeout in seconds - per test " |
| 102 | f"(default: {default_test_run_timeout})") |
| 103 | |
| 104 | parser.add_argument("--failed-dir", action="store", type=directory, |
| 105 | help="directory containing failed tests") |
| 106 | |
| 107 | filter_help_string = """\ |
| 108 | expression consists of 3 string selectors separated by '.' separators: |
| 109 | |
| 110 | <file>.<class>.<function> |
| 111 | |
| 112 | - selectors restrict which files/classes/functions are run |
| 113 | - selector can be replaced with '*' or omitted entirely if not needed |
| 114 | - <file> selector is automatically prepended with 'test_' if required |
| 115 | - '.' separators are required only if selector(s) follow(s) |
| 116 | |
| 117 | examples: |
| 118 | |
| 119 | 1. all of the following expressions are equivalent and will select |
| 120 | all test classes and functions from test_bfd.py: |
| 121 | 'test_bfd' 'bfd' 'test_bfd..' 'bfd.' 'bfd.*.*' 'test_bfd.*.*' |
| 122 | 2. 'bfd.BFDAPITestCase' selects all tests from test_bfd.py, |
| 123 | which are part of BFDAPITestCase class |
| 124 | 3. 'bfd.BFDAPITestCase.test_add_bfd' selects a single test named |
| 125 | test_add_bfd from test_bfd.py/BFDAPITestCase |
| 126 | 4. '.*.test_add_bfd' selects all test functions named test_add_bfd |
| 127 | from all files/classes |
| 128 | """ |
| 129 | parser.add_argument("--filter", action="store", |
| 130 | metavar="FILTER_EXPRESSION", help=filter_help_string) |
| 131 | |
| 132 | default_retries = 0 |
| 133 | |
| 134 | parser.add_argument("--retries", action="store", default=default_retries, |
| 135 | type=positive_int_or_default(default_retries), |
| 136 | help="retry failed tests RETRIES times") |
| 137 | |
| 138 | parser.add_argument("--step", action="store_true", default=False, |
| 139 | help="enable stepping through tests") |
| 140 | |
| 141 | debug_help_string = """\ |
| 142 | attach - attach to already running vpp |
| 143 | core - detect coredump and load core in gdb on crash |
| 144 | gdb - print VPP PID and pause allowing attaching gdb |
| 145 | gdbserver - same as above, but run gdb in gdbserver |
| 146 | """ |
| 147 | |
| 148 | parser.add_argument("--debug", action="store", |
| 149 | choices=["attach", "core", "gdb", "gdbserver"], |
| 150 | help=debug_help_string) |
| 151 | |
| 152 | parser.add_argument("--debug-framework", action="store_true", |
| 153 | help="enable internal test framework debugging") |
| 154 | |
| 155 | parser.add_argument("--compress-core", action="store_true", |
| 156 | help="compress core files if not debugging them") |
| 157 | |
| 158 | parser.add_argument("--extended", action="store_true", |
| 159 | help="run extended tests") |
| 160 | |
| 161 | parser.add_argument("--sanity", action="store_true", |
| 162 | help="perform sanity vpp run before running tests") |
| 163 | |
| 164 | parser.add_argument("--force-foreground", action="store_true", |
| 165 | help="force running in foreground - don't fork") |
| 166 | |
| 167 | parser.add_argument("--jobs", action="store", type=positive_int_or_auto, |
| 168 | default="auto", help="maximum concurrent test jobs") |
| 169 | |
| 170 | parser.add_argument("--venv-dir", action="store", |
| 171 | type=directory, help="path to virtual environment") |
| 172 | |
| 173 | default_rnd_seed = time.time() |
| 174 | parser.add_argument("--rnd-seed", action="store", default=default_rnd_seed, |
Andrew Yourtchenko | f56b007 | 2022-03-18 17:05:53 +0000 | [diff] [blame] | 175 | type=positive_float_or_default(default_rnd_seed), |
Klement Sekera | b23ffd7 | 2021-05-31 16:08:53 +0200 | [diff] [blame] | 176 | help="random generator seed (default: current time)") |
| 177 | |
| 178 | parser.add_argument("--vpp-worker-count", action="store", type=worker_config, |
| 179 | default=0, help="number of vpp workers") |
| 180 | |
| 181 | parser.add_argument("--gcov", action="store_true", |
| 182 | default=False, help="running gcov tests") |
| 183 | |
| 184 | parser.add_argument("--cache-vpp-output", action="store_true", default=False, |
| 185 | help="cache VPP stdout/stderr and log as one block " |
| 186 | "after test finishes") |
| 187 | |
| 188 | parser.add_argument("--vpp-ws-dir", action="store", required=True, |
| 189 | type=directory, help="vpp workspace directory") |
| 190 | |
| 191 | parser.add_argument("--vpp-tag", action="store", default="vpp_debug", |
| 192 | metavar="VPP_TAG", required=True, |
| 193 | help="vpp tag (e.g. vpp, vpp_debug, vpp_gcov)") |
| 194 | |
| 195 | parser.add_argument("--vpp", action="store", help="path to vpp binary " |
| 196 | "(default: derive from VPP_WS_DIR and VPP_TAG)") |
| 197 | |
| 198 | parser.add_argument("--vpp-install-dir", type=directory, |
| 199 | action="store", help="path to vpp install directory" |
| 200 | "(default: derive from VPP_WS_DIR and VPP_TAG)") |
| 201 | |
| 202 | parser.add_argument("--vpp-build-dir", action="store", type=directory, |
| 203 | help="vpp build directory" |
| 204 | "(default: derive from VPP_WS_DIR and VPP_TAG)") |
| 205 | |
| 206 | parser.add_argument("--vpp-plugin-dir", action="append", type=directory, |
| 207 | help="directory containing vpp plugins" |
| 208 | "(default: derive from VPP_WS_DIR and VPP_TAG)") |
| 209 | |
| 210 | parser.add_argument("--vpp-test-plugin-dir", action="append", type=directory, |
| 211 | help="directory containing vpp api test plugins" |
| 212 | "(default: derive from VPP_WS_DIR and VPP_TAG)") |
| 213 | |
| 214 | parser.add_argument("--extern-plugin-dir", action="append", type=directory, |
| 215 | default=[], help="directory containing external plugins") |
| 216 | |
| 217 | parser.add_argument("--coredump-size", action="store", default="unlimited", |
| 218 | help="specify vpp coredump size") |
| 219 | |
| 220 | parser.add_argument("--max-vpp-cpus", action="store", type=int_or_auto, |
| 221 | default=0, help="max cpus used by vpp") |
| 222 | |
| 223 | variant_help_string = """\ |
| 224 | specify which march node variant to unit test |
| 225 | e.g. --variant=skx - test the skx march variants |
| 226 | e.g. --variant=icl - test the icl march variants |
| 227 | """ |
| 228 | |
| 229 | parser.add_argument("--variant", action="store", help=variant_help_string) |
| 230 | |
| 231 | parser.add_argument("--api-fuzz", action="store", default=None, |
| 232 | help="specify api fuzzing parameters") |
| 233 | |
| 234 | parser.add_argument("--wipe-tmp-dir", action="store_true", default=True, |
| 235 | help="remove test tmp directory before running test") |
| 236 | |
| 237 | parser.add_argument("--tmp-dir", action="store", default="/tmp", |
| 238 | type=directory_verify_or_create, |
| 239 | help="directory where to store test temporary directories") |
| 240 | |
| 241 | parser.add_argument("--log-dir", action="store", |
| 242 | type=directory_verify_or_create, |
| 243 | help="directory where to store directories " |
| 244 | "containing log files (default: --tmp-dir)") |
| 245 | |
| 246 | default_keep_pcaps = False |
| 247 | parser.add_argument("--keep-pcaps", action="store_true", |
| 248 | default=default_keep_pcaps, |
| 249 | help="if set, keep all pcap files from a test run" |
| 250 | f" (default: {default_keep_pcaps})") |
| 251 | |
| 252 | config = parser.parse_args() |
| 253 | |
| 254 | ws = config.vpp_ws_dir |
| 255 | br = f"{ws}/build-root" |
| 256 | tag = config.vpp_tag |
| 257 | |
| 258 | if config.vpp_install_dir is None: |
| 259 | config.vpp_install_dir = f"{br}/install-{tag}-native" |
| 260 | |
| 261 | if config.vpp is None: |
| 262 | config.vpp = f"{config.vpp_install_dir}/vpp/bin/vpp" |
| 263 | |
| 264 | if config.vpp_build_dir is None: |
| 265 | config.vpp_build_dir = f"{br}/build-{tag}-native" |
| 266 | |
| 267 | libs = ["lib", "lib64"] |
| 268 | |
| 269 | if config.vpp_plugin_dir is None: |
| 270 | config.vpp_plugin_dir = [ |
| 271 | f"{config.vpp_install_dir}/vpp/{lib}/vpp_plugins" for lib in libs] |
| 272 | |
| 273 | if config.vpp_test_plugin_dir is None: |
| 274 | config.vpp_test_plugin_dir = [ |
| 275 | f"{config.vpp_install_dir}/vpp/{lib}/vpp_api_test_plugins" |
| 276 | for lib in libs] |
| 277 | |
| 278 | test_dirs = [f"{ws}/test"] |
| 279 | |
| 280 | if config.test_src_dir is not None: |
| 281 | test_dirs.extend(config.test_src_dir) |
| 282 | |
| 283 | config.test_src_dir = test_dirs |
| 284 | |
| 285 | |
| 286 | if config.venv_dir is None: |
| 287 | config.venv_dir = f"{ws}/test/venv" |
| 288 | |
| 289 | available_cpus = psutil.Process().cpu_affinity() |
| 290 | num_cpus = len(available_cpus) |
| 291 | |
| 292 | if config.max_vpp_cpus == 'auto': |
| 293 | max_vpp_cpus = num_cpus |
| 294 | elif config.max_vpp_cpus > 0: |
| 295 | max_vpp_cpus = min(config.max_vpp_cpus, num_cpus) |
| 296 | else: |
| 297 | max_vpp_cpus = num_cpus |
| 298 | |
| 299 | if __name__ == "__main__": |
| 300 | print("Provided arguments:") |
| 301 | for i in config.__dict__: |
| 302 | print(f" {i} is {config.__dict__[i]}") |