generate_config_tests.py (8504B)
1 #!/usr/bin/env python3 2 """Generate test data for configuration reporting. 3 """ 4 5 # Copyright The Mbed TLS Contributors 6 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 7 8 import inspect 9 import re 10 import sys 11 from typing import Iterable, Iterator, List, Optional, Tuple 12 13 import project_scripts # pylint: disable=unused-import 14 import config 15 from mbedtls_framework import build_tree 16 from mbedtls_framework import config_common 17 from mbedtls_framework import test_case 18 from mbedtls_framework import test_data_generation 19 20 21 def single_setting_case(setting: config_common.Setting, when_on: bool, 22 dependencies: List[str], 23 note: Optional[str]) -> test_case.TestCase: 24 """Construct a test case for a boolean setting. 25 26 This test case passes if the setting and its dependencies are enabled, 27 and is skipped otherwise. 28 29 * setting: the setting to be tested. 30 * when_on: True to test with the setting enabled, or False to test 31 with the setting disabled. 32 * dependencies: extra dependencies for the test case. 33 * note: a note to add after the setting name in the test description. 34 This is generally a summary of dependencies, and is generally empty 35 if the given setting is only tested once. 36 """ 37 base = setting.name if when_on else '!' + setting.name 38 tc = test_case.TestCase() 39 tc.set_function('pass') 40 description_suffix = ' (' + note + ')' if note else '' 41 tc.set_description('Config: ' + base + description_suffix) 42 tc.set_dependencies([base] + dependencies) 43 return tc 44 45 46 PSA_WANT_KEY_TYPE_KEY_PAIR_RE = \ 47 re.compile(r'(?P<prefix>PSA_WANT_KEY_TYPE_(?P<type>\w+)_KEY_PAIR_)(?P<operation>\w+)\Z') 48 49 # If foo is a setting that is only meaningful when bar is enabled, set 50 # SIMPLE_DEPENDENCIES[foo]=bar. More generally, bar can be a colon-separated 51 # list of settings, meaning that all the settings must be enabled. Each setting 52 # in bar can be prefixed with '!' to negate it. This is the same syntax as a 53 # depends_on directive in test data. 54 # See also `dependencies_of_settting`. 55 SIMPLE_DEPENDENCIES = { 56 'MBEDTLS_AESCE_C': 'MBEDTLS_AES_C', 57 'MBEDTLS_AESNI_C': 'MBEDTLS_AES_C', 58 'MBEDTLS_ERROR_STRERROR_DUMMY': '!MBEDTLS_ERROR_C', 59 'MBEDTLS_GENPRIME': 'MBEDTLS_RSA_C', 60 'MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES': 'MBEDTLS_ENTROPY_C', 61 'MBEDTLS_PKCS1_V15': 'MBEDTLS_RSA_C', 62 'MBEDTLS_PKCS1_V21': 'MBEDTLS_RSA_C', 63 'MBEDTLS_PSA_CRYPTO_CLIENT': '!MBEDTLS_PSA_CRYPTO_C', 64 'MBEDTLS_PSA_INJECT_ENTROPY': 'MBEDTLS_PSA_CRYPTO_C', 65 'MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS': 'MBEDTLS_PSA_CRYPTO_C', 66 } 67 68 if build_tree.is_mbedtls_3_6(): 69 SIMPLE_DEPENDENCIES['MBEDTLS_NO_PLATFORM_ENTROPY'] = 'MBEDTLS_ENTROPY_C' 70 71 def dependencies_of_setting(cfg: config_common.Config, 72 setting: config_common.Setting) -> Optional[str]: 73 """Return dependencies without which a setting is not meaningful. 74 75 The dependencies of a setting express when a setting can be enabled and 76 is relevant. For example, if ``check_config.h`` errors out when 77 ``defined(FOO) && !defined(BAR)``, then ``BAR`` is a dependency of ``FOO``. 78 If ``FOO`` has no effect when ``CORGE`` is disabled, then ``CORGE`` 79 is a dependency of ``FOO``. 80 81 The return value can be a colon-separated list of settings, if the setting 82 is only meaningful when all of these settings are enabled. Each setting can 83 be negated by prefixing them with '!'. This is the same syntax as a 84 depends_on directive in test data. 85 """ 86 #pylint: disable=too-many-branches,too-many-return-statements 87 name = setting.name 88 if name in SIMPLE_DEPENDENCIES: 89 return SIMPLE_DEPENDENCIES[name] 90 if name.startswith('MBEDTLS_') and not name.endswith('_C'): 91 if name.startswith('MBEDTLS_CIPHER_PADDING_'): 92 return 'MBEDTLS_CIPHER_C:MBEDTLS_CIPHER_MODE_CBC' 93 if name.startswith('MBEDTLS_PK_PARSE_EC_'): 94 return 'MBEDTLS_PK_C:' + test_case.psa_or_3_6_feature_macro( 95 'PSA_KEY_TYPE_ECC_PUBLIC_KEY', test_case.Domain36.USE_PSA) 96 97 # For TLS settings, insist on having them once off and once on in 98 # a configuration where both client support and server support are 99 # enabled. The settings are also meaningful when only one side is 100 # enabled, but there isn't much point in having separate records 101 # for client-side and server-side, so we keep things simple. 102 # Requiring both sides to be enabled also means we know we'll run 103 # tests that only run Mbed TLS against itself, which only run in 104 # configurations with both sides enabled. 105 if name.startswith('MBEDTLS_SSL_TLS1_3_') or \ 106 name == 'MBEDTLS_SSL_EARLY_DATA': 107 return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_PROTO_TLS1_3' 108 if name.startswith('MBEDTLS_SSL_DTLS_'): 109 return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_PROTO_DTLS' 110 if name.startswith('MBEDTLS_SSL_'): 111 return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C' 112 for pos in re.finditer(r'_', name): 113 super_name = name[:pos.start()] + '_C' 114 if cfg.known(super_name): 115 return super_name 116 if name.startswith('PSA_WANT_'): 117 deps = 'MBEDTLS_PSA_CRYPTO_CLIENT' 118 m = PSA_WANT_KEY_TYPE_KEY_PAIR_RE.match(name) 119 if m and m.group('operation') != 'BASIC': 120 deps += ':' + m.group('prefix') + 'BASIC' 121 return deps 122 return None 123 124 def conditions_for_setting(cfg: config_common.Config, 125 setting: config_common.Setting 126 ) -> Iterator[Tuple[List[str], str]]: 127 """Enumerate the conditions under which to test the given setting. 128 129 * cfg: all configuration settings. 130 * setting: the setting to be tested. 131 132 Generate a stream of conditions, i.e. extra dependencies to test with 133 together with a human-readable explanation of each dependency. Some 134 typical cases: 135 136 * By default, generate a one-element stream with no extra dependencies. 137 * If the setting is ignored unless some other setting is enabled, generate 138 a one-element stream with that other setting as an extra dependency. 139 * If the setting is known to interact with some other setting, generate 140 a stream with one element where this setting is on and one where it's off. 141 * To skip the setting altogether, generate an empty stream. 142 """ 143 name = setting.name 144 if name.endswith('_ALT') and not config.is_seamless_alt(name): 145 # We don't test alt implementations, except (most) platform alts 146 return 147 dependencies = dependencies_of_setting(cfg, setting) 148 if dependencies: 149 yield [dependencies], '' 150 return 151 yield [], '' 152 153 154 def enumerate_boolean_setting_cases(cfg: config_common.Config 155 ) -> Iterable[test_case.TestCase]: 156 """Emit test cases for all boolean settings.""" 157 for name in sorted(cfg.settings.keys()): 158 setting = cfg.settings[name] 159 if not name.startswith('PSA_WANT_') and setting.value: 160 continue # non-boolean setting 161 for when_on in True, False: 162 for deps, note in conditions_for_setting(cfg, setting): 163 yield single_setting_case(setting, when_on, deps, note) 164 165 166 167 class ConfigTestGenerator(test_data_generation.TestGenerator): 168 """Generate test cases for configuration reporting.""" 169 170 def __init__(self, settings): 171 # pylint: disable=no-member 172 config_members = dict(inspect.getmembers(config)) 173 if 'MbedTLSConfig' in config_members: 174 self.mbedtls_config = config.MbedTLSConfig() 175 self.targets['test_suite_config.mbedtls_boolean'] = \ 176 lambda: enumerate_boolean_setting_cases(self.mbedtls_config) 177 if 'CryptoConfig' in config_members: 178 if build_tree.is_mbedtls_3_6(): 179 self.psa_config = config.CryptoConfig() 180 self.targets['test_suite_config.psa_boolean'] = \ 181 lambda: enumerate_boolean_setting_cases(self.psa_config) 182 elif 'TFPSACryptoConfig' in config_members: 183 self.psa_config = config.TFPSACryptoConfig() 184 self.targets['test_suite_config.psa_boolean'] = \ 185 lambda: enumerate_boolean_setting_cases(self.psa_config) 186 super().__init__(settings) 187 188 189 if __name__ == '__main__': 190 test_data_generation.main(sys.argv[1:], __doc__, ConfigTestGenerator)