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 ret = subprocess.call(command) 203 if ret != 0: 204 if command[0] not in ['make', options.make_command]: 205 log_line('*** [{}] Error {}'.format(' '.join(command), ret)) 206 if not options.keep_going or not built: 207 return False 208 success = False 209 built = True 210 return success 211 212# If the configuration option A requires B, make sure that 213# B in REVERSE_DEPENDENCIES[A]. 214# All the information here should be contained in check_config.h. This 215# file includes a copy because it changes rarely and it would be a pain 216# to extract automatically. 217REVERSE_DEPENDENCIES = { 218 'MBEDTLS_AES_C': ['MBEDTLS_CTR_DRBG_C', 219 'MBEDTLS_NIST_KW_C'], 220 'MBEDTLS_CHACHA20_C': ['MBEDTLS_CHACHAPOLY_C'], 221 'MBEDTLS_ECDSA_C': ['MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED', 222 'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED'], 223 'MBEDTLS_ECP_C': ['MBEDTLS_ECDSA_C', 224 'MBEDTLS_ECDH_C', 225 'MBEDTLS_ECJPAKE_C', 226 'MBEDTLS_ECP_RESTARTABLE', 227 'MBEDTLS_PK_PARSE_EC_EXTENDED', 228 'MBEDTLS_PK_PARSE_EC_COMPRESSED', 229 'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED', 230 'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED', 231 'MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED', 232 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED', 233 'MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED', 234 'MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED', 235 'MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED', 236 'MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED'], 237 'MBEDTLS_ECP_DP_SECP256R1_ENABLED': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'], 238 'MBEDTLS_PKCS1_V21': ['MBEDTLS_X509_RSASSA_PSS_SUPPORT'], 239 'MBEDTLS_PKCS1_V15': ['MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED', 240 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED', 241 'MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED', 242 'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED'], 243 'MBEDTLS_RSA_C': ['MBEDTLS_X509_RSASSA_PSS_SUPPORT', 244 'MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED', 245 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED', 246 'MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED', 247 'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED', 248 'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED'], 249 'MBEDTLS_SHA256_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED', 250 'MBEDTLS_ENTROPY_FORCE_SHA256', 251 'MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT', 252 'MBEDTLS_SHA256_USE_A64_CRYPTO_ONLY', 253 'MBEDTLS_LMS_C', 254 'MBEDTLS_LMS_PRIVATE'], 255 'MBEDTLS_SHA512_C': ['MBEDTLS_SHA512_USE_A64_CRYPTO_IF_PRESENT', 256 'MBEDTLS_SHA512_USE_A64_CRYPTO_ONLY'], 257 'MBEDTLS_SHA224_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED', 258 'MBEDTLS_ENTROPY_FORCE_SHA256', 259 'MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT', 260 'MBEDTLS_SHA256_USE_A64_CRYPTO_ONLY'], 261 'MBEDTLS_X509_RSASSA_PSS_SUPPORT': [] 262} 263 264# If an option is tested in an exclusive test, alter the following defines. 265# These are not necessarily dependencies, but just minimal required changes 266# if a given define is the only one enabled from an exclusive group. 267EXCLUSIVE_GROUPS = { 268 'MBEDTLS_SHA512_C': ['-MBEDTLS_SSL_COOKIE_C', 269 '-MBEDTLS_SSL_TLS_C'], 270 'MBEDTLS_ECP_DP_CURVE448_ENABLED': ['-MBEDTLS_ECDSA_C', 271 '-MBEDTLS_ECDSA_DETERMINISTIC', 272 '-MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED', 273 '-MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED', 274 '-MBEDTLS_ECJPAKE_C', 275 '-MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'], 276 'MBEDTLS_ECP_DP_CURVE25519_ENABLED': ['-MBEDTLS_ECDSA_C', 277 '-MBEDTLS_ECDSA_DETERMINISTIC', 278 '-MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED', 279 '-MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED', 280 '-MBEDTLS_ECJPAKE_C', 281 '-MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'], 282 'MBEDTLS_ARIA_C': ['-MBEDTLS_CMAC_C'], 283 'MBEDTLS_CAMELLIA_C': ['-MBEDTLS_CMAC_C'], 284 'MBEDTLS_CHACHA20_C': ['-MBEDTLS_CMAC_C', '-MBEDTLS_CCM_C', '-MBEDTLS_GCM_C'], 285 'MBEDTLS_DES_C': ['-MBEDTLS_CCM_C', 286 '-MBEDTLS_GCM_C', 287 '-MBEDTLS_SSL_TICKET_C', 288 '-MBEDTLS_SSL_CONTEXT_SERIALIZATION'], 289} 290def handle_exclusive_groups(config_settings, symbol): 291 """For every symbol tested in an exclusive group check if there are other 292defines to be altered. """ 293 for dep in EXCLUSIVE_GROUPS.get(symbol, []): 294 unset = dep.startswith('-') 295 dep = dep[1:] 296 config_settings[dep] = not unset 297 298def turn_off_dependencies(config_settings): 299 """For every option turned off config_settings, also turn off what depends on it. 300An option O is turned off if config_settings[O] is False.""" 301 for key, value in sorted(config_settings.items()): 302 if value is not False: 303 continue 304 for dep in REVERSE_DEPENDENCIES.get(key, []): 305 config_settings[dep] = False 306 307class BaseDomain: # pylint: disable=too-few-public-methods, unused-argument 308 """A base class for all domains.""" 309 def __init__(self, symbols, commands, exclude): 310 """Initialize the jobs container""" 311 self.jobs = [] 312 313class ExclusiveDomain(BaseDomain): # pylint: disable=too-few-public-methods 314 """A domain consisting of a set of conceptually-equivalent settings. 315Establish a list of configuration symbols. For each symbol, run a test job 316with this symbol set and the others unset.""" 317 def __init__(self, symbols, commands, exclude=None): 318 """Build a domain for the specified list of configuration symbols. 319The domain contains a set of jobs that enable one of the elements 320of symbols and disable the others. 321Each job runs the specified commands. 322If exclude is a regular expression, skip generated jobs whose description 323would match this regular expression.""" 324 super().__init__(symbols, commands, exclude) 325 base_config_settings = {} 326 for symbol in symbols: 327 base_config_settings[symbol] = False 328 for symbol in symbols: 329 description = symbol 330 if exclude and re.match(exclude, description): 331 continue 332 config_settings = base_config_settings.copy() 333 config_settings[symbol] = True 334 handle_exclusive_groups(config_settings, symbol) 335 turn_off_dependencies(config_settings) 336 job = Job(description, config_settings, commands) 337 self.jobs.append(job) 338 339class ComplementaryDomain(BaseDomain): # pylint: disable=too-few-public-methods 340 """A domain consisting of a set of loosely-related settings. 341Establish a list of configuration symbols. For each symbol, run a test job 342with this symbol unset. 343If exclude is a regular expression, skip generated jobs whose description 344would match this regular expression.""" 345 def __init__(self, symbols, commands, exclude=None): 346 """Build a domain for the specified list of configuration symbols. 347Each job in the domain disables one of the specified symbols. 348Each job runs the specified commands.""" 349 super().__init__(symbols, commands, exclude) 350 for symbol in symbols: 351 description = '!' + symbol 352 if exclude and re.match(exclude, description): 353 continue 354 config_settings = {symbol: False} 355 turn_off_dependencies(config_settings) 356 job = Job(description, config_settings, commands) 357 self.jobs.append(job) 358 359class DualDomain(ExclusiveDomain, ComplementaryDomain): # pylint: disable=too-few-public-methods 360 """A domain that contains both the ExclusiveDomain and BaseDomain tests. 361Both parent class __init__ calls are performed in any order and 362each call adds respective jobs. The job array initialization is done once in 363BaseDomain, before the parent __init__ calls.""" 364 365class CipherInfo: # pylint: disable=too-few-public-methods 366 """Collect data about cipher.h.""" 367 def __init__(self): 368 self.base_symbols = set() 369 with open('include/mbedtls/cipher.h', encoding="utf-8") as fh: 370 for line in fh: 371 m = re.match(r' *MBEDTLS_CIPHER_ID_(\w+),', line) 372 if m and m.group(1) not in ['NONE', 'NULL', '3DES']: 373 self.base_symbols.add('MBEDTLS_' + m.group(1) + '_C') 374 375class DomainData: 376 """A container for domains and jobs, used to structurize testing.""" 377 def config_symbols_matching(self, regexp): 378 """List the mbedtls_config.h settings matching regexp.""" 379 return [symbol for symbol in self.all_config_symbols 380 if re.match(regexp, symbol)] 381 382 def __init__(self, options, conf): 383 """Gather data about the library and establish a list of domains to test.""" 384 build_command = [options.make_command, 'CFLAGS=-Werror'] 385 build_and_test = [build_command, [options.make_command, 'test']] 386 self.all_config_symbols = set(conf.settings.keys()) 387 # Find hash modules by name. 388 hash_symbols = self.config_symbols_matching(r'MBEDTLS_(MD|RIPEMD|SHA)[0-9]+_C\Z') 389 # Find elliptic curve enabling macros by name. 390 curve_symbols = self.config_symbols_matching(r'MBEDTLS_ECP_DP_\w+_ENABLED\Z') 391 # Find key exchange enabling macros by name. 392 key_exchange_symbols = self.config_symbols_matching(r'MBEDTLS_KEY_EXCHANGE_\w+_ENABLED\Z') 393 # Find cipher IDs (block permutations and stream ciphers --- chaining 394 # and padding modes are exercised separately) information by parsing 395 # cipher.h, as the information is not readily available in mbedtls_config.h. 396 cipher_info = CipherInfo() 397 # Find block cipher chaining and padding mode enabling macros by name. 398 cipher_chaining_symbols = self.config_symbols_matching(r'MBEDTLS_CIPHER_MODE_\w+\Z') 399 cipher_padding_symbols = self.config_symbols_matching(r'MBEDTLS_CIPHER_PADDING_\w+\Z') 400 self.domains = { 401 # Cipher IDs, chaining modes and padding modes. Run the test suites. 402 'cipher_id': ExclusiveDomain(cipher_info.base_symbols, 403 build_and_test), 404 'cipher_chaining': ExclusiveDomain(cipher_chaining_symbols, 405 build_and_test), 406 'cipher_padding': ExclusiveDomain(cipher_padding_symbols, 407 build_and_test), 408 # Elliptic curves. Run the test suites. 409 'curves': ExclusiveDomain(curve_symbols, build_and_test), 410 # Hash algorithms. Excluding exclusive domains of MD, RIPEMD, SHA1, 411 # SHA224 and SHA384 because MBEDTLS_ENTROPY_C is extensively used 412 # across various modules, but it depends on either SHA256 or SHA512. 413 # As a consequence an "exclusive" test of anything other than SHA256 414 # or SHA512 with MBEDTLS_ENTROPY_C enabled is not possible. 415 'hashes': DualDomain(hash_symbols, build_and_test, 416 exclude=r'MBEDTLS_(MD|RIPEMD|SHA1_)' \ 417 '|MBEDTLS_SHA224_' \ 418 '|MBEDTLS_SHA384_' \ 419 '|MBEDTLS_SHA3_'), 420 # Key exchange types. 421 'kex': ExclusiveDomain(key_exchange_symbols, build_and_test), 422 'pkalgs': ComplementaryDomain(['MBEDTLS_ECDSA_C', 423 'MBEDTLS_ECP_C', 424 'MBEDTLS_PKCS1_V21', 425 'MBEDTLS_PKCS1_V15', 426 'MBEDTLS_RSA_C', 427 'MBEDTLS_X509_RSASSA_PSS_SUPPORT'], 428 build_and_test), 429 } 430 self.jobs = {} 431 for domain in self.domains.values(): 432 for job in domain.jobs: 433 self.jobs[job.name] = job 434 435 def get_jobs(self, name): 436 """Return the list of jobs identified by the given name. 437A name can either be the name of a domain or the name of one specific job.""" 438 if name in self.domains: 439 return sorted(self.domains[name].jobs, key=lambda job: job.name) 440 else: 441 return [self.jobs[name]] 442 443def run(options, job, conf, colors=NO_COLORS): 444 """Run the specified job (a Job instance).""" 445 subprocess.check_call([options.make_command, 'clean']) 446 job.announce(colors, None) 447 if not job.configure(conf, options, colors): 448 job.announce(colors, False) 449 return False 450 conf.write() 451 success = job.test(options) 452 job.announce(colors, success) 453 return success 454 455def run_tests(options, domain_data, conf): 456 """Run the desired jobs. 457domain_data should be a DomainData instance that describes the available 458domains and jobs. 459Run the jobs listed in options.tasks.""" 460 if not hasattr(options, 'config_backup'): 461 options.config_backup = options.config + '.bak' 462 colors = Colors(options) 463 jobs = [] 464 failures = [] 465 successes = [] 466 for name in options.tasks: 467 jobs += domain_data.get_jobs(name) 468 backup_config(options) 469 try: 470 for job in jobs: 471 success = run(options, job, conf, colors=colors) 472 if not success: 473 if options.keep_going: 474 failures.append(job.name) 475 else: 476 return False 477 else: 478 successes.append(job.name) 479 restore_config(options) 480 except: 481 # Restore the configuration, except in stop-on-error mode if there 482 # was an error, where we leave the failing configuration up for 483 # developer convenience. 484 if options.keep_going: 485 restore_config(options) 486 raise 487 if successes: 488 log_line('{} passed'.format(' '.join(successes)), color=colors.bold_green) 489 if failures: 490 log_line('{} FAILED'.format(' '.join(failures)), color=colors.bold_red) 491 return False 492 else: 493 return True 494 495def main(): 496 try: 497 parser = argparse.ArgumentParser( 498 formatter_class=argparse.RawDescriptionHelpFormatter, 499 description= 500 "Test Mbed TLS with a subset of algorithms.\n\n" 501 "Example usage:\n" 502 r"./tests/scripts/depends.py \!MBEDTLS_SHA1_C MBEDTLS_SHA256_C""\n" 503 "./tests/scripts/depends.py MBEDTLS_AES_C hashes\n" 504 "./tests/scripts/depends.py cipher_id cipher_chaining\n") 505 parser.add_argument('--color', metavar='WHEN', 506 help='Colorize the output (always/auto/never)', 507 choices=['always', 'auto', 'never'], default='auto') 508 parser.add_argument('-c', '--config', metavar='FILE', 509 help='Configuration file to modify', 510 default='include/mbedtls/mbedtls_config.h') 511 parser.add_argument('-C', '--directory', metavar='DIR', 512 help='Change to this directory before anything else', 513 default='.') 514 parser.add_argument('-k', '--keep-going', 515 help='Try all configurations even if some fail (default)', 516 action='store_true', dest='keep_going', default=True) 517 parser.add_argument('-e', '--no-keep-going', 518 help='Stop as soon as a configuration fails', 519 action='store_false', dest='keep_going') 520 parser.add_argument('--list-jobs', 521 help='List supported jobs and exit', 522 action='append_const', dest='list', const='jobs') 523 parser.add_argument('--list-domains', 524 help='List supported domains and exit', 525 action='append_const', dest='list', const='domains') 526 parser.add_argument('--make-command', metavar='CMD', 527 help='Command to run instead of make (e.g. gmake)', 528 action='store', default='make') 529 parser.add_argument('--unset-use-psa', 530 help='Unset MBEDTLS_USE_PSA_CRYPTO before any test', 531 action='store_true', dest='unset_use_psa') 532 parser.add_argument('tasks', metavar='TASKS', nargs='*', 533 help='The domain(s) or job(s) to test (default: all).', 534 default=True) 535 options = parser.parse_args() 536 os.chdir(options.directory) 537 conf = config.ConfigFile(options.config) 538 domain_data = DomainData(options, conf) 539 540 if options.tasks is True: 541 options.tasks = sorted(domain_data.domains.keys()) 542 if options.list: 543 for arg in options.list: 544 for domain_name in sorted(getattr(domain_data, arg).keys()): 545 print(domain_name) 546 sys.exit(0) 547 else: 548 sys.exit(0 if run_tests(options, domain_data, conf) else 1) 549 except Exception: # pylint: disable=broad-except 550 traceback.print_exc() 551 sys.exit(3) 552 553if __name__ == '__main__': 554 main() 555