1#!/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() 558