quickjs-tart

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

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)