blob: 1990cd21cabfa2eac36b4f720d552c1cd5404eb8 [file] [log] [blame]
Tom Rini0344c602024-10-08 13:56:50 -06001#!/usr/bin/env python3
2
3# Copyright The Mbed TLS Contributors
4# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
5
6"""
7Test Mbed TLS with a subset of algorithms.
8
9This script can be divided into several steps:
10
11First, include/mbedtls/mbedtls_config.h or a different config file passed
12in the arguments is parsed to extract any configuration options (using config.py).
13
14Then, test domains (groups of jobs, tests) are built based on predefined data
15collected in the DomainData class. Here, each domain has five major traits:
16- domain name, can be used to run only specific tests via command-line;
17- configuration building method, described in detail below;
18- list of symbols passed to the configuration building method;
19- commands to be run on each job (only build, build and test, or any other custom);
20- optional list of symbols to be excluded from testing.
21
22The configuration building method can be one of the three following:
23
24- ComplementaryDomain - build a job for each passed symbol by disabling a single
25 symbol and its reverse dependencies (defined in REVERSE_DEPENDENCIES);
26
27- ExclusiveDomain - build a job where, for each passed symbol, only this particular
28 one is defined and other symbols from the list are unset. For each job look for
29 any non-standard symbols to set/unset in EXCLUSIVE_GROUPS. These are usually not
30 direct dependencies, but rather non-trivial results of other configs missing. Then
31 look for any unset symbols and handle their reverse dependencies.
32 Examples of EXCLUSIVE_GROUPS usage:
33 - MBEDTLS_SHA512_C job turns off all hashes except SHA512. MBEDTLS_SSL_COOKIE_C
34 requires either SHA256 or SHA384 to work, so it also has to be disabled.
35 This is not a dependency on SHA512_C, but a result of an exclusive domain
36 config building method. Relevant field:
37 'MBEDTLS_SHA512_C': ['-MBEDTLS_SSL_COOKIE_C'],
38
39- DualDomain - combination of the two above - both complementary and exclusive domain
40 job generation code will be run. Currently only used for hashes.
41
42Lastly, the collected jobs are executed and (optionally) tested, with
43error reporting and coloring as configured in options. Each test starts with
44a full config without a couple of slowing down or unnecessary options
45(see set_reference_config), then the specific job config is derived.
46"""
47import argparse
48import os
49import re
50import shutil
51import subprocess
52import sys
53import traceback
54from typing import Union
55
56# Add the Mbed TLS Python library directory to the module search path
57import scripts_path # pylint: disable=unused-import
58import config
59
60class Colors: # pylint: disable=too-few-public-methods
61 """Minimalistic support for colored output.
62Each field of an object of this class is either None if colored output
63is not possible or not desired, or a pair of strings (start, stop) such
64that outputting start switches the text color to the desired color and
65stop switches the text color back to the default."""
66 red = None
67 green = None
68 cyan = None
69 bold_red = None
70 bold_green = None
71 def __init__(self, options=None):
72 """Initialize color profile according to passed options."""
73 if not options or options.color in ['no', 'never']:
74 want_color = False
75 elif options.color in ['yes', 'always']:
76 want_color = True
77 else:
78 want_color = sys.stderr.isatty()
79 if want_color:
80 # Assume ANSI compatible terminal
81 normal = '\033[0m'
82 self.red = ('\033[31m', normal)
83 self.green = ('\033[32m', normal)
84 self.cyan = ('\033[36m', normal)
85 self.bold_red = ('\033[1;31m', normal)
86 self.bold_green = ('\033[1;32m', normal)
87NO_COLORS = Colors(None)
88
89def log_line(text, prefix='depends.py:', suffix='', color=None):
90 """Print a status message."""
91 if color is not None:
92 prefix = color[0] + prefix
93 suffix = suffix + color[1]
94 sys.stderr.write(prefix + ' ' + text + suffix + '\n')
95 sys.stderr.flush()
96
97def log_command(cmd):
98 """Print a trace of the specified command.
99cmd is a list of strings: a command name and its arguments."""
100 log_line(' '.join(cmd), prefix='+')
101
102def backup_config(options):
103 """Back up the library configuration file (mbedtls_config.h).
104If the backup file already exists, it is presumed to be the desired backup,
105so don't make another backup."""
106 if os.path.exists(options.config_backup):
107 options.own_backup = False
108 else:
109 options.own_backup = True
110 shutil.copy(options.config, options.config_backup)
111
112def restore_config(options):
113 """Restore the library configuration file (mbedtls_config.h).
114Remove the backup file if it was saved earlier."""
115 if options.own_backup:
116 shutil.move(options.config_backup, options.config)
117 else:
118 shutil.copy(options.config_backup, options.config)
119
120def option_exists(conf, option):
121 return option in conf.settings
122
123def set_config_option_value(conf, option, colors, value: Union[bool, str]):
124 """Set/unset a configuration option, optionally specifying a value.
125value can be either True/False (set/unset config option), or a string,
126which will make a symbol defined with a certain value."""
127 if not option_exists(conf, option):
128 log_line('Symbol {} was not found in {}'.format(option, conf.filename), color=colors.red)
129 return False
130
131 if value is False:
132 log_command(['config.py', 'unset', option])
133 conf.unset(option)
134 elif value is True:
135 log_command(['config.py', 'set', option])
136 conf.set(option)
137 else:
138 log_command(['config.py', 'set', option, value])
139 conf.set(option, value)
140 return True
141
142def set_reference_config(conf, options, colors):
143 """Change the library configuration file (mbedtls_config.h) to the reference state.
144The reference state is the one from which the tested configurations are
145derived."""
146 # Turn off options that are not relevant to the tests and slow them down.
147 log_command(['config.py', 'full'])
148 conf.adapt(config.full_adapter)
149 set_config_option_value(conf, 'MBEDTLS_TEST_HOOKS', colors, False)
150 set_config_option_value(conf, 'MBEDTLS_PSA_CRYPTO_CONFIG', colors, False)
151 if options.unset_use_psa:
152 set_config_option_value(conf, 'MBEDTLS_USE_PSA_CRYPTO', colors, False)
153
154class Job:
155 """A job builds the library in a specific configuration and runs some tests."""
156 def __init__(self, name, config_settings, commands):
157 """Build a job object.
158The job uses the configuration described by config_settings. This is a
159dictionary where the keys are preprocessor symbols and the values are
160booleans or strings. A boolean indicates whether or not to #define the
161symbol. With a string, the symbol is #define'd to that value.
162After setting the configuration, the job runs the programs specified by
163commands. This is a list of lists of strings; each list of string is a
164command name and its arguments and is passed to subprocess.call with
165shell=False."""
166 self.name = name
167 self.config_settings = config_settings
168 self.commands = commands
169
170 def announce(self, colors, what):
171 '''Announce the start or completion of a job.
172If what is None, announce the start of the job.
173If what is True, announce that the job has passed.
174If what is False, announce that the job has failed.'''
175 if what is True:
176 log_line(self.name + ' PASSED', color=colors.green)
177 elif what is False:
178 log_line(self.name + ' FAILED', color=colors.red)
179 else:
180 log_line('starting ' + self.name, color=colors.cyan)
181
182 def configure(self, conf, options, colors):
183 '''Set library configuration options as required for the job.'''
184 set_reference_config(conf, options, colors)
185 for key, value in sorted(self.config_settings.items()):
186 ret = set_config_option_value(conf, key, colors, value)
187 if ret is False:
188 return False
189 return True
190
191 def test(self, options):
192 '''Run the job's build and test commands.
193Return True if all the commands succeed and False otherwise.
194If options.keep_going is false, stop as soon as one command fails. Otherwise
195run all the commands, except that if the first command fails, none of the
196other commands are run (typically, the first command is a build command
197and subsequent commands are tests that cannot run if the build failed).'''
198 built = False
199 success = True
200 for command in self.commands:
201 log_command(command)
202 env = os.environ.copy()
203 if 'MBEDTLS_TEST_CONFIGURATION' in env:
204 env['MBEDTLS_TEST_CONFIGURATION'] += '-' + self.name
205 ret = subprocess.call(command, env=env)
206 if ret != 0:
207 if command[0] not in ['make', options.make_command]:
208 log_line('*** [{}] Error {}'.format(' '.join(command), ret))
209 if not options.keep_going or not built:
210 return False
211 success = False
212 built = True
213 return success
214
215# If the configuration option A requires B, make sure that
216# B in REVERSE_DEPENDENCIES[A].
217# All the information here should be contained in check_config.h. This
218# file includes a copy because it changes rarely and it would be a pain
219# to extract automatically.
220REVERSE_DEPENDENCIES = {
221 'MBEDTLS_AES_C': ['MBEDTLS_CTR_DRBG_C',
222 'MBEDTLS_NIST_KW_C'],
223 'MBEDTLS_CHACHA20_C': ['MBEDTLS_CHACHAPOLY_C'],
224 'MBEDTLS_ECDSA_C': ['MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED',
225 'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED'],
226 'MBEDTLS_ECP_C': ['MBEDTLS_ECDSA_C',
227 'MBEDTLS_ECDH_C',
228 'MBEDTLS_ECJPAKE_C',
229 'MBEDTLS_ECP_RESTARTABLE',
230 'MBEDTLS_PK_PARSE_EC_EXTENDED',
231 'MBEDTLS_PK_PARSE_EC_COMPRESSED',
232 'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED',
233 'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED',
234 'MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED',
235 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
236 'MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED',
237 'MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED',
238 'MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED',
239 'MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED'],
240 'MBEDTLS_ECP_DP_SECP256R1_ENABLED': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'],
241 'MBEDTLS_PKCS1_V21': ['MBEDTLS_X509_RSASSA_PSS_SUPPORT'],
242 'MBEDTLS_PKCS1_V15': ['MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED',
243 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
244 'MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED',
245 'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED'],
246 'MBEDTLS_RSA_C': ['MBEDTLS_X509_RSASSA_PSS_SUPPORT',
247 'MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED',
248 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
249 'MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED',
250 'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED',
251 'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED'],
252 'MBEDTLS_SHA256_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED',
253 'MBEDTLS_ENTROPY_FORCE_SHA256',
254 'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_IF_PRESENT',
255 'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_ONLY',
256 'MBEDTLS_LMS_C',
257 'MBEDTLS_LMS_PRIVATE'],
258 'MBEDTLS_SHA512_C': ['MBEDTLS_SHA512_USE_A64_CRYPTO_IF_PRESENT',
259 'MBEDTLS_SHA512_USE_A64_CRYPTO_ONLY'],
260 'MBEDTLS_SHA224_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED',
261 'MBEDTLS_ENTROPY_FORCE_SHA256',
262 'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_IF_PRESENT',
263 'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_ONLY'],
264 'MBEDTLS_X509_RSASSA_PSS_SUPPORT': []
265}
266
267# If an option is tested in an exclusive test, alter the following defines.
268# These are not necessarily dependencies, but just minimal required changes
269# if a given define is the only one enabled from an exclusive group.
270EXCLUSIVE_GROUPS = {
271 'MBEDTLS_SHA512_C': ['-MBEDTLS_SSL_COOKIE_C',
272 '-MBEDTLS_SSL_TLS_C'],
273 'MBEDTLS_ECP_DP_CURVE448_ENABLED': ['-MBEDTLS_ECDSA_C',
274 '-MBEDTLS_ECDSA_DETERMINISTIC',
275 '-MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED',
276 '-MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED',
277 '-MBEDTLS_ECJPAKE_C',
278 '-MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'],
279 'MBEDTLS_ECP_DP_CURVE25519_ENABLED': ['-MBEDTLS_ECDSA_C',
280 '-MBEDTLS_ECDSA_DETERMINISTIC',
281 '-MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED',
282 '-MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED',
283 '-MBEDTLS_ECJPAKE_C',
284 '-MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'],
285 'MBEDTLS_ARIA_C': ['-MBEDTLS_CMAC_C'],
286 'MBEDTLS_CAMELLIA_C': ['-MBEDTLS_CMAC_C'],
287 'MBEDTLS_CHACHA20_C': ['-MBEDTLS_CMAC_C', '-MBEDTLS_CCM_C', '-MBEDTLS_GCM_C'],
288 'MBEDTLS_DES_C': ['-MBEDTLS_CCM_C',
289 '-MBEDTLS_GCM_C',
290 '-MBEDTLS_SSL_TICKET_C',
291 '-MBEDTLS_SSL_CONTEXT_SERIALIZATION'],
292}
293def handle_exclusive_groups(config_settings, symbol):
294 """For every symbol tested in an exclusive group check if there are other
295defines to be altered. """
296 for dep in EXCLUSIVE_GROUPS.get(symbol, []):
297 unset = dep.startswith('-')
298 dep = dep[1:]
299 config_settings[dep] = not unset
300
301def turn_off_dependencies(config_settings):
302 """For every option turned off config_settings, also turn off what depends on it.
303An option O is turned off if config_settings[O] is False."""
304 for key, value in sorted(config_settings.items()):
305 if value is not False:
306 continue
307 for dep in REVERSE_DEPENDENCIES.get(key, []):
308 config_settings[dep] = False
309
310class BaseDomain: # pylint: disable=too-few-public-methods, unused-argument
311 """A base class for all domains."""
312 def __init__(self, symbols, commands, exclude):
313 """Initialize the jobs container"""
314 self.jobs = []
315
316class ExclusiveDomain(BaseDomain): # pylint: disable=too-few-public-methods
317 """A domain consisting of a set of conceptually-equivalent settings.
318Establish a list of configuration symbols. For each symbol, run a test job
319with this symbol set and the others unset."""
320 def __init__(self, symbols, commands, exclude=None):
321 """Build a domain for the specified list of configuration symbols.
322The domain contains a set of jobs that enable one of the elements
323of symbols and disable the others.
324Each job runs the specified commands.
325If exclude is a regular expression, skip generated jobs whose description
326would match this regular expression."""
327 super().__init__(symbols, commands, exclude)
328 base_config_settings = {}
329 for symbol in symbols:
330 base_config_settings[symbol] = False
331 for symbol in symbols:
332 description = symbol
333 if exclude and re.match(exclude, description):
334 continue
335 config_settings = base_config_settings.copy()
336 config_settings[symbol] = True
337 handle_exclusive_groups(config_settings, symbol)
338 turn_off_dependencies(config_settings)
339 job = Job(description, config_settings, commands)
340 self.jobs.append(job)
341
342class ComplementaryDomain(BaseDomain): # pylint: disable=too-few-public-methods
343 """A domain consisting of a set of loosely-related settings.
344Establish a list of configuration symbols. For each symbol, run a test job
345with this symbol unset.
346If exclude is a regular expression, skip generated jobs whose description
347would match this regular expression."""
348 def __init__(self, symbols, commands, exclude=None):
349 """Build a domain for the specified list of configuration symbols.
350Each job in the domain disables one of the specified symbols.
351Each job runs the specified commands."""
352 super().__init__(symbols, commands, exclude)
353 for symbol in symbols:
354 description = '!' + symbol
355 if exclude and re.match(exclude, description):
356 continue
357 config_settings = {symbol: False}
358 turn_off_dependencies(config_settings)
359 job = Job(description, config_settings, commands)
360 self.jobs.append(job)
361
362class DualDomain(ExclusiveDomain, ComplementaryDomain): # pylint: disable=too-few-public-methods
363 """A domain that contains both the ExclusiveDomain and BaseDomain tests.
364Both parent class __init__ calls are performed in any order and
365each call adds respective jobs. The job array initialization is done once in
366BaseDomain, before the parent __init__ calls."""
367
368class CipherInfo: # pylint: disable=too-few-public-methods
369 """Collect data about cipher.h."""
370 def __init__(self):
371 self.base_symbols = set()
372 with open('include/mbedtls/cipher.h', encoding="utf-8") as fh:
373 for line in fh:
374 m = re.match(r' *MBEDTLS_CIPHER_ID_(\w+),', line)
375 if m and m.group(1) not in ['NONE', 'NULL', '3DES']:
376 self.base_symbols.add('MBEDTLS_' + m.group(1) + '_C')
377
378class DomainData:
379 """A container for domains and jobs, used to structurize testing."""
380 def config_symbols_matching(self, regexp):
381 """List the mbedtls_config.h settings matching regexp."""
382 return [symbol for symbol in self.all_config_symbols
383 if re.match(regexp, symbol)]
384
385 def __init__(self, options, conf):
386 """Gather data about the library and establish a list of domains to test."""
387 build_command = [options.make_command, 'CFLAGS=-Werror -O2']
388 build_and_test = [build_command, [options.make_command, 'test']]
389 self.all_config_symbols = set(conf.settings.keys())
390 # Find hash modules by name.
391 hash_symbols = self.config_symbols_matching(r'MBEDTLS_(MD|RIPEMD|SHA)[0-9]+_C\Z')
392 # Find elliptic curve enabling macros by name.
393 curve_symbols = self.config_symbols_matching(r'MBEDTLS_ECP_DP_\w+_ENABLED\Z')
394 # Find key exchange enabling macros by name.
395 key_exchange_symbols = self.config_symbols_matching(r'MBEDTLS_KEY_EXCHANGE_\w+_ENABLED\Z')
396 # Find cipher IDs (block permutations and stream ciphers --- chaining
397 # and padding modes are exercised separately) information by parsing
398 # cipher.h, as the information is not readily available in mbedtls_config.h.
399 cipher_info = CipherInfo()
400 # Find block cipher chaining and padding mode enabling macros by name.
401 cipher_chaining_symbols = self.config_symbols_matching(r'MBEDTLS_CIPHER_MODE_\w+\Z')
402 cipher_padding_symbols = self.config_symbols_matching(r'MBEDTLS_CIPHER_PADDING_\w+\Z')
403 self.domains = {
404 # Cipher IDs, chaining modes and padding modes. Run the test suites.
405 'cipher_id': ExclusiveDomain(cipher_info.base_symbols,
406 build_and_test),
407 'cipher_chaining': ExclusiveDomain(cipher_chaining_symbols,
408 build_and_test),
409 'cipher_padding': ExclusiveDomain(cipher_padding_symbols,
410 build_and_test),
411 # Elliptic curves. Run the test suites.
412 'curves': ExclusiveDomain(curve_symbols, build_and_test),
413 # Hash algorithms. Excluding exclusive domains of MD, RIPEMD, SHA1,
414 # SHA224 and SHA384 because MBEDTLS_ENTROPY_C is extensively used
415 # across various modules, but it depends on either SHA256 or SHA512.
416 # As a consequence an "exclusive" test of anything other than SHA256
417 # or SHA512 with MBEDTLS_ENTROPY_C enabled is not possible.
418 'hashes': DualDomain(hash_symbols, build_and_test,
419 exclude=r'MBEDTLS_(MD|RIPEMD|SHA1_)' \
420 '|MBEDTLS_SHA224_' \
421 '|MBEDTLS_SHA384_' \
422 '|MBEDTLS_SHA3_'),
423 # Key exchange types.
424 'kex': ExclusiveDomain(key_exchange_symbols, build_and_test),
425 'pkalgs': ComplementaryDomain(['MBEDTLS_ECDSA_C',
426 'MBEDTLS_ECP_C',
427 'MBEDTLS_PKCS1_V21',
428 'MBEDTLS_PKCS1_V15',
429 'MBEDTLS_RSA_C',
430 'MBEDTLS_X509_RSASSA_PSS_SUPPORT'],
431 build_and_test),
432 }
433 self.jobs = {}
434 for domain in self.domains.values():
435 for job in domain.jobs:
436 self.jobs[job.name] = job
437
438 def get_jobs(self, name):
439 """Return the list of jobs identified by the given name.
440A name can either be the name of a domain or the name of one specific job."""
441 if name in self.domains:
442 return sorted(self.domains[name].jobs, key=lambda job: job.name)
443 else:
444 return [self.jobs[name]]
445
446def run(options, job, conf, colors=NO_COLORS):
447 """Run the specified job (a Job instance)."""
448 subprocess.check_call([options.make_command, 'clean'])
449 job.announce(colors, None)
450 if not job.configure(conf, options, colors):
451 job.announce(colors, False)
452 return False
453 conf.write()
454 success = job.test(options)
455 job.announce(colors, success)
456 return success
457
458def run_tests(options, domain_data, conf):
459 """Run the desired jobs.
460domain_data should be a DomainData instance that describes the available
461domains and jobs.
462Run the jobs listed in options.tasks."""
463 if not hasattr(options, 'config_backup'):
464 options.config_backup = options.config + '.bak'
465 colors = Colors(options)
466 jobs = []
467 failures = []
468 successes = []
469 for name in options.tasks:
470 jobs += domain_data.get_jobs(name)
471 backup_config(options)
472 try:
473 for job in jobs:
474 success = run(options, job, conf, colors=colors)
475 if not success:
476 if options.keep_going:
477 failures.append(job.name)
478 else:
479 return False
480 else:
481 successes.append(job.name)
482 restore_config(options)
483 except:
484 # Restore the configuration, except in stop-on-error mode if there
485 # was an error, where we leave the failing configuration up for
486 # developer convenience.
487 if options.keep_going:
488 restore_config(options)
489 raise
490 if successes:
491 log_line('{} passed'.format(' '.join(successes)), color=colors.bold_green)
492 if failures:
493 log_line('{} FAILED'.format(' '.join(failures)), color=colors.bold_red)
494 return False
495 else:
496 return True
497
498def main():
499 try:
500 parser = argparse.ArgumentParser(
501 formatter_class=argparse.RawDescriptionHelpFormatter,
502 description=
503 "Test Mbed TLS with a subset of algorithms.\n\n"
504 "Example usage:\n"
505 r"./tests/scripts/depends.py \!MBEDTLS_SHA1_C MBEDTLS_SHA256_C""\n"
506 "./tests/scripts/depends.py MBEDTLS_AES_C hashes\n"
507 "./tests/scripts/depends.py cipher_id cipher_chaining\n")
508 parser.add_argument('--color', metavar='WHEN',
509 help='Colorize the output (always/auto/never)',
510 choices=['always', 'auto', 'never'], default='auto')
511 parser.add_argument('-c', '--config', metavar='FILE',
512 help='Configuration file to modify',
513 default='include/mbedtls/mbedtls_config.h')
514 parser.add_argument('-C', '--directory', metavar='DIR',
515 help='Change to this directory before anything else',
516 default='.')
517 parser.add_argument('-k', '--keep-going',
518 help='Try all configurations even if some fail (default)',
519 action='store_true', dest='keep_going', default=True)
520 parser.add_argument('-e', '--no-keep-going',
521 help='Stop as soon as a configuration fails',
522 action='store_false', dest='keep_going')
523 parser.add_argument('--list-jobs',
524 help='List supported jobs and exit',
525 action='append_const', dest='list', const='jobs')
526 parser.add_argument('--list-domains',
527 help='List supported domains and exit',
528 action='append_const', dest='list', const='domains')
529 parser.add_argument('--make-command', metavar='CMD',
530 help='Command to run instead of make (e.g. gmake)',
531 action='store', default='make')
532 parser.add_argument('--unset-use-psa',
533 help='Unset MBEDTLS_USE_PSA_CRYPTO before any test',
534 action='store_true', dest='unset_use_psa')
535 parser.add_argument('tasks', metavar='TASKS', nargs='*',
536 help='The domain(s) or job(s) to test (default: all).',
537 default=True)
538 options = parser.parse_args()
539 os.chdir(options.directory)
540 conf = config.ConfigFile(options.config)
541 domain_data = DomainData(options, conf)
542
543 if options.tasks is True:
544 options.tasks = sorted(domain_data.domains.keys())
545 if options.list:
546 for arg in options.list:
547 for domain_name in sorted(getattr(domain_data, arg).keys()):
548 print(domain_name)
549 sys.exit(0)
550 else:
551 sys.exit(0 if run_tests(options, domain_data, conf) else 1)
552 except Exception: # pylint: disable=broad-except
553 traceback.print_exc()
554 sys.exit(3)
555
556if __name__ == '__main__':
557 main()