tests: support multiple filter expressions
[vpp.git] / test / config.py
1 import argparse
2 import os
3 import psutil
4 import time
5
6
7 def positive_int_or_default(default):
8     def positive_integer(v):
9         if v is None or v == "":
10             return default
11         if int(v) <= 0:
12             raise ValueError("value must be positive")
13         return int(v)
14
15     return positive_integer
16
17
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
26     return positive_float
27
28
29 def positive_int_or_auto(v):
30     if v is None or v in ("", "auto"):
31         return "auto"
32     if int(v) <= 0:
33         raise ValueError("value must be positive or auto")
34     return int(v)
35
36
37 def int_or_auto(v):
38     if v is None or v in ("", "auto"):
39         return "auto"
40     if int(v) < 0:
41         raise ValueError("value must be positive or auto")
42     return int(v)
43
44
45 def int_choice_or_default(options, default):
46     assert default in options
47
48     def choice(v):
49         if v is None or v == "":
50             return default
51         if int(v) in options:
52             return int(v)
53         raise ValueError("invalid choice")
54
55     return choice
56
57
58 def worker_config(v):
59     if v is None or v == "":
60         return 0
61     if v.startswith("workers "):
62         return int(v.split(" ")[1])
63     return int(v)
64
65
66 def directory(v):
67     if not os.path.isdir(v):
68         raise ValueError(f"provided path '{v}' doesn't exist or is not a directory")
69     return v
70
71
72 def directory_verify_or_create(v):
73     if not os.path.isdir(v):
74         os.mkdir(v)
75     return v
76
77
78 parser = argparse.ArgumentParser(
79     description="VPP unit tests", formatter_class=argparse.RawTextHelpFormatter
80 )
81
82 parser.add_argument(
83     "--failfast", action="store_true", help="stop running tests on first failure"
84 )
85
86 parser.add_argument(
87     "--test-src-dir",
88     action="append",
89     type=directory,
90     help="directory containing test files "
91     "(may be specified multiple times) "
92     "(VPP_WS_DIR/test is added automatically to the set)",
93 )
94
95 default_verbose = 0
96
97 parser.add_argument(
98     "--verbose",
99     action="store",
100     default=default_verbose,
101     type=int_choice_or_default((0, 1, 2), default_verbose),
102     help="verbosity setting - 0 - least verbose, 2 - most verbose (default: 0)",
103 )
104
105 default_test_run_timeout = 600
106
107 parser.add_argument(
108     "--timeout",
109     action="store",
110     type=positive_int_or_default(default_test_run_timeout),
111     default=default_test_run_timeout,
112     metavar="TEST_RUN_TIMEOUT",
113     help="test run timeout in seconds - per test "
114     f"(default: {default_test_run_timeout})",
115 )
116
117 parser.add_argument(
118     "--failed-dir",
119     action="store",
120     type=directory,
121     help="directory containing failed tests (default: --tmp-dir)",
122 )
123
124 filter_help_string = """\
125 expression consists of one or more filters separated by commas (',')
126 filter consists of 3 string selectors separated by dots ('.')
127
128     <file>.<class>.<function>
129
130 - selectors restrict which files/classes/functions are run
131 - selector can be replaced with '*' or omitted entirely if not needed
132 - <file> selector is automatically prepended with 'test_' if required
133 - '.' separators are required only if selector(s) follow(s)
134
135 examples:
136
137 1. all of the following expressions are equivalent and will select
138    all test classes and functions from test_bfd.py:
139    'test_bfd' 'bfd' 'test_bfd..' 'bfd.' 'bfd.*.*' 'test_bfd.*.*'
140 2. 'bfd.BFDAPITestCase' selects all tests from test_bfd.py,
141    which are part of BFDAPITestCase class
142 3. 'bfd.BFDAPITestCase.test_add_bfd' selects a single test named
143    test_add_bfd from test_bfd.py/BFDAPITestCase
144 4. '.*.test_add_bfd' selects all test functions named test_add_bfd
145    from all files/classes
146 5. 'bfd,ip4,..test_icmp_error' selects all test functions in test_bfd.py,
147    test_ip4.py and all test functions named 'test_icmp_error' in all files
148 """
149 parser.add_argument(
150     "--filter", action="store", metavar="FILTER_EXPRESSION", help=filter_help_string
151 )
152
153 default_retries = 0
154
155 parser.add_argument(
156     "--retries",
157     action="store",
158     default=default_retries,
159     type=positive_int_or_default(default_retries),
160     help="retry failed tests RETRIES times",
161 )
162
163 parser.add_argument(
164     "--step", action="store_true", default=False, help="enable stepping through tests"
165 )
166
167 debug_help_string = """\
168 attach     - attach to already running vpp
169 core       - detect coredump and load core in gdb on crash
170 gdb        - print VPP PID and pause allowing attaching gdb
171 gdbserver  - same as above, but run gdb in gdbserver
172 """
173
174 parser.add_argument(
175     "--debug",
176     action="store",
177     choices=["attach", "core", "gdb", "gdbserver"],
178     help=debug_help_string,
179 )
180
181 parser.add_argument(
182     "--debug-framework",
183     action="store_true",
184     help="enable internal test framework debugging",
185 )
186
187 parser.add_argument(
188     "--compress-core",
189     action="store_true",
190     help="compress core files if not debugging them",
191 )
192
193 parser.add_argument("--extended", action="store_true", help="run extended tests")
194
195 parser.add_argument(
196     "--sanity", action="store_true", help="perform sanity vpp run before running tests"
197 )
198
199 parser.add_argument(
200     "--force-foreground",
201     action="store_true",
202     help="force running in foreground - don't fork",
203 )
204
205 parser.add_argument(
206     "--jobs",
207     action="store",
208     type=positive_int_or_auto,
209     default="auto",
210     help="maximum concurrent test jobs",
211 )
212
213 parser.add_argument(
214     "--venv-dir", action="store", type=directory, help="path to virtual environment"
215 )
216
217 default_rnd_seed = time.time()
218 parser.add_argument(
219     "--rnd-seed",
220     action="store",
221     default=default_rnd_seed,
222     type=positive_float_or_default(default_rnd_seed),
223     help="random generator seed (default: current time)",
224 )
225
226 parser.add_argument(
227     "--vpp-worker-count",
228     action="store",
229     type=worker_config,
230     default=0,
231     help="number of vpp workers",
232 )
233
234 parser.add_argument(
235     "--gcov", action="store_true", default=False, help="running gcov tests"
236 )
237
238 parser.add_argument(
239     "--cache-vpp-output",
240     action="store_true",
241     default=False,
242     help="cache VPP stdout/stderr and log as one block after test finishes",
243 )
244
245 parser.add_argument(
246     "--vpp-ws-dir",
247     action="store",
248     required=True,
249     type=directory,
250     help="vpp workspace directory",
251 )
252
253 parser.add_argument(
254     "--vpp-tag",
255     action="store",
256     default="vpp_debug",
257     metavar="VPP_TAG",
258     required=True,
259     help="vpp tag (e.g. vpp, vpp_debug, vpp_gcov)",
260 )
261
262 parser.add_argument(
263     "--vpp",
264     action="store",
265     help="path to vpp binary (default: derive from VPP_WS_DIR and VPP_TAG)",
266 )
267
268 parser.add_argument(
269     "--vpp-install-dir",
270     type=directory,
271     action="store",
272     help="path to vpp install directory"
273     "(default: derive from VPP_WS_DIR and VPP_TAG)",
274 )
275
276 parser.add_argument(
277     "--vpp-build-dir",
278     action="store",
279     type=directory,
280     help="vpp build directory (default: derive from VPP_WS_DIR and VPP_TAG)",
281 )
282
283 parser.add_argument(
284     "--vpp-plugin-dir",
285     action="append",
286     type=directory,
287     help="directory containing vpp plugins"
288     "(default: derive from VPP_WS_DIR and VPP_TAG)",
289 )
290
291 parser.add_argument(
292     "--vpp-test-plugin-dir",
293     action="append",
294     type=directory,
295     help="directory containing vpp api test plugins"
296     "(default: derive from VPP_WS_DIR and VPP_TAG)",
297 )
298
299 parser.add_argument(
300     "--extern-plugin-dir",
301     action="append",
302     type=directory,
303     default=[],
304     help="directory containing external plugins",
305 )
306
307 parser.add_argument(
308     "--extern-apidir",
309     action="append",
310     type=directory,
311     default=[],
312     help="directory to look for API JSON files",
313 )
314
315 parser.add_argument(
316     "--coredump-size",
317     action="store",
318     default="unlimited",
319     help="specify vpp coredump size",
320 )
321
322 parser.add_argument(
323     "--max-vpp-cpus",
324     action="store",
325     type=int_or_auto,
326     default=0,
327     help="max cpus used by vpp",
328 )
329
330 variant_help_string = """\
331 specify which march node variant to unit test
332   e.g. --variant=skx - test the skx march variants
333   e.g. --variant=icl - test the icl march variants
334 """
335
336 parser.add_argument("--variant", action="store", help=variant_help_string)
337
338 parser.add_argument(
339     "--api-fuzz", action="store", default=None, help="specify api fuzzing parameters"
340 )
341
342 parser.add_argument(
343     "--wipe-tmp-dir",
344     action="store_true",
345     default=True,
346     help="remove test tmp directory before running test",
347 )
348
349 parser.add_argument(
350     "--tmp-dir",
351     action="store",
352     default="/tmp",
353     type=directory_verify_or_create,
354     help="directory where to store test temporary directories",
355 )
356
357 parser.add_argument(
358     "--log-dir",
359     action="store",
360     type=directory_verify_or_create,
361     help="directory where to store directories "
362     "containing log files (default: --tmp-dir)",
363 )
364
365 default_keep_pcaps = False
366 parser.add_argument(
367     "--keep-pcaps",
368     action="store_true",
369     default=default_keep_pcaps,
370     help=f"if set, keep all pcap files from a test run (default: {default_keep_pcaps})",
371 )
372
373 parser.add_argument(
374     "-r",
375     "--use-running-vpp",
376     dest="running_vpp",
377     required=False,
378     action="store_true",
379     default=False,
380     help="Runs tests against a running VPP.",
381 )
382
383 parser.add_argument(
384     "-d",
385     "--socket-dir",
386     dest="socket_dir",
387     required=False,
388     action="store",
389     default="",
390     help="Relative or absolute path to running VPP's socket directory.\n"
391     "The directory must contain VPP's socket files:api.sock & stats.sock.\n"
392     "Default: /var/run/vpp if VPP is started as the root user, else "
393     "/var/run/user/${uid}/vpp.",
394 )
395
396 config = parser.parse_args()
397
398 ws = config.vpp_ws_dir
399 br = f"{ws}/build-root"
400 tag = config.vpp_tag
401
402 if config.vpp_install_dir is None:
403     config.vpp_install_dir = f"{br}/install-{tag}-native"
404
405 if config.vpp is None:
406     config.vpp = f"{config.vpp_install_dir}/vpp/bin/vpp"
407
408 if config.vpp_build_dir is None:
409     config.vpp_build_dir = f"{br}/build-{tag}-native"
410
411 libs = ["lib", "lib64"]
412
413 if config.vpp_plugin_dir is None:
414     config.vpp_plugin_dir = [
415         f"{config.vpp_install_dir}/vpp/{lib}/vpp_plugins" for lib in libs
416     ]
417
418 if config.vpp_test_plugin_dir is None:
419     config.vpp_test_plugin_dir = [
420         f"{config.vpp_install_dir}/vpp/{lib}/vpp_api_test_plugins" for lib in libs
421     ]
422
423 test_dirs = [f"{ws}/test"]
424
425 if config.test_src_dir is not None:
426     test_dirs.extend(config.test_src_dir)
427
428 config.test_src_dir = test_dirs
429
430
431 if config.venv_dir is None:
432     config.venv_dir = f"{ws}/build-root/test/venv"
433
434 if config.failed_dir is None:
435     config.failed_dir = f"{config.tmp_dir}"
436
437 available_cpus = psutil.Process().cpu_affinity()
438 num_cpus = len(available_cpus)
439
440 if config.max_vpp_cpus == "auto":
441     max_vpp_cpus = num_cpus
442 elif config.max_vpp_cpus > 0:
443     max_vpp_cpus = min(config.max_vpp_cpus, num_cpus)
444 else:
445     max_vpp_cpus = num_cpus
446
447 if __name__ == "__main__":
448     print("Provided arguments:")
449     for i in config.__dict__:
450         print(f"  {i} is {config.__dict__[i]}")