blob: b2a2f27b2acce408845f1ed1e9ef880c0e32711b [file] [log] [blame]
Gilles Peskineb39e3ec2019-01-29 08:50:20 +01001#!/usr/bin/env python3
2
3# Copyright (c) 2018, 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"""Test Mbed TLS with a subset of algorithms.
21"""
22
23import argparse
24import os
25import re
26import shutil
27import subprocess
28import sys
29import traceback
30
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +010031class Colors:
32 """Minimalistic support for colored output.
33Each field of an object of this class is either None if colored output
34is not possible or not desired, or a pair of strings (start, stop) such
35that outputting start switches the text color to the desired color and
36stop switches the text color back to the default."""
37 red = None
38 green = None
39 bold_red = None
40 bold_green = None
41 def __init__(self, options=None):
42 if not options or options.color in ['no', 'never']:
43 want_color = False
44 elif options.color in ['yes', 'always']:
45 want_color = True
46 else:
47 want_color = sys.stderr.isatty()
48 if want_color:
49 # Assume ANSI compatible terminal
50 normal = '\033[0m'
51 self.red = ('\033[31m', normal)
52 self.green = ('\033[32m', normal)
53 self.bold_red = ('\033[1;31m', normal)
54 self.bold_green = ('\033[1;32m', normal)
55NO_COLORS = Colors(None)
56
57def log_line(text, prefix='depends.py:', suffix='', color=None):
Gilles Peskineb39e3ec2019-01-29 08:50:20 +010058 """Print a status message."""
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +010059 if color != None:
60 prefix = color[0] + prefix
61 suffix = suffix + color[1]
62 sys.stderr.write(prefix + ' ' + text + suffix + '\n')
Gilles Peskine46c82562019-01-29 18:42:55 +010063 sys.stderr.flush()
Gilles Peskineb39e3ec2019-01-29 08:50:20 +010064
Gilles Peskine54aa5c62019-01-29 18:46:34 +010065def log_command(cmd):
66 """Print a trace of the specified command.
67cmd is a list of strings: a command name and its arguments."""
68 log_line(' '.join(cmd), prefix='+')
69
Gilles Peskineb39e3ec2019-01-29 08:50:20 +010070def backup_config(options):
Andrzej Kureke05b17f2022-09-28 03:17:56 -040071 """Back up the library configuration file (mbedtls_config.h).
Gilles Peskinebf7537d2019-01-29 18:52:16 +010072If the backup file already exists, it is presumed to be the desired backup,
73so don't make another backup."""
74 if os.path.exists(options.config_backup):
75 options.own_backup = False
76 else:
77 options.own_backup = True
78 shutil.copy(options.config, options.config_backup)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +010079
Gilles Peskinebf7537d2019-01-29 18:52:16 +010080def restore_config(options):
Andrzej Kureke05b17f2022-09-28 03:17:56 -040081 """Restore the library configuration file (mbedtls_config.h).
Gilles Peskinebf7537d2019-01-29 18:52:16 +010082Remove the backup file if it was saved earlier."""
83 if options.own_backup:
Gilles Peskineb39e3ec2019-01-29 08:50:20 +010084 shutil.move(options.config_backup, options.config)
85 else:
86 shutil.copy(options.config_backup, options.config)
Gilles Peskinebf7537d2019-01-29 18:52:16 +010087
Gilles Peskine54aa5c62019-01-29 18:46:34 +010088def run_config_pl(options, args):
89 """Run scripts/config.pl with the specified arguments."""
90 cmd = ['scripts/config.pl']
Andrzej Kureke05b17f2022-09-28 03:17:56 -040091 if options.config != 'include/mbedtls/mbedtls_config.h':
Gilles Peskine54aa5c62019-01-29 18:46:34 +010092 cmd += ['--file', options.config]
93 cmd += args
94 log_command(cmd)
95 subprocess.check_call(cmd)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +010096
97class Job:
98 """A job builds the library in a specific configuration and runs some tests."""
99 def __init__(self, name, config_settings, commands):
100 """Build a job object.
101The job uses the configuration described by config_settings. This is a
102dictionary where the keys are preprocessor symbols and the values are
103booleans or strings. A boolean indicates whether or not to #define the
104symbol. With a string, the symbol is #define'd to that value.
105After setting the configuration, the job runs the programs specified by
106commands. This is a list of lists of strings; each list of string is a
107command name and its arguments and is passed to subprocess.call with
108shell=False."""
109 self.name = name
110 self.config_settings = config_settings
111 self.commands = commands
112
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +0100113 def announce(self, colors, what):
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100114 '''Announce the start or completion of a job.
115If what is None, announce the start of the job.
116If what is True, announce that the job has passed.
117If what is False, announce that the job has failed.'''
118 if what is True:
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +0100119 log_line(self.name + ' PASSED', color=colors.green)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100120 elif what is False:
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +0100121 log_line(self.name + ' FAILED', color=colors.red)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100122 else:
123 log_line('starting ' + self.name)
124
Gilles Peskinebf7537d2019-01-29 18:52:16 +0100125 def set_reference_config(self, options):
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400126 """Change the library configuration file (mbedtls_config.h) to the reference state.
Gilles Peskinebf7537d2019-01-29 18:52:16 +0100127 The reference state is the one from which the tested configurations are
128 derived."""
129 # Turn off memory management options that are not relevant to
130 # the tests and slow them down.
131 run_config_pl(options, ['full'])
132 run_config_pl(options, ['unset', 'MBEDTLS_MEMORY_BACKTRACE'])
133 run_config_pl(options, ['unset', 'MBEDTLS_MEMORY_BUFFER_ALLOC_C'])
134 run_config_pl(options, ['unset', 'MBEDTLS_MEMORY_DEBUG'])
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100135
Gilles Peskine54aa5c62019-01-29 18:46:34 +0100136 def configure(self, options):
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100137 '''Set library configuration options as required for the job.
138config_file_name indicates which file to modify.'''
Gilles Peskinebf7537d2019-01-29 18:52:16 +0100139 self.set_reference_config(options)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100140 for key, value in sorted(self.config_settings.items()):
141 if value is True:
142 args = ['set', key]
143 elif value is False:
144 args = ['unset', key]
145 else:
146 args = ['set', key, value]
Gilles Peskine54aa5c62019-01-29 18:46:34 +0100147 run_config_pl(options, args)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100148
149 def test(self, options):
150 '''Run the job's build and test commands.
151Return True if all the commands succeed and False otherwise.
152If options.keep_going is false, stop as soon as one command fails. Otherwise
153run all the commands, except that if the first command fails, none of the
154other commands are run (typically, the first command is a build command
155and subsequent commands are tests that cannot run if the build failed).'''
156 built = False
157 success = True
158 for command in self.commands:
Gilles Peskine54aa5c62019-01-29 18:46:34 +0100159 log_command(command)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100160 ret = subprocess.call(command)
161 if ret != 0:
162 if command[0] not in ['make', options.make_command]:
163 log_line('*** [{}] Error {}'.format(' '.join(command), ret))
164 if not options.keep_going or not built:
165 return False
166 success = False
167 built = True
168 return success
169
170# SSL/TLS versions up to 1.1 and corresponding options. These require
171# both MD5 and SHA-1.
172ssl_pre_1_2_dependencies = ['MBEDTLS_SSL_CBC_RECORD_SPLITTING',
173 'MBEDTLS_SSL_PROTO_SSL3',
174 'MBEDTLS_SSL_PROTO_TLS1',
175 'MBEDTLS_SSL_PROTO_TLS1_1']
176
177# If the configuration option A requires B, make sure that
178# B in reverse_dependencies[A].
Gilles Peskine584c24a2019-01-29 19:30:40 +0100179# All the information here should be contained in check_config.h. This
180# file includes a copy because it changes rarely and it would be a pain
181# to extract automatically.
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100182reverse_dependencies = {
Gilles Peskine34a15572019-01-29 23:12:28 +0100183 'MBEDTLS_AES_C': ['MBEDTLS_CTR_DRBG_C',
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400184 'MBEDTLS_NIST_KW_C'],
Gilles Peskine34a15572019-01-29 23:12:28 +0100185 'MBEDTLS_CHACHA20_C': ['MBEDTLS_CHACHAPOLY_C'],
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400186 'MBEDTLS_ECDSA_C': ['MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED',
187 'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED'],
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100188 'MBEDTLS_ECP_C': ['MBEDTLS_ECDSA_C',
189 'MBEDTLS_ECDH_C',
190 'MBEDTLS_ECJPAKE_C',
191 'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED',
192 'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED',
193 'MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED',
194 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400195 'MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED',
196 'MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'],
Gilles Peskine584c24a2019-01-29 19:30:40 +0100197 'MBEDTLS_ECP_DP_SECP256R1_ENABLED': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'],
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100198 'MBEDTLS_MD5_C': ssl_pre_1_2_dependencies,
199 'MBEDTLS_PKCS1_V21': ['MBEDTLS_X509_RSASSA_PSS_SUPPORT'],
200 'MBEDTLS_PKCS1_V15': ['MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED',
201 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
202 'MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED',
203 'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED'],
204 'MBEDTLS_RSA_C': ['MBEDTLS_X509_RSASSA_PSS_SUPPORT',
205 'MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED',
206 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
207 'MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED',
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400208 'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED',
209 'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED'],
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100210 'MBEDTLS_SHA1_C': ssl_pre_1_2_dependencies,
Gilles Peskine584c24a2019-01-29 19:30:40 +0100211 'MBEDTLS_SHA256_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED',
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400212 'MBEDTLS_ENTROPY_FORCE_SHA256',
213 'MBEDTLS_SHA224_C',
214 'MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT',
215 'MBEDTLS_SHA256_USE_A64_CRYPTO_ONLY',
216 'MBEDTLS_SSL_PROTO_TLS1_3'],
217 'MBEDTLS_SHA512_C': ['MBEDTLS_SHA384_C',
218 'MBEDTLS_SHA512_USE_A64_CRYPTO_IF_PRESENT',
219 'MBEDTLS_SHA512_USE_A64_CRYPTO_ONLY'],
220 'MBEDTLS_SHA224_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED',
221 'MBEDTLS_ENTROPY_FORCE_SHA256',
222 'MBEDTLS_SHA256_C',
223 'MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT',
224 'MBEDTLS_SHA256_USE_A64_CRYPTO_ONLY'],
225 'MBEDTLS_SHA384_C': ['MBEDTLS_SSL_PROTO_TLS1_3'],
226 'MBEDTLS_X509_RSASSA_PSS_SUPPORT': []
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100227}
228
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400229# If an option is tested in an exclusive test, alter the following defines.
230# These are not neccesarily dependencies, but just minimal required changes
231# if a given define is the only one enabled from an exclusive group.
232exclusive_groups = {
233 'MBEDTLS_SHA224_C': ['MBEDTLS_SHA256_C'],
234 'MBEDTLS_SHA384_C': ['MBEDTLS_SHA512_C'],
235 'MBEDTLS_ECP_DP_CURVE448_ENABLED': ['!MBEDTLS_ECDSA_C',
236 '!MBEDTLS_ECDSA_DETERMINISTIC',
237 '!MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED',
238 '!MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED',
239 '!MBEDTLS_ECJPAKE_C',
240 '!MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'],
241 'MBEDTLS_ECP_DP_CURVE25519_ENABLED': ['!MBEDTLS_ECDSA_C',
242 '!MBEDTLS_ECDSA_DETERMINISTIC',
243 '!MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED',
244 '!MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED',
245 '!MBEDTLS_ECJPAKE_C',
246 '!MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'],
247 'MBEDTLS_ARIA_C': ['!MBEDTLS_CMAC_C'],
248 'MBEDTLS_CAMELLIA_C': ['!MBEDTLS_CMAC_C'],
249 'MBEDTLS_CHACHA20_C': ['!MBEDTLS_CMAC_C', '!MBEDTLS_CCM_C', '!MBEDTLS_GCM_C'],
250 'MBEDTLS_DES_C': ['!MBEDTLS_CCM_C', '!MBEDTLS_GCM_C'],
251}
252def handle_exclusive_groups(config_settings, symbol):
253 """For every symbol tested in an exclusive group check if there are other
254defines to be altered. """
255 for dep in exclusive_groups.get(symbol, []):
256 unset = dep.startswith('!')
257 if unset:
258 dep=dep[1:]
259 config_settings[dep] = not unset
260
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100261def turn_off_dependencies(config_settings):
262 """For every option turned off config_settings, also turn off what depends on it.
263An option O is turned off if config_settings[O] is False."""
264 for key, value in sorted(config_settings.items()):
265 if value is not False:
266 continue
267 for dep in reverse_dependencies.get(key, []):
268 config_settings[dep] = False
269
270class Domain:
271 """A domain is a set of jobs that all relate to a particular configuration aspect."""
272 pass
273
274class ExclusiveDomain(Domain):
275 """A domain consisting of a set of conceptually-equivalent settings.
276Establish a list of configuration symbols. For each symbol, run a test job
277with this symbol set and the others unset, and a test job with this symbol
278unset and the others set."""
Gilles Peskineb1284cf2019-01-29 18:56:03 +0100279 def __init__(self, symbols, commands, exclude=None):
280 """Build a domain for the specified list of configuration symbols.
281The domain contains two sets of jobs: jobs that enable one of the elements
282of symbols and disable the others, and jobs that disable one of the elements
283of symbols and enable the others.
284Each job runs the specified commands.
285If exclude is a regular expression, skip generated jobs whose description
286would match this regular expression."""
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100287 self.jobs = []
288 for invert in [False, True]:
289 base_config_settings = {}
290 for symbol in symbols:
291 base_config_settings[symbol] = invert
292 for symbol in symbols:
293 description = '!' + symbol if invert else symbol
Gilles Peskineb1284cf2019-01-29 18:56:03 +0100294 if exclude and re.match(exclude, description):
295 continue
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100296 config_settings = base_config_settings.copy()
297 config_settings[symbol] = not invert
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400298 if not invert:
299 handle_exclusive_groups(config_settings, symbol)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100300 turn_off_dependencies(config_settings)
301 job = Job(description, config_settings, commands)
302 self.jobs.append(job)
303
304class ComplementaryDomain:
305 """A domain consisting of a set of loosely-related settings.
306Establish a list of configuration symbols. For each symbol, run a test job
307with this symbol unset."""
308 def __init__(self, symbols, commands):
Gilles Peskineb1284cf2019-01-29 18:56:03 +0100309 """Build a domain for the specified list of configuration symbols.
310Each job in the domain disables one of the specified symbols.
311Each job runs the specified commands."""
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100312 self.jobs = []
313 for symbol in symbols:
314 description = '!' + symbol
315 config_settings = {symbol: False}
316 turn_off_dependencies(config_settings)
317 job = Job(description, config_settings, commands)
318 self.jobs.append(job)
319
Gilles Peskine34a15572019-01-29 23:12:28 +0100320class CipherInfo:
321 """Collect data about cipher.h."""
322 def __init__(self, options):
323 self.base_symbols = set()
324 with open('include/mbedtls/cipher.h') as fh:
325 for line in fh:
326 m = re.match(r' *MBEDTLS_CIPHER_ID_(\w+),', line)
327 if m and m.group(1) not in ['NONE', 'NULL', '3DES']:
328 self.base_symbols.add('MBEDTLS_' + m.group(1) + '_C')
329
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100330class DomainData:
331 """Collect data about the library."""
332 def collect_config_symbols(self, options):
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400333 """Read the list of settings from mbedtls_config.h.
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100334Return them in a generator."""
335 with open(options.config) as config_file:
336 rx = re.compile(r'\s*(?://\s*)?#define\s+(\w+)\s*(?:$|/[/*])')
337 for line in config_file:
338 m = re.match(rx, line)
339 if m:
340 yield m.group(1)
341
342 def config_symbols_matching(self, regexp):
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400343 """List the mbedtls_config.h settings matching regexp."""
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100344 return [symbol for symbol in self.all_config_symbols
345 if re.match(regexp, symbol)]
346
347 def __init__(self, options):
348 """Gather data about the library and establish a list of domains to test."""
349 build_command = [options.make_command, 'CFLAGS=-Werror']
350 build_and_test = [build_command, [options.make_command, 'test']]
351 self.all_config_symbols = set(self.collect_config_symbols(options))
352 # Find hash modules by name.
353 hash_symbols = self.config_symbols_matching(r'MBEDTLS_(MD|RIPEMD|SHA)[0-9]+_C\Z')
354 # Find elliptic curve enabling macros by name.
355 curve_symbols = self.config_symbols_matching(r'MBEDTLS_ECP_DP_\w+_ENABLED\Z')
356 # Find key exchange enabling macros by name.
357 key_exchange_symbols = self.config_symbols_matching(r'MBEDTLS_KEY_EXCHANGE_\w+_ENABLED\Z')
Gilles Peskine34a15572019-01-29 23:12:28 +0100358 # Find cipher IDs (block permutations and stream ciphers --- chaining
359 # and padding modes are exercised separately) information by parsing
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400360 # cipher.h, as the information is not readily available in mbedtls_config.h.
361
Gilles Peskine34a15572019-01-29 23:12:28 +0100362 cipher_info = CipherInfo(options)
363 # Find block cipher chaining and padding mode enabling macros by name.
364 cipher_chaining_symbols = self.config_symbols_matching(r'MBEDTLS_CIPHER_MODE_\w+\Z')
365 cipher_padding_symbols = self.config_symbols_matching(r'MBEDTLS_CIPHER_PADDING_\w+\Z')
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100366 self.domains = {
Gilles Peskine34a15572019-01-29 23:12:28 +0100367 # Cipher IDs, chaining modes and padding modes. Run the test suites.
368 'cipher_id': ExclusiveDomain(cipher_info.base_symbols,
369 build_and_test),
370 'cipher_chaining': ExclusiveDomain(cipher_chaining_symbols,
371 build_and_test),
372 'cipher_padding': ExclusiveDomain(cipher_padding_symbols,
373 build_and_test),
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100374 # Elliptic curves. Run the test suites.
375 'curves': ExclusiveDomain(curve_symbols, build_and_test),
376 # Hash algorithms. Exclude configurations with only one
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400377 # hash which is obsolete. Run the test suites. Exclude
378 # SHA512 and SHA256, as these are tested with SHA384 and SHA224.
Gilles Peskineb1284cf2019-01-29 18:56:03 +0100379 'hashes': ExclusiveDomain(hash_symbols, build_and_test,
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400380 exclude=r'MBEDTLS_(MD|RIPEMD|SHA1_|SHA256_|SHA512_)|!MBEDTLS_(SHA256_|SHA512_)'),
Gilles Peskinec3b4dee2019-01-29 19:33:05 +0100381 # Key exchange types. Only build the library and the sample
382 # programs.
383 'kex': ExclusiveDomain(key_exchange_symbols,
384 [build_command + ['lib'],
385 build_command + ['-C', 'programs']]),
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100386 'pkalgs': ComplementaryDomain(['MBEDTLS_ECDSA_C',
387 'MBEDTLS_ECP_C',
388 'MBEDTLS_PKCS1_V21',
389 'MBEDTLS_PKCS1_V15',
390 'MBEDTLS_RSA_C',
391 'MBEDTLS_X509_RSASSA_PSS_SUPPORT'],
392 build_and_test),
393 }
394 self.jobs = {}
395 for domain in self.domains.values():
396 for job in domain.jobs:
397 self.jobs[job.name] = job
398
399 def get_jobs(self, name):
400 """Return the list of jobs identified by the given name.
401A name can either be the name of a domain or the name of one specific job."""
402 if name in self.domains:
403 return sorted(self.domains[name].jobs, key=lambda job: job.name)
404 else:
405 return [self.jobs[name]]
406
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +0100407def run(options, job, colors=NO_COLORS):
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100408 """Run the specified job (a Job instance)."""
409 subprocess.check_call([options.make_command, 'clean'])
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +0100410 job.announce(colors, None)
Gilles Peskine54aa5c62019-01-29 18:46:34 +0100411 job.configure(options)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100412 success = job.test(options)
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +0100413 job.announce(colors, success)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100414 return success
415
416def main(options, domain_data):
417 """Run the desired jobs.
418domain_data should be a DomainData instance that describes the available
419domains and jobs.
420Run the jobs listed in options.domains."""
421 if not hasattr(options, 'config_backup'):
422 options.config_backup = options.config + '.bak'
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +0100423 colors = Colors(options)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100424 jobs = []
425 failures = []
426 successes = []
427 for name in options.domains:
428 jobs += domain_data.get_jobs(name)
429 backup_config(options)
430 try:
431 for job in jobs:
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +0100432 success = run(options, job, colors=colors)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100433 if not success:
434 if options.keep_going:
435 failures.append(job.name)
436 else:
437 return False
438 else:
439 successes.append(job.name)
Gilles Peskinebf7537d2019-01-29 18:52:16 +0100440 restore_config(options)
441 except:
442 # Restore the configuration, except in stop-on-error mode if there
443 # was an error, where we leave the failing configuration up for
444 # developer convenience.
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100445 if options.keep_going:
Gilles Peskinebf7537d2019-01-29 18:52:16 +0100446 restore_config(options)
447 raise
Gilles Peskinee85163b2019-01-29 18:50:03 +0100448 if successes:
449 log_line('{} passed'.format(' '.join(successes)), color=colors.bold_green)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100450 if failures:
Gilles Peskinee85163b2019-01-29 18:50:03 +0100451 log_line('{} FAILED'.format(' '.join(failures)), color=colors.bold_red)
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100452 return False
453 else:
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100454 return True
455
456
457if __name__ == '__main__':
458 try:
459 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0fa7cbe2019-01-29 18:48:48 +0100460 parser.add_argument('--color', metavar='WHEN',
461 help='Colorize the output (always/auto/never)',
462 choices=['always', 'auto', 'never'], default='auto')
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100463 parser.add_argument('-c', '--config', metavar='FILE',
464 help='Configuration file to modify',
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400465 default='include/mbedtls/mbedtls_config.h')
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100466 parser.add_argument('-C', '--directory', metavar='DIR',
467 help='Change to this directory before anything else',
468 default='.')
469 parser.add_argument('-k', '--keep-going',
470 help='Try all configurations even if some fail (default)',
471 action='store_true', dest='keep_going', default=True)
472 parser.add_argument('-e', '--no-keep-going',
473 help='Stop as soon as a configuration fails',
474 action='store_false', dest='keep_going')
475 parser.add_argument('--list-jobs',
476 help='List supported jobs and exit',
477 action='append_const', dest='list', const='jobs')
478 parser.add_argument('--list-domains',
479 help='List supported domains and exit',
480 action='append_const', dest='list', const='domains')
481 parser.add_argument('--make-command', metavar='CMD',
482 help='Command to run instead of make (e.g. gmake)',
483 action='store', default='make')
484 parser.add_argument('domains', metavar='DOMAIN', nargs='*',
Andrzej Kureke05b17f2022-09-28 03:17:56 -0400485 help='The domain(s) to test (default: all). This can be also a list of jobs to run.',
Gilles Peskineb39e3ec2019-01-29 08:50:20 +0100486 default=True)
487 options = parser.parse_args()
488 os.chdir(options.directory)
489 domain_data = DomainData(options)
490 if options.domains == True:
491 options.domains = sorted(domain_data.domains.keys())
492 if options.list:
493 for what in options.list:
494 for key in sorted(getattr(domain_data, what).keys()):
495 print(key)
496 exit(0)
497 else:
498 sys.exit(0 if main(options, domain_data) else 1)
499 except SystemExit:
500 raise
501 except:
502 traceback.print_exc()
503 exit(3)