depends.py 26 KB

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