quickjs-tart

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

test_case.py (5899B)


      1 """Library for constructing an Mbed TLS test case.
      2 """
      3 
      4 # Copyright The Mbed TLS Contributors
      5 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
      6 #
      7 
      8 import binascii
      9 import os
     10 import sys
     11 from typing import Iterable, List, Optional
     12 from enum import Enum
     13 
     14 from . import build_tree
     15 from . import psa_information
     16 from . import typing_util
     17 
     18 HASHES_3_6 = {
     19     "PSA_ALG_MD5" : "MBEDTLS_MD_CAN_MD5",
     20     "PSA_ALG_RIPEMD160" : "MBEDTLS_MD_CAN_RIPEMD160",
     21     "PSA_ALG_SHA_1" : "MBEDTLS_MD_CAN_SHA1",
     22     "PSA_ALG_SHA_224" : "MBEDTLS_MD_CAN_SHA224",
     23     "PSA_ALG_SHA_256" : "MBEDTLS_MD_CAN_SHA256",
     24     "PSA_ALG_SHA_384" : "MBEDTLS_MD_CAN_SHA384",
     25     "PSA_ALG_SHA_512" : "MBEDTLS_MD_CAN_SHA512",
     26     "PSA_ALG_SHA3_224" : "MBEDTLS_MD_CAN_SHA3_224",
     27     "PSA_ALG_SHA3_256" : "MBEDTLS_MD_CAN_SHA3_256",
     28     "PSA_ALG_SHA3_384" : "MBEDTLS_MD_CAN_SHA3_384",
     29     "PSA_ALG_SHA3_512" : "MBEDTLS_MD_CAN_SHA3_512"
     30 }
     31 
     32 PK_MACROS_3_6 = {
     33     "PSA_KEY_TYPE_ECC_PUBLIC_KEY" : "MBEDTLS_PK_HAVE_ECC_KEYS"
     34 }
     35 
     36 class Domain36(Enum):
     37     PSA = 1
     38     TLS_1_3_ONLY = 2
     39     USE_PSA = 3
     40     LEGACY = 4
     41 
     42 def hex_string(data: bytes) -> str:
     43     return '"' + binascii.hexlify(data).decode('ascii') + '"'
     44 
     45 class MissingDescription(Exception):
     46     pass
     47 
     48 class MissingFunction(Exception):
     49     pass
     50 
     51 class TestCase:
     52     """An Mbed TLS test case."""
     53 
     54     def __init__(self, description: Optional[str] = None):
     55         self.comments = [] #type: List[str]
     56         self.description = description #type: Optional[str]
     57         self.dependencies = [] #type: List[str]
     58         self.function = None #type: Optional[str]
     59         self.arguments = [] #type: List[str]
     60         self.skip_reasons = [] #type: List[str]
     61 
     62     def add_comment(self, *lines: str) -> None:
     63         self.comments += lines
     64 
     65     def set_description(self, description: str) -> None:
     66         self.description = description
     67 
     68     def get_dependencies(self) -> List[str]:
     69         return self.dependencies
     70 
     71     def set_dependencies(self, dependencies: List[str]) -> None:
     72         self.dependencies = dependencies
     73 
     74     def set_function(self, function: str) -> None:
     75         self.function = function
     76 
     77     def set_arguments(self, arguments: List[str]) -> None:
     78         self.arguments = arguments
     79 
     80     def skip_because(self, reason: str) -> None:
     81         """Skip this test case.
     82 
     83         It will be included in the output, but commented out.
     84 
     85         This is intended for test cases that are obtained from a
     86         systematic enumeration, but that have dependencies that cannot
     87         be fulfilled. Since we don't want to have test cases that are
     88         never executed, we arrange not to have actual test cases. But
     89         we do include comments to make it easier to understand the output
     90         of test case generation.
     91 
     92         reason must be a non-empty string explaining to humans why this
     93         test case is skipped.
     94         """
     95         self.skip_reasons.append(reason)
     96 
     97     def check_completeness(self) -> None:
     98         if self.description is None:
     99             raise MissingDescription
    100         if self.function is None:
    101             raise MissingFunction
    102 
    103     def write(self, out: typing_util.Writable) -> None:
    104         """Write the .data file paragraph for this test case.
    105 
    106         The output starts and ends with a single newline character. If the
    107         surrounding code writes lines (consisting of non-newline characters
    108         and a final newline), you will end up with a blank line before, but
    109         not after the test case.
    110         """
    111         self.check_completeness()
    112         assert self.description is not None # guide mypy
    113         assert self.function is not None # guide mypy
    114         out.write('\n')
    115         for line in self.comments:
    116             out.write('# ' + line + '\n')
    117         prefix = ''
    118         if self.skip_reasons:
    119             prefix = '## '
    120             for reason in self.skip_reasons:
    121                 out.write('## # skipped because: ' + reason + '\n')
    122         out.write(prefix + self.description + '\n')
    123         dependencies = self.get_dependencies()
    124         if dependencies:
    125             out.write(prefix + 'depends_on:' +
    126                       ':'.join(dependencies) + '\n')
    127         out.write(prefix + self.function + ':' +
    128                   ':'.join(self.arguments) + '\n')
    129 
    130 def write_data_file(filename: str,
    131                     test_cases: Iterable[TestCase],
    132                     caller: Optional[str] = None) -> None:
    133     """Write the test cases to the specified file.
    134 
    135     If the file already exists, it is overwritten.
    136     """
    137     if caller is None:
    138         caller = os.path.basename(sys.argv[0])
    139     tempfile = filename + '.new'
    140     with open(tempfile, 'w') as out:
    141         out.write('# Automatically generated by {}. Do not edit!\n'
    142                   .format(caller))
    143         for tc in test_cases:
    144             tc.write(out)
    145         out.write('\n# End of automatically generated file.\n')
    146     os.replace(tempfile, filename)
    147 
    148 def psa_or_3_6_feature_macro(psa_name: str,
    149                              domain_3_6: Domain36) -> str:
    150     """Determine the dependency symbol for a given psa_name based on
    151        the domain and Mbed TLS version. For more information about the domains,
    152        and MBEDTLS_MD_CAN_ prefixed symbols, see transition-guards.md.
    153        This function currently works with hashes and some PK symbols only.
    154        It accepts PSA_ALG_xxx or PSA_KEY_TYPE_xxx as inputs for psa_name.
    155     """
    156 
    157     if domain_3_6 == Domain36.PSA or domain_3_6 == Domain36.TLS_1_3_ONLY or \
    158         not build_tree.is_mbedtls_3_6():
    159         if psa_name in PK_MACROS_3_6 or psa_name in HASHES_3_6:
    160             return psa_information.psa_want_symbol(psa_name)
    161 
    162     if domain_3_6 == Domain36.USE_PSA:
    163         if psa_name in PK_MACROS_3_6:
    164             return PK_MACROS_3_6[psa_name]
    165 
    166     if psa_name in HASHES_3_6:
    167         return HASHES_3_6[psa_name]
    168 
    169     raise ValueError(f'Unable to determine dependency symbol for {psa_name} in {domain_3_6}')