quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

depends.py (26612B)


      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 """
      7 Test Mbed TLS with a subset of algorithms.
      8 
      9 This script can be divided into several steps:
     10 
     11 First, include/mbedtls/mbedtls_config.h or a different config file passed
     12 in the arguments is parsed to extract any configuration options (using config.py).
     13 
     14 Then, test domains (groups of jobs, tests) are built based on predefined data
     15 collected 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 
     22 The 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 
     42 Lastly, the collected jobs are executed and (optionally) tested, with
     43 error reporting and coloring as configured in options. Each test starts with
     44 a full config without a couple of slowing down or unnecessary options
     45 (see set_reference_config), then the specific job config is derived.
     46 """
     47 import argparse
     48 import os
     49 import re
     50 import shutil
     51 import subprocess
     52 import sys
     53 import traceback
     54 from typing import Union
     55 
     56 # Add the Mbed TLS Python library directory to the module search path
     57 import scripts_path # pylint: disable=unused-import
     58 import config
     59 
     60 class Colors: # pylint: disable=too-few-public-methods
     61     """Minimalistic support for colored output.
     62 Each field of an object of this class is either None if colored output
     63 is not possible or not desired, or a pair of strings (start, stop) such
     64 that outputting start switches the text color to the desired color and
     65 stop 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)
     87 NO_COLORS = Colors(None)
     88 
     89 def 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 
     97 def log_command(cmd):
     98     """Print a trace of the specified command.
     99 cmd is a list of strings: a command name and its arguments."""
    100     log_line(' '.join(cmd), prefix='+')
    101 
    102 def backup_config(options):
    103     """Back up the library configuration file (mbedtls_config.h).
    104 If the backup file already exists, it is presumed to be the desired backup,
    105 so 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 
    112 def restore_config(options):
    113     """Restore the library configuration file (mbedtls_config.h).
    114 Remove 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 
    120 def option_exists(conf, option):
    121     return option in conf.settings
    122 
    123 def set_config_option_value(conf, option, colors, value: Union[bool, str]):
    124     """Set/unset a configuration option, optionally specifying a value.
    125 value can be either True/False (set/unset config option), or a string,
    126 which 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 
    142 def set_reference_config(conf, options, colors):
    143     """Change the library configuration file (mbedtls_config.h) to the reference state.
    144 The reference state is the one from which the tested configurations are
    145 derived."""
    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 
    154 class 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.
    158 The job uses the configuration described by config_settings. This is a
    159 dictionary where the keys are preprocessor symbols and the values are
    160 booleans or strings. A boolean indicates whether or not to #define the
    161 symbol. With a string, the symbol is #define'd to that value.
    162 After setting the configuration, the job runs the programs specified by
    163 commands. This is a list of lists of strings; each list of string is a
    164 command name and its arguments and is passed to subprocess.call with
    165 shell=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.
    172 If what is None, announce the start of the job.
    173 If what is True, announce that the job has passed.
    174 If 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.
    193 Return True if all the commands succeed and False otherwise.
    194 If options.keep_going is false, stop as soon as one command fails. Otherwise
    195 run all the commands, except that if the first command fails, none of the
    196 other commands are run (typically, the first command is a build command
    197 and 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.
    220 REVERSE_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.
    270 EXCLUSIVE_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 }
    293 def handle_exclusive_groups(config_settings, symbol):
    294     """For every symbol tested in an exclusive group check if there are other
    295 defines 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 
    301 def turn_off_dependencies(config_settings):
    302     """For every option turned off config_settings, also turn off what depends on it.
    303 An 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 
    310 class 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 
    316 class ExclusiveDomain(BaseDomain): # pylint: disable=too-few-public-methods
    317     """A domain consisting of a set of conceptually-equivalent settings.
    318 Establish a list of configuration symbols. For each symbol, run a test job
    319 with 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.
    322 The domain contains a set of jobs that enable one of the elements
    323 of symbols and disable the others.
    324 Each job runs the specified commands.
    325 If exclude is a regular expression, skip generated jobs whose description
    326 would 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 
    342 class ComplementaryDomain(BaseDomain): # pylint: disable=too-few-public-methods
    343     """A domain consisting of a set of loosely-related settings.
    344 Establish a list of configuration symbols. For each symbol, run a test job
    345 with this symbol unset.
    346 If exclude is a regular expression, skip generated jobs whose description
    347 would match this regular expression."""
    348     def __init__(self, symbols, commands, exclude=None):
    349         """Build a domain for the specified list of configuration symbols.
    350 Each job in the domain disables one of the specified symbols.
    351 Each 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 
    362 class DualDomain(ExclusiveDomain, ComplementaryDomain): # pylint: disable=too-few-public-methods
    363     """A domain that contains both the ExclusiveDomain and BaseDomain tests.
    364 Both parent class __init__ calls are performed in any order and
    365 each call adds respective jobs. The job array initialization is done once in
    366 BaseDomain, before the parent __init__ calls."""
    367 
    368 class 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 
    378 class 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.
    440 A 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 
    446 def 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 
    458 def run_tests(options, domain_data, conf):
    459     """Run the desired jobs.
    460 domain_data should be a DomainData instance that describes the available
    461 domains and jobs.
    462 Run 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 
    498 def 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.MbedTLSConfig(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 
    556 if __name__ == '__main__':
    557     main()