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