diff --git a/CMakeLists.txt b/CMakeLists.txt index 306cf0261d..e47667545a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,11 +323,11 @@ if(ENABLE_TESTING OR ENABLE_PROGRAMS) ${CMAKE_CURRENT_SOURCE_DIR}/tests COMMAND "${MBEDTLS_PYTHON_EXECUTABLE}" - "${CMAKE_CURRENT_SOURCE_DIR}/tests/scripts/generate_test_keys.py" + "${CMAKE_CURRENT_SOURCE_DIR}/framework/scripts/generate_test_keys.py" "--output" "${CMAKE_CURRENT_SOURCE_DIR}/tests/src/test_keys.h" DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/tests/scripts/generate_test_keys.py + ${CMAKE_CURRENT_SOURCE_DIR}/framework/scripts/generate_test_keys.py ) add_custom_target(test_keys_header DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tests/src/test_keys.h) add_custom_command( @@ -337,11 +337,11 @@ if(ENABLE_TESTING OR ENABLE_PROGRAMS) ${CMAKE_CURRENT_SOURCE_DIR}/tests COMMAND "${MBEDTLS_PYTHON_EXECUTABLE}" - "${CMAKE_CURRENT_SOURCE_DIR}/tests/scripts/generate_test_cert_macros.py" + "${CMAKE_CURRENT_SOURCE_DIR}/framework/scripts/generate_test_cert_macros.py" "--output" "${CMAKE_CURRENT_SOURCE_DIR}/tests/src/test_certs.h" DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/tests/scripts/generate_test_cert_macros.py + ${CMAKE_CURRENT_SOURCE_DIR}/framework/scripts/generate_test_cert_macros.py ) add_custom_target(test_certs_header DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tests/src/test_certs.h) add_dependencies(mbedtls_test test_keys_header test_certs_header) diff --git a/docs/architecture/psa-crypto-implementation-structure.md b/docs/architecture/psa-crypto-implementation-structure.md index 7e0e37d251..0954602cb6 100644 --- a/docs/architecture/psa-crypto-implementation-structure.md +++ b/docs/architecture/psa-crypto-implementation-structure.md @@ -153,7 +153,7 @@ The size of operation structures needs to be known at compile time, since caller ### Unit tests -A number of unit tests are automatically generated by `tests/scripts/generate_psa_tests.py` based on the algorithms and key types declared in `include/psa/crypto_values.h` and `include/psa/crypto_extra.h`: +A number of unit tests are automatically generated by `framework/scripts/generate_psa_tests.py` based on the algorithms and key types declared in `include/psa/crypto_values.h` and `include/psa/crypto_extra.h`: * Attempt to create a key with a key type that is not supported. * Attempt to perform an operation with a combination of key type and algorithm that is not valid or not supported. diff --git a/docs/architecture/psa-shared-memory.md b/docs/architecture/psa-shared-memory.md index ef3a6b09de..283ffc6265 100644 --- a/docs/architecture/psa-shared-memory.md +++ b/docs/architecture/psa-shared-memory.md @@ -663,7 +663,7 @@ psa_status_t mem_poison_psa_aead_update(psa_aead_operation_t *operation, There now exists a more generic mechanism for making exactly this kind of transformation - the PSA test wrappers, which exist in the files `tests/include/test/psa_test_wrappers.h` and `tests/src/psa_test_wrappers.c`. These are wrappers around all PSA functions that allow testing code to be inserted at the start and end of a PSA function call. -The test wrappers are generated by a script, although they are not automatically generated as part of the build process. Instead, they are checked into source control and must be manually updated when functions change by running `tests/scripts/generate_psa_wrappers.py`. +The test wrappers are generated by a script, although they are not automatically generated as part of the build process. Instead, they are checked into source control and must be manually updated when functions change by running `framework/scripts/generate_psa_wrappers.py`. Poisoning code is added to these test wrappers where relevant in order to pre-poison and post-unpoison the parameters to the functions. diff --git a/framework b/framework index e156a8eb8e..623c1b4532 160000 --- a/framework +++ b/framework @@ -1 +1 @@ -Subproject commit e156a8eb8e6db88cdf0a3041fc7f645131eab16d +Subproject commit 623c1b4532e8de64a5d82ea84a7496e64c370d15 diff --git a/scripts/abi_check.py b/scripts/abi_check.py index ec0d4730df..f91d80e504 100755 --- a/scripts/abi_check.py +++ b/scripts/abi_check.py @@ -326,8 +326,14 @@ class AbiChecker: @staticmethod def _list_generated_test_data_files(git_worktree_path): """List the generated test data files.""" + generate_psa_tests = 'framework/scripts/generate_psa_tests.py' + if not os.path.isfile(git_worktree_path + '/' + generate_psa_tests): + # The checked-out revision is from before generate_psa_tests.py + # was moved to the framework submodule. Use the old location. + generate_psa_tests = 'tests/scripts/generate_psa_tests.py' + output = subprocess.check_output( - ['tests/scripts/generate_psa_tests.py', '--list'], + [generate_psa_tests, '--list'], cwd=git_worktree_path, ).decode('ascii') return [line for line in output.split('\n') if line] @@ -353,8 +359,14 @@ class AbiChecker: if 'storage_format' in filename: storage_data_files.add(filename) to_be_generated.add(filename) + + generate_psa_tests = 'framework/scripts/generate_psa_tests.py' + if not os.path.isfile(git_worktree_path + '/' + generate_psa_tests): + # The checked-out revision is from before generate_psa_tests.py + # was moved to the framework submodule. Use the old location. + generate_psa_tests = 'tests/scripts/generate_psa_tests.py' subprocess.check_call( - ['tests/scripts/generate_psa_tests.py'] + sorted(to_be_generated), + [generate_psa_tests] + sorted(to_be_generated), cwd=git_worktree_path, ) for test_file in sorted(storage_data_files): diff --git a/scripts/make_generated_files.bat b/scripts/make_generated_files.bat index 11bcb1ae0b..f04f6b72a9 100644 --- a/scripts/make_generated_files.bat +++ b/scripts/make_generated_files.bat @@ -10,8 +10,8 @@ perl scripts\generate_features.pl || exit /b 1 python scripts\generate_ssl_debug_helpers.py || exit /b 1 perl scripts\generate_visualc_files.pl || exit /b 1 python scripts\generate_psa_constants.py || exit /b 1 -python tests\scripts\generate_bignum_tests.py || exit /b 1 -python tests\scripts\generate_ecp_tests.py || exit /b 1 -python tests\scripts\generate_psa_tests.py || exit /b 1 -python tests\scripts\generate_test_keys.py --output tests\src\test_keys.h || exit /b 1 -python tests\scripts\generate_test_cert_macros.py --output tests\src\test_certs.h || exit /b 1 +python framework\scripts\generate_bignum_tests.py || exit /b 1 +python framework\scripts\generate_ecp_tests.py || exit /b 1 +python framework\scripts\generate_psa_tests.py || exit /b 1 +python framework\scripts\generate_test_keys.py --output tests\src\test_keys.h || exit /b 1 +python framework\scripts\generate_test_cert_macros.py --output tests\src\test_certs.h || exit /b 1 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ffe3cc85ae..5bc38b4e70 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,7 +21,7 @@ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/suites) execute_process( COMMAND ${MBEDTLS_PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_bignum_tests.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_bignum_tests.py --list-for-cmake WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.. @@ -33,7 +33,7 @@ string(REGEX REPLACE "[^;]*/" "" execute_process( COMMAND ${MBEDTLS_PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_ecp_tests.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_ecp_tests.py --list-for-cmake WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.. @@ -45,7 +45,7 @@ string(REGEX REPLACE "[^;]*/" "" execute_process( COMMAND ${MBEDTLS_PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_psa_tests.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_psa_tests.py --list-for-cmake WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.. @@ -81,10 +81,10 @@ if(GEN_FILES) ${CMAKE_CURRENT_SOURCE_DIR}/.. COMMAND ${MBEDTLS_PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_bignum_tests.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_bignum_tests.py --directory ${CMAKE_CURRENT_BINARY_DIR}/suites DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_bignum_tests.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_bignum_tests.py ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/bignum_common.py ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/bignum_core.py ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/bignum_mod_raw.py @@ -99,10 +99,10 @@ if(GEN_FILES) ${CMAKE_CURRENT_SOURCE_DIR}/.. COMMAND ${MBEDTLS_PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_ecp_tests.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_ecp_tests.py --directory ${CMAKE_CURRENT_BINARY_DIR}/suites DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_ecp_tests.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_ecp_tests.py ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/bignum_common.py ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/ecp.py ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/test_case.py @@ -115,10 +115,10 @@ if(GEN_FILES) ${CMAKE_CURRENT_SOURCE_DIR}/.. COMMAND ${MBEDTLS_PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_psa_tests.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_psa_tests.py --directory ${CMAKE_CURRENT_BINARY_DIR}/suites DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_psa_tests.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_psa_tests.py ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/crypto_data_tests.py ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/crypto_knowledge.py ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/macro_collector.py @@ -220,7 +220,7 @@ function(add_test_suite suite_name) test_suite_${data_name}.c COMMAND ${MBEDTLS_PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_test_code.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_test_code.py -f ${CMAKE_CURRENT_SOURCE_DIR}/suites/test_suite_${suite_name}.function -d ${data_file} -t ${CMAKE_CURRENT_SOURCE_DIR}/suites/main_test.function @@ -229,7 +229,7 @@ function(add_test_suite suite_name) --helpers-file ${CMAKE_CURRENT_SOURCE_DIR}/suites/helpers.function -o . DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_test_code.py + ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_test_code.py ${CMAKE_CURRENT_SOURCE_DIR}/suites/test_suite_${suite_name}.function ${data_file} ${CMAKE_CURRENT_SOURCE_DIR}/suites/main_test.function diff --git a/tests/Makefile b/tests/Makefile index 3d7a605ab7..d71c2e32b3 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -18,25 +18,25 @@ endif .PHONY: generated_files GENERATED_BIGNUM_DATA_FILES := $(patsubst tests/%,%,$(shell \ - $(PYTHON) scripts/generate_bignum_tests.py --list || \ + $(PYTHON) ../framework/scripts/generate_bignum_tests.py --list || \ echo FAILED \ )) ifeq ($(GENERATED_BIGNUM_DATA_FILES),FAILED) -$(error "$(PYTHON) scripts/generate_bignum_tests.py --list" failed) +$(error "$(PYTHON) ../framework/scripts/generate_bignum_tests.py --list" failed) endif GENERATED_ECP_DATA_FILES := $(patsubst tests/%,%,$(shell \ - $(PYTHON) scripts/generate_ecp_tests.py --list || \ + $(PYTHON) ../framework/scripts/generate_ecp_tests.py --list || \ echo FAILED \ )) ifeq ($(GENERATED_ECP_DATA_FILES),FAILED) -$(error "$(PYTHON) scripts/generate_ecp_tests.py --list" failed) +$(error "$(PYTHON) ../framework/scripts/generate_ecp_tests.py --list" failed) endif GENERATED_PSA_DATA_FILES := $(patsubst tests/%,%,$(shell \ - $(PYTHON) scripts/generate_psa_tests.py --list || \ + $(PYTHON) ../framework/scripts/generate_psa_tests.py --list || \ echo FAILED \ )) ifeq ($(GENERATED_PSA_DATA_FILES),FAILED) -$(error "$(PYTHON) scripts/generate_psa_tests.py --list" failed) +$(error "$(PYTHON) ../framework/scripts/generate_psa_tests.py --list" failed) endif GENERATED_FILES := $(GENERATED_PSA_DATA_FILES) $(GENERATED_ECP_DATA_FILES) $(GENERATED_BIGNUM_DATA_FILES) generated_files: $(GENERATED_FILES) src/test_keys.h src/test_certs.h @@ -49,7 +49,7 @@ generated_files: $(GENERATED_FILES) src/test_keys.h src/test_certs.h # a separate instance of the recipe for each output file. .SECONDARY: generated_bignum_test_data generated_ecp_test_data generated_psa_test_data $(GENERATED_BIGNUM_DATA_FILES): $(gen_file_dep) generated_bignum_test_data -generated_bignum_test_data: scripts/generate_bignum_tests.py +generated_bignum_test_data: ../framework/scripts/generate_bignum_tests.py generated_bignum_test_data: ../framework/scripts/mbedtls_framework/bignum_common.py generated_bignum_test_data: ../framework/scripts/mbedtls_framework/bignum_core.py generated_bignum_test_data: ../framework/scripts/mbedtls_framework/bignum_mod_raw.py @@ -58,20 +58,20 @@ generated_bignum_test_data: ../framework/scripts/mbedtls_framework/test_case.py generated_bignum_test_data: ../framework/scripts/mbedtls_framework/test_data_generation.py generated_bignum_test_data: echo " Gen $(GENERATED_BIGNUM_DATA_FILES)" - $(PYTHON) scripts/generate_bignum_tests.py + $(PYTHON) ../framework/scripts/generate_bignum_tests.py $(GENERATED_ECP_DATA_FILES): $(gen_file_dep) generated_ecp_test_data -generated_ecp_test_data: scripts/generate_ecp_tests.py +generated_ecp_test_data: ../framework/scripts/generate_ecp_tests.py generated_ecp_test_data: ../framework/scripts/mbedtls_framework/bignum_common.py generated_ecp_test_data: ../framework/scripts/mbedtls_framework/ecp.py generated_ecp_test_data: ../framework/scripts/mbedtls_framework/test_case.py generated_ecp_test_data: ../framework/scripts/mbedtls_framework/test_data_generation.py generated_ecp_test_data: echo " Gen $(GENERATED_ECP_DATA_FILES)" - $(PYTHON) scripts/generate_ecp_tests.py + $(PYTHON) ../framework/scripts/generate_ecp_tests.py $(GENERATED_PSA_DATA_FILES): $(gen_file_dep) generated_psa_test_data -generated_psa_test_data: scripts/generate_psa_tests.py +generated_psa_test_data: ../framework/scripts/generate_psa_tests.py generated_psa_test_data: ../framework/scripts/mbedtls_framework/crypto_data_tests.py generated_psa_test_data: ../framework/scripts/mbedtls_framework/crypto_knowledge.py generated_psa_test_data: ../framework/scripts/mbedtls_framework/macro_collector.py @@ -90,7 +90,7 @@ generated_psa_test_data: ../include/psa/crypto_extra.h generated_psa_test_data: suites/test_suite_psa_crypto_metadata.data generated_psa_test_data: echo " Gen $(GENERATED_PSA_DATA_FILES) ..." - $(PYTHON) scripts/generate_psa_tests.py + $(PYTHON) ../framework/scripts/generate_psa_tests.py # A test application is built for each suites/test_suite_*.data file. # Application name is same as .data file's base name and can be @@ -112,12 +112,12 @@ all: $(BINARIES) mbedtls_test: $(MBEDTLS_TEST_OBJS) -src/test_certs.h: scripts/generate_test_cert_macros.py \ - $($(PYTHON) scripts/generate_test_cert_macros.py --list-dependencies) - $(PYTHON) scripts/generate_test_cert_macros.py --output $@ +src/test_certs.h: ../framework/scripts/generate_test_cert_macros.py \ + $($(PYTHON) ../framework/scripts/generate_test_cert_macros.py --list-dependencies) + $(PYTHON) ../framework/scripts/generate_test_cert_macros.py --output $@ -src/test_keys.h: scripts/generate_test_keys.py - $(PYTHON) scripts/generate_test_keys.py --output $@ +src/test_keys.h: ../framework/scripts/generate_test_keys.py + $(PYTHON) ../framework/scripts/generate_test_keys.py --output $@ TEST_OBJS_DEPS = $(wildcard include/test/*.h include/test/*/*.h) ifdef RECORD_PSA_STATUS_COVERAGE_LOG @@ -159,9 +159,9 @@ c: $(C_FILES) # dot in .c file's base name. # .SECONDEXPANSION: -%.c: suites/$$(firstword $$(subst ., ,$$*)).function suites/%.data scripts/generate_test_code.py suites/helpers.function suites/main_test.function suites/host_test.function +%.c: suites/$$(firstword $$(subst ., ,$$*)).function suites/%.data ../framework/scripts/generate_test_code.py suites/helpers.function suites/main_test.function suites/host_test.function echo " Gen $@" - $(PYTHON) scripts/generate_test_code.py -f suites/$(firstword $(subst ., ,$*)).function \ + $(PYTHON) ../framework/scripts/generate_test_code.py -f suites/$(firstword $(subst ., ,$*)).function \ -d suites/$*.data \ -t suites/main_test.function \ -p suites/host_test.function \ diff --git a/tests/data_files/test_certs.h.jinja2 b/tests/data_files/test_certs.h.jinja2 index f2657d883f..c420c7964b 100644 --- a/tests/data_files/test_certs.h.jinja2 +++ b/tests/data_files/test_certs.h.jinja2 @@ -5,7 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ -/* THIS FILE is generated by `tests/scripts/generate_test_cert_macros.py` */ +/* THIS FILE is generated by `framework/scripts/generate_test_cert_macros.py` */ /* *INDENT-OFF* */ {% for mode, name, value in macros %} diff --git a/tests/scripts/all.sh b/tests/scripts/all.sh index d0dcc99c3d..3ce8ebb9d5 100755 --- a/tests/scripts/all.sh +++ b/tests/scripts/all.sh @@ -6434,7 +6434,7 @@ component_check_test_helpers () { # unittest writes out mundane stuff like number or tests run on stderr. # Our convention is to reserve stderr for actual errors, and write # harmless info on stdout so it can be suppress with --quiet. - ./tests/scripts/test_generate_test_code.py 2>&1 + ./framework/scripts/test_generate_test_code.py 2>&1 msg "unit test: translate_ciphers.py" python3 -m unittest tests/scripts/translate_ciphers.py 2>&1 diff --git a/tests/scripts/check-generated-files.sh b/tests/scripts/check-generated-files.sh index 049721bf1d..e740f33865 100755 --- a/tests/scripts/check-generated-files.sh +++ b/tests/scripts/check-generated-files.sh @@ -128,10 +128,10 @@ check() # These checks are common to Mbed TLS and TF-PSA-Crypto check scripts/generate_psa_constants.py programs/psa/psa_constant_names_generated.c -check tests/scripts/generate_bignum_tests.py $(tests/scripts/generate_bignum_tests.py --list) -check tests/scripts/generate_ecp_tests.py $(tests/scripts/generate_ecp_tests.py --list) -check tests/scripts/generate_psa_tests.py $(tests/scripts/generate_psa_tests.py --list) -check tests/scripts/generate_test_keys.py tests/src/test_keys.h +check framework/scripts/generate_bignum_tests.py $(framework/scripts/generate_bignum_tests.py --list) +check framework/scripts/generate_ecp_tests.py $(framework/scripts/generate_ecp_tests.py --list) +check framework/scripts/generate_psa_tests.py $(framework/scripts/generate_psa_tests.py --list) +check framework/scripts/generate_test_keys.py tests/src/test_keys.h check scripts/generate_driver_wrappers.py $library_dir/psa_crypto_driver_wrappers.h $library_dir/psa_crypto_driver_wrappers_no_static.c # Additional checks for Mbed TLS only @@ -140,7 +140,7 @@ if in_mbedtls_repo; then check scripts/generate_query_config.pl programs/test/query_config.c check scripts/generate_features.pl library/version_features.c check scripts/generate_ssl_debug_helpers.py library/ssl_debug_helpers_generated.c - check tests/scripts/generate_test_cert_macros.py tests/src/test_certs.h + check framework/scripts/generate_test_cert_macros.py tests/src/test_certs.h # generate_visualc_files enumerates source files (library/*.c). It doesn't # care about their content, but the files must exist. So it must run after # the step that creates or updates these files. @@ -150,4 +150,4 @@ fi # Generated files that are present in the repository even in the development # branch. (This is intended to be temporary, until the generator scripts are # fully reviewed and the build scripts support a generated header file.) -check tests/scripts/generate_psa_wrappers.py tests/include/test/psa_test_wrappers.h tests/src/psa_test_wrappers.c +check framework/scripts/generate_psa_wrappers.py tests/include/test/psa_test_wrappers.h tests/src/psa_test_wrappers.c diff --git a/tests/scripts/generate_bignum_tests.py b/tests/scripts/generate_bignum_tests.py deleted file mode 100755 index b855e91abf..0000000000 --- a/tests/scripts/generate_bignum_tests.py +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/env python3 -"""Generate test data for bignum functions. - -With no arguments, generate all test data. With non-option arguments, -generate only the specified files. - -Class structure: - -Child classes of test_data_generation.BaseTarget (file targets) represent an output -file. These indicate where test cases will be written to, for all subclasses of -this target. Multiple file targets should not reuse a `target_basename`. - -Each subclass derived from a file target can either be: - - A concrete class, representing a test function, which generates test cases. - - An abstract class containing shared methods and attributes, not associated - with a test function. An example is BignumOperation, which provides - common features used for bignum binary operations. - -Both concrete and abstract subclasses can be derived from, to implement -additional test cases (see BignumCmp and BignumCmpAbs for examples of deriving -from abstract and concrete classes). - - -Adding test case generation for a function: - -A subclass representing the test function should be added, deriving from a -file target such as BignumTarget. This test class must set/implement the -following: - - test_function: the function name from the associated .function file. - - test_name: a descriptive name or brief summary to refer to the test - function. - - arguments(): a method to generate the list of arguments required for the - test_function. - - generate_function_tests(): a method to generate TestCases for the function. - This should create instances of the class with required input data, and - call `.create_test_case()` to yield the TestCase. - -Additional details and other attributes/methods are given in the documentation -of BaseTarget in test_data_generation.py. -""" - -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -import sys - -from abc import ABCMeta -from typing import List - -import scripts_path # pylint: disable=unused-import -from mbedtls_framework import test_data_generation -from mbedtls_framework import bignum_common -# Import modules containing additional test classes -# Test function classes in these modules will be registered by -# the framework -from mbedtls_framework import bignum_core, bignum_mod_raw, bignum_mod # pylint: disable=unused-import - -class BignumTarget(test_data_generation.BaseTarget): - #pylint: disable=too-few-public-methods - """Target for bignum (legacy) test case generation.""" - target_basename = 'test_suite_bignum.generated' - - -class BignumOperation(bignum_common.OperationCommon, BignumTarget, - metaclass=ABCMeta): - #pylint: disable=abstract-method - """Common features for bignum operations in legacy tests.""" - unique_combinations_only = True - input_values = [ - "", "0", "-", "-0", - "7b", "-7b", - "0000000000000000123", "-0000000000000000123", - "1230000000000000000", "-1230000000000000000" - ] - - def description_suffix(self) -> str: - #pylint: disable=no-self-use # derived classes need self - """Text to add at the end of the test case description.""" - return "" - - def description(self) -> str: - """Generate a description for the test case. - - If not set, case_description uses the form A `symbol` B, where symbol - is used to represent the operation. Descriptions of each value are - generated to provide some context to the test case. - """ - if not self.case_description: - self.case_description = "{} {} {}".format( - self.value_description(self.arg_a), - self.symbol, - self.value_description(self.arg_b) - ) - description_suffix = self.description_suffix() - if description_suffix: - self.case_description += " " + description_suffix - return super().description() - - @staticmethod - def value_description(val) -> str: - """Generate a description of the argument val. - - This produces a simple description of the value, which is used in test - case naming to add context. - """ - if val == "": - return "0 (null)" - if val == "-": - return "negative 0 (null)" - if val == "0": - return "0 (1 limb)" - - if val[0] == "-": - tmp = "negative" - val = val[1:] - else: - tmp = "positive" - if val[0] == "0": - tmp += " with leading zero limb" - elif len(val) > 10: - tmp = "large " + tmp - return tmp - - -class BignumCmp(BignumOperation): - """Test cases for bignum value comparison.""" - count = 0 - test_function = "mpi_cmp_mpi" - test_name = "MPI compare" - input_cases = [ - ("-2", "-3"), - ("-2", "-2"), - ("2b4", "2b5"), - ("2b5", "2b6") - ] - - def __init__(self, val_a, val_b) -> None: - super().__init__(val_a, val_b) - self._result = int(self.int_a > self.int_b) - int(self.int_a < self.int_b) - self.symbol = ["<", "==", ">"][self._result + 1] - - def result(self) -> List[str]: - return [str(self._result)] - - -class BignumCmpAbs(BignumCmp): - """Test cases for absolute bignum value comparison.""" - count = 0 - test_function = "mpi_cmp_abs" - test_name = "MPI compare (abs)" - - def __init__(self, val_a, val_b) -> None: - super().__init__(val_a.strip("-"), val_b.strip("-")) - - -class BignumAdd(BignumOperation): - """Test cases for bignum value addition.""" - count = 0 - symbol = "+" - test_function = "mpi_add_mpi" - test_name = "MPI add" - input_cases = bignum_common.combination_pairs( - [ - "1c67967269c6", "9cde3", - "-1c67967269c6", "-9cde3", - ] - ) - - def __init__(self, val_a: str, val_b: str) -> None: - super().__init__(val_a, val_b) - self._result = self.int_a + self.int_b - - def description_suffix(self) -> str: - if (self.int_a >= 0 and self.int_b >= 0): - return "" # obviously positive result or 0 - if (self.int_a <= 0 and self.int_b <= 0): - return "" # obviously negative result or 0 - # The sign of the result is not obvious, so indicate it - return ", result{}0".format('>' if self._result > 0 else - '<' if self._result < 0 else '=') - - def result(self) -> List[str]: - return [bignum_common.quote_str("{:x}".format(self._result))] - -if __name__ == '__main__': - # Use the section of the docstring relevant to the CLI as description - test_data_generation.main(sys.argv[1:], "\n".join(__doc__.splitlines()[:4])) diff --git a/tests/scripts/generate_ecp_tests.py b/tests/scripts/generate_ecp_tests.py deleted file mode 100755 index c5281ad02b..0000000000 --- a/tests/scripts/generate_ecp_tests.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -"""Generate test data for ecp functions. - -The command line usage, class structure and available methods are the same -as in generate_bignum_tests.py. -""" - -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -import sys - -import scripts_path # pylint: disable=unused-import -from mbedtls_framework import test_data_generation -# Import modules containing additional test classes -# Test function classes in these modules will be registered by -# the framework -from mbedtls_framework import ecp # pylint: disable=unused-import - -if __name__ == '__main__': - # Use the section of the docstring relevant to the CLI as description - test_data_generation.main(sys.argv[1:], "\n".join(__doc__.splitlines()[:4])) diff --git a/tests/scripts/generate_pkcs7_tests.py b/tests/scripts/generate_pkcs7_tests.py deleted file mode 100755 index 0e484b023d..0000000000 --- a/tests/scripts/generate_pkcs7_tests.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later -# - -""" -Make fuzz like testing for pkcs7 tests -Given a valid DER pkcs7 file add tests to the test_suite_pkcs7.data file - - It is expected that the pkcs7_asn1_fail( data_t *pkcs7_buf ) - function is defined in test_suite_pkcs7.function - - This is not meant to be portable code, if anything it is meant to serve as - documentation for showing how those ugly tests in test_suite_pkcs7.data were created -""" - - -import sys -from os.path import exists - -PKCS7_TEST_FILE = "../suites/test_suite_pkcs7.data" - -class Test: # pylint: disable=too-few-public-methods - """ - A instance of a test in test_suite_pkcs7.data - """ - def __init__(self, name, depends, func_call): - self.name = name - self.depends = depends - self.func_call = func_call - - # pylint: disable=no-self-use - def to_string(self): - return "\n" + self.name + "\n" + self.depends + "\n" + self.func_call + "\n" - -class TestData: - """ - Take in test_suite_pkcs7.data file. - Allow for new tests to be added. - """ - mandatory_dep = "MBEDTLS_MD_CAN_SHA256" - test_name = "PKCS7 Parse Failure Invalid ASN1" - test_function = "pkcs7_asn1_fail:" - def __init__(self, file_name): - self.file_name = file_name - self.last_test_num, self.old_tests = self.read_test_file(file_name) - self.new_tests = [] - - # pylint: disable=no-self-use - def read_test_file(self, file): - """ - Parse the test_suite_pkcs7.data file. - """ - tests = [] - if not exists(file): - print(file + " Does not exist") - sys.exit() - with open(file, "r", encoding='UTF-8') as fp: - data = fp.read() - lines = [line.strip() for line in data.split('\n') if len(line.strip()) > 1] - i = 0 - while i < len(lines): - if "depends" in lines[i+1]: - tests.append(Test(lines[i], lines[i+1], lines[i+2])) - i += 3 - else: - tests.append(Test(lines[i], None, lines[i+1])) - i += 2 - latest_test_num = float(tests[-1].name.split('#')[1]) - return latest_test_num, tests - - def add(self, name, func_call): - self.last_test_num += 1 - self.new_tests.append(Test(self.test_name + ": " + name + " #" + \ - str(self.last_test_num), "depends_on:" + self.mandatory_dep, \ - self.test_function + '"' + func_call + '"')) - - def write_changes(self): - with open(self.file_name, 'a', encoding='UTF-8') as fw: - fw.write("\n") - for t in self.new_tests: - fw.write(t.to_string()) - - -def asn1_mutate(data): - """ - We have been given an asn1 structure representing a pkcs7. - We want to return an array of slightly modified versions of this data - they should be modified in a way which makes the structure invalid - - We know that asn1 structures are: - |---1 byte showing data type---|----byte(s) for length of data---|---data content--| - We know that some data types can contain other data types. - Return a dictionary of reasons and mutated data types. - """ - - # off the bat just add bytes to start and end of the buffer - mutations = [] - reasons = [] - mutations.append(["00"] + data) - reasons.append("Add null byte to start") - mutations.append(data + ["00"]) - reasons.append("Add null byte to end") - # for every asn1 entry we should attempt to: - # - change the data type tag - # - make the length longer than actual - # - make the length shorter than actual - i = 0 - while i < len(data): - tag_i = i - leng_i = tag_i + 1 - data_i = leng_i + 1 + (int(data[leng_i][1], 16) if data[leng_i][0] == '8' else 0) - if data[leng_i][0] == '8': - length = int(''.join(data[leng_i + 1: data_i]), 16) - else: - length = int(data[leng_i], 16) - - tag = data[tag_i] - print("Looking at ans1: offset " + str(i) + " tag = " + tag + \ - ", length = " + str(length)+ ":") - print(''.join(data[data_i:data_i+length])) - # change tag to something else - if tag == "02": - # turn integers into octet strings - new_tag = "04" - else: - # turn everything else into an integer - new_tag = "02" - mutations.append(data[:tag_i] + [new_tag] + data[leng_i:]) - reasons.append("Change tag " + tag + " to " + new_tag) - - # change lengths to too big - # skip any edge cases which would cause carry over - if int(data[data_i - 1], 16) < 255: - new_length = str(hex(int(data[data_i - 1], 16) + 1))[2:] - if len(new_length) == 1: - new_length = "0"+new_length - mutations.append(data[:data_i -1] + [new_length] + data[data_i:]) - reasons.append("Change length from " + str(length) + " to " \ - + str(length + 1)) - # we can add another test here for tags that contain other tags \ - # where they have more data than there containing tags account for - if tag in ["30", "a0", "31"]: - mutations.append(data[:data_i -1] + [new_length] + \ - data[data_i:data_i + length] + ["00"] + \ - data[data_i + length:]) - reasons.append("Change contents of tag " + tag + " to contain \ - one unaccounted extra byte") - # change lengths to too small - if int(data[data_i - 1], 16) > 0: - new_length = str(hex(int(data[data_i - 1], 16) - 1))[2:] - if len(new_length) == 1: - new_length = "0"+new_length - mutations.append(data[:data_i -1] + [new_length] + data[data_i:]) - reasons.append("Change length from " + str(length) + " to " + str(length - 1)) - - # some tag types contain other tag types so we should iterate into the data - if tag in ["30", "a0", "31"]: - i = data_i - else: - i = data_i + length - - return list(zip(reasons, mutations)) - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("USAGE: " + sys.argv[0] + " ") - sys.exit() - - DATA_FILE = sys.argv[1] - TEST_DATA = TestData(PKCS7_TEST_FILE) - with open(DATA_FILE, 'rb') as f: - DATA_STR = f.read().hex() - # make data an array of byte strings eg ['de','ad','be','ef'] - HEX_DATA = list(map(''.join, [[DATA_STR[i], DATA_STR[i+1]] for i in range(0, len(DATA_STR), \ - 2)])) - # returns tuples of test_names and modified data buffers - MUT_ARR = asn1_mutate(HEX_DATA) - - print("made " + str(len(MUT_ARR)) + " new tests") - for new_test in MUT_ARR: - TEST_DATA.add(new_test[0], ''.join(new_test[1])) - - TEST_DATA.write_changes() diff --git a/tests/scripts/generate_psa_tests.py b/tests/scripts/generate_psa_tests.py deleted file mode 100755 index 75d02b9e23..0000000000 --- a/tests/scripts/generate_psa_tests.py +++ /dev/null @@ -1,850 +0,0 @@ -#!/usr/bin/env python3 -"""Generate test data for PSA cryptographic mechanisms. - -With no arguments, generate all test data. With non-option arguments, -generate only the specified files. -""" - -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -import enum -import re -import sys -from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional - -import scripts_path # pylint: disable=unused-import -from mbedtls_framework import crypto_data_tests -from mbedtls_framework import crypto_knowledge -from mbedtls_framework import macro_collector #pylint: disable=unused-import -from mbedtls_framework import psa_information -from mbedtls_framework import psa_storage -from mbedtls_framework import test_case -from mbedtls_framework import test_data_generation - - - -def test_case_for_key_type_not_supported( - verb: str, key_type: str, bits: int, - dependencies: List[str], - *args: str, - param_descr: str = '' -) -> test_case.TestCase: - """Return one test case exercising a key creation method - for an unsupported key type or size. - """ - psa_information.hack_dependencies_not_implemented(dependencies) - tc = test_case.TestCase() - short_key_type = crypto_knowledge.short_expression(key_type) - adverb = 'not' if dependencies else 'never' - if param_descr: - adverb = param_descr + ' ' + adverb - tc.set_description('PSA {} {} {}-bit {} supported' - .format(verb, short_key_type, bits, adverb)) - tc.set_dependencies(dependencies) - tc.set_function(verb + '_not_supported') - tc.set_arguments([key_type] + list(args)) - return tc - -class KeyTypeNotSupported: - """Generate test cases for when a key type is not supported.""" - - def __init__(self, info: psa_information.Information) -> None: - self.constructors = info.constructors - - ALWAYS_SUPPORTED = frozenset([ - 'PSA_KEY_TYPE_DERIVE', - 'PSA_KEY_TYPE_PASSWORD', - 'PSA_KEY_TYPE_PASSWORD_HASH', - 'PSA_KEY_TYPE_RAW_DATA', - 'PSA_KEY_TYPE_HMAC' - ]) - def test_cases_for_key_type_not_supported( - self, - kt: crypto_knowledge.KeyType, - param: Optional[int] = None, - param_descr: str = '', - ) -> Iterator[test_case.TestCase]: - """Return test cases exercising key creation when the given type is unsupported. - - If param is present and not None, emit test cases conditioned on this - parameter not being supported. If it is absent or None, emit test cases - conditioned on the base type not being supported. - """ - if kt.name in self.ALWAYS_SUPPORTED: - # Don't generate test cases for key types that are always supported. - # They would be skipped in all configurations, which is noise. - return - import_dependencies = [('!' if param is None else '') + - psa_information.psa_want_symbol(kt.name)] - if kt.params is not None: - import_dependencies += [('!' if param == i else '') + - psa_information.psa_want_symbol(sym) - for i, sym in enumerate(kt.params)] - if kt.name.endswith('_PUBLIC_KEY'): - generate_dependencies = [] - else: - generate_dependencies = \ - psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE') - import_dependencies = \ - psa_information.fix_key_pair_dependencies(import_dependencies, 'BASIC') - for bits in kt.sizes_to_test(): - yield test_case_for_key_type_not_supported( - 'import', kt.expression, bits, - psa_information.finish_family_dependencies(import_dependencies, bits), - test_case.hex_string(kt.key_material(bits)), - param_descr=param_descr, - ) - if not generate_dependencies and param is not None: - # If generation is impossible for this key type, rather than - # supported or not depending on implementation capabilities, - # only generate the test case once. - continue - # For public key we expect that key generation fails with - # INVALID_ARGUMENT. It is handled by KeyGenerate class. - if not kt.is_public(): - yield test_case_for_key_type_not_supported( - 'generate', kt.expression, bits, - psa_information.finish_family_dependencies(generate_dependencies, bits), - str(bits), - param_descr=param_descr, - ) - # To be added: derive - - ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR', - 'PSA_KEY_TYPE_ECC_PUBLIC_KEY') - DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR', - 'PSA_KEY_TYPE_DH_PUBLIC_KEY') - - def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]: - """Generate test cases that exercise the creation of keys of unsupported types.""" - for key_type in sorted(self.constructors.key_types): - if key_type in self.ECC_KEY_TYPES: - continue - if key_type in self.DH_KEY_TYPES: - continue - kt = crypto_knowledge.KeyType(key_type) - yield from self.test_cases_for_key_type_not_supported(kt) - for curve_family in sorted(self.constructors.ecc_curves): - for constr in self.ECC_KEY_TYPES: - kt = crypto_knowledge.KeyType(constr, [curve_family]) - yield from self.test_cases_for_key_type_not_supported( - kt, param_descr='type') - yield from self.test_cases_for_key_type_not_supported( - kt, 0, param_descr='curve') - for dh_family in sorted(self.constructors.dh_groups): - for constr in self.DH_KEY_TYPES: - kt = crypto_knowledge.KeyType(constr, [dh_family]) - yield from self.test_cases_for_key_type_not_supported( - kt, param_descr='type') - yield from self.test_cases_for_key_type_not_supported( - kt, 0, param_descr='group') - -def test_case_for_key_generation( - key_type: str, bits: int, - dependencies: List[str], - *args: str, - result: str = '' -) -> test_case.TestCase: - """Return one test case exercising a key generation. - """ - psa_information.hack_dependencies_not_implemented(dependencies) - tc = test_case.TestCase() - short_key_type = crypto_knowledge.short_expression(key_type) - tc.set_description('PSA {} {}-bit' - .format(short_key_type, bits)) - tc.set_dependencies(dependencies) - tc.set_function('generate_key') - tc.set_arguments([key_type] + list(args) + [result]) - - return tc - -class KeyGenerate: - """Generate positive and negative (invalid argument) test cases for key generation.""" - - def __init__(self, info: psa_information.Information) -> None: - self.constructors = info.constructors - - ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR', - 'PSA_KEY_TYPE_ECC_PUBLIC_KEY') - DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR', - 'PSA_KEY_TYPE_DH_PUBLIC_KEY') - - @staticmethod - def test_cases_for_key_type_key_generation( - kt: crypto_knowledge.KeyType - ) -> Iterator[test_case.TestCase]: - """Return test cases exercising key generation. - - All key types can be generated except for public keys. For public key - PSA_ERROR_INVALID_ARGUMENT status is expected. - """ - result = 'PSA_SUCCESS' - - import_dependencies = [psa_information.psa_want_symbol(kt.name)] - if kt.params is not None: - import_dependencies += [psa_information.psa_want_symbol(sym) - for i, sym in enumerate(kt.params)] - if kt.name.endswith('_PUBLIC_KEY'): - # The library checks whether the key type is a public key generically, - # before it reaches a point where it needs support for the specific key - # type, so it returns INVALID_ARGUMENT for unsupported public key types. - generate_dependencies = [] - result = 'PSA_ERROR_INVALID_ARGUMENT' - else: - generate_dependencies = \ - psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE') - for bits in kt.sizes_to_test(): - if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR': - size_dependency = "PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS <= " + str(bits) - test_dependencies = generate_dependencies + [size_dependency] - else: - test_dependencies = generate_dependencies - yield test_case_for_key_generation( - kt.expression, bits, - psa_information.finish_family_dependencies(test_dependencies, bits), - str(bits), - result - ) - - def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]: - """Generate test cases that exercise the generation of keys.""" - for key_type in sorted(self.constructors.key_types): - if key_type in self.ECC_KEY_TYPES: - continue - if key_type in self.DH_KEY_TYPES: - continue - kt = crypto_knowledge.KeyType(key_type) - yield from self.test_cases_for_key_type_key_generation(kt) - for curve_family in sorted(self.constructors.ecc_curves): - for constr in self.ECC_KEY_TYPES: - kt = crypto_knowledge.KeyType(constr, [curve_family]) - yield from self.test_cases_for_key_type_key_generation(kt) - for dh_family in sorted(self.constructors.dh_groups): - for constr in self.DH_KEY_TYPES: - kt = crypto_knowledge.KeyType(constr, [dh_family]) - yield from self.test_cases_for_key_type_key_generation(kt) - -class OpFail: - """Generate test cases for operations that must fail.""" - #pylint: disable=too-few-public-methods - - class Reason(enum.Enum): - NOT_SUPPORTED = 0 - INVALID = 1 - INCOMPATIBLE = 2 - PUBLIC = 3 - - def __init__(self, info: psa_information.Information) -> None: - self.constructors = info.constructors - key_type_expressions = self.constructors.generate_expressions( - sorted(self.constructors.key_types) - ) - self.key_types = [crypto_knowledge.KeyType(kt_expr) - for kt_expr in key_type_expressions] - - def make_test_case( - self, - alg: crypto_knowledge.Algorithm, - category: crypto_knowledge.AlgorithmCategory, - reason: 'Reason', - kt: Optional[crypto_knowledge.KeyType] = None, - not_deps: FrozenSet[str] = frozenset(), - ) -> test_case.TestCase: - """Construct a failure test case for a one-key or keyless operation.""" - #pylint: disable=too-many-arguments,too-many-locals - tc = test_case.TestCase() - pretty_alg = alg.short_expression() - if reason == self.Reason.NOT_SUPPORTED: - short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep) - for dep in not_deps] - pretty_reason = '!' + '&'.join(sorted(short_deps)) - else: - pretty_reason = reason.name.lower() - if kt: - key_type = kt.expression - pretty_type = kt.short_expression() - else: - key_type = '' - pretty_type = '' - tc.set_description('PSA {} {}: {}{}' - .format(category.name.lower(), - pretty_alg, - pretty_reason, - ' with ' + pretty_type if pretty_type else '')) - dependencies = psa_information.automatic_dependencies(alg.base_expression, key_type) - dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC') - for i, dep in enumerate(dependencies): - if dep in not_deps: - dependencies[i] = '!' + dep - tc.set_dependencies(dependencies) - tc.set_function(category.name.lower() + '_fail') - arguments = [] # type: List[str] - if kt: - key_material = kt.key_material(kt.sizes_to_test()[0]) - arguments += [key_type, test_case.hex_string(key_material)] - arguments.append(alg.expression) - if category.is_asymmetric(): - arguments.append('1' if reason == self.Reason.PUBLIC else '0') - error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else - 'INVALID_ARGUMENT') - arguments.append('PSA_ERROR_' + error) - tc.set_arguments(arguments) - return tc - - def no_key_test_cases( - self, - alg: crypto_knowledge.Algorithm, - category: crypto_knowledge.AlgorithmCategory, - ) -> Iterator[test_case.TestCase]: - """Generate failure test cases for keyless operations with the specified algorithm.""" - if alg.can_do(category): - # Compatible operation, unsupported algorithm - for dep in psa_information.automatic_dependencies(alg.base_expression): - yield self.make_test_case(alg, category, - self.Reason.NOT_SUPPORTED, - not_deps=frozenset([dep])) - else: - # Incompatible operation, supported algorithm - yield self.make_test_case(alg, category, self.Reason.INVALID) - - def one_key_test_cases( - self, - alg: crypto_knowledge.Algorithm, - category: crypto_knowledge.AlgorithmCategory, - ) -> Iterator[test_case.TestCase]: - """Generate failure test cases for one-key operations with the specified algorithm.""" - for kt in self.key_types: - key_is_compatible = kt.can_do(alg) - if key_is_compatible and alg.can_do(category): - # Compatible key and operation, unsupported algorithm - for dep in psa_information.automatic_dependencies(alg.base_expression): - yield self.make_test_case(alg, category, - self.Reason.NOT_SUPPORTED, - kt=kt, not_deps=frozenset([dep])) - # Public key for a private-key operation - if category.is_asymmetric() and kt.is_public(): - yield self.make_test_case(alg, category, - self.Reason.PUBLIC, - kt=kt) - elif key_is_compatible: - # Compatible key, incompatible operation, supported algorithm - yield self.make_test_case(alg, category, - self.Reason.INVALID, - kt=kt) - elif alg.can_do(category): - # Incompatible key, compatible operation, supported algorithm - yield self.make_test_case(alg, category, - self.Reason.INCOMPATIBLE, - kt=kt) - else: - # Incompatible key and operation. Don't test cases where - # multiple things are wrong, to keep the number of test - # cases reasonable. - pass - - def test_cases_for_algorithm( - self, - alg: crypto_knowledge.Algorithm, - ) -> Iterator[test_case.TestCase]: - """Generate operation failure test cases for the specified algorithm.""" - for category in crypto_knowledge.AlgorithmCategory: - if category == crypto_knowledge.AlgorithmCategory.PAKE: - # PAKE operations are not implemented yet - pass - elif category.requires_key(): - yield from self.one_key_test_cases(alg, category) - else: - yield from self.no_key_test_cases(alg, category) - - def all_test_cases(self) -> Iterator[test_case.TestCase]: - """Generate all test cases for operations that must fail.""" - algorithms = sorted(self.constructors.algorithms) - for expr in self.constructors.generate_expressions(algorithms): - alg = crypto_knowledge.Algorithm(expr) - yield from self.test_cases_for_algorithm(alg) - - -class StorageKey(psa_storage.Key): - """Representation of a key for storage format testing.""" - - IMPLICIT_USAGE_FLAGS = { - 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE', - 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE' - } #type: Dict[str, str] - """Mapping of usage flags to the flags that they imply.""" - - def __init__( - self, - usage: Iterable[str], - without_implicit_usage: Optional[bool] = False, - **kwargs - ) -> None: - """Prepare to generate a key. - - * `usage` : The usage flags used for the key. - * `without_implicit_usage`: Flag to define to apply the usage extension - """ - usage_flags = set(usage) - if not without_implicit_usage: - for flag in sorted(usage_flags): - if flag in self.IMPLICIT_USAGE_FLAGS: - usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag]) - if usage_flags: - usage_expression = ' | '.join(sorted(usage_flags)) - else: - usage_expression = '0' - super().__init__(usage=usage_expression, **kwargs) - -class StorageTestData(StorageKey): - """Representation of test case data for storage format testing.""" - - def __init__( - self, - description: str, - expected_usage: Optional[List[str]] = None, - **kwargs - ) -> None: - """Prepare to generate test data - - * `description` : used for the test case names - * `expected_usage`: the usage flags generated as the expected usage flags - in the test cases. CAn differ from the usage flags - stored in the keys because of the usage flags extension. - """ - super().__init__(**kwargs) - self.description = description #type: str - if expected_usage is None: - self.expected_usage = self.usage #type: psa_storage.Expr - elif expected_usage: - self.expected_usage = psa_storage.Expr(' | '.join(expected_usage)) - else: - self.expected_usage = psa_storage.Expr(0) - -class StorageFormat: - """Storage format stability test cases.""" - - def __init__(self, info: psa_information.Information, version: int, forward: bool) -> None: - """Prepare to generate test cases for storage format stability. - - * `info`: information about the API. See the `Information` class. - * `version`: the storage format version to generate test cases for. - * `forward`: if true, generate forward compatibility test cases which - save a key and check that its representation is as intended. Otherwise - generate backward compatibility test cases which inject a key - representation and check that it can be read and used. - """ - self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator - self.version = version #type: int - self.forward = forward #type: bool - - RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z') - BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z') - @classmethod - def exercise_key_with_algorithm( - cls, - key_type: psa_storage.Expr, bits: int, - alg: psa_storage.Expr - ) -> bool: - """Whether to exercise the given key with the given algorithm. - - Normally only the type and algorithm matter for compatibility, and - this is handled in crypto_knowledge.KeyType.can_do(). This function - exists to detect exceptional cases. Exceptional cases detected here - are not tested in OpFail and should therefore have manually written - test cases. - """ - # Some test keys have the RAW_DATA type and attributes that don't - # necessarily make sense. We do this to validate numerical - # encodings of the attributes. - # Raw data keys have no useful exercise anyway so there is no - # loss of test coverage. - if key_type.string == 'PSA_KEY_TYPE_RAW_DATA': - return False - # OAEP requires room for two hashes plus wrapping - m = cls.RSA_OAEP_RE.match(alg.string) - if m: - hash_alg = m.group(1) - hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg) - key_length = (bits + 7) // 8 - # Leave enough room for at least one byte of plaintext - return key_length > 2 * hash_length + 2 - # There's nothing wrong with ECC keys on Brainpool curves, - # but operations with them are very slow. So we only exercise them - # with a single algorithm, not with all possible hashes. We do - # exercise other curves with all algorithms so test coverage is - # perfectly adequate like this. - m = cls.BRAINPOOL_RE.match(key_type.string) - if m and alg.string != 'PSA_ALG_ECDSA_ANY': - return False - return True - - def make_test_case(self, key: StorageTestData) -> test_case.TestCase: - """Construct a storage format test case for the given key. - - If ``forward`` is true, generate a forward compatibility test case: - create a key and validate that it has the expected representation. - Otherwise generate a backward compatibility test case: inject the - key representation into storage and validate that it can be read - correctly. - """ - verb = 'save' if self.forward else 'read' - tc = test_case.TestCase() - tc.set_description(verb + ' ' + key.description) - dependencies = psa_information.automatic_dependencies( - key.lifetime.string, key.type.string, - key.alg.string, key.alg2.string, - ) - dependencies = psa_information.finish_family_dependencies(dependencies, key.bits) - dependencies += psa_information.generate_deps_from_description(key.description) - dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC') - tc.set_dependencies(dependencies) - tc.set_function('key_storage_' + verb) - if self.forward: - extra_arguments = [] - else: - flags = [] - if self.exercise_key_with_algorithm(key.type, key.bits, key.alg): - flags.append('TEST_FLAG_EXERCISE') - if 'READ_ONLY' in key.lifetime.string: - flags.append('TEST_FLAG_READ_ONLY') - extra_arguments = [' | '.join(flags) if flags else '0'] - tc.set_arguments([key.lifetime.string, - key.type.string, str(key.bits), - key.expected_usage.string, - key.alg.string, key.alg2.string, - '"' + key.material.hex() + '"', - '"' + key.hex() + '"', - *extra_arguments]) - return tc - - def key_for_lifetime( - self, - lifetime: str, - ) -> StorageTestData: - """Construct a test key for the given lifetime.""" - short = lifetime - short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION', - r'', short) - short = crypto_knowledge.short_expression(short) - description = 'lifetime: ' + short - key = StorageTestData(version=self.version, - id=1, lifetime=lifetime, - type='PSA_KEY_TYPE_RAW_DATA', bits=8, - usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0, - material=b'L', - description=description) - return key - - def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]: - """Generate test keys covering lifetimes.""" - lifetimes = sorted(self.constructors.lifetimes) - expressions = self.constructors.generate_expressions(lifetimes) - for lifetime in expressions: - # Don't attempt to create or load a volatile key in storage - if 'VOLATILE' in lifetime: - continue - # Don't attempt to create a read-only key in storage, - # but do attempt to load one. - if 'READ_ONLY' in lifetime and self.forward: - continue - yield self.key_for_lifetime(lifetime) - - def key_for_usage_flags( - self, - usage_flags: List[str], - short: Optional[str] = None, - test_implicit_usage: Optional[bool] = True - ) -> StorageTestData: - """Construct a test key for the given key usage.""" - extra_desc = ' without implication' if test_implicit_usage else '' - description = 'usage' + extra_desc + ': ' - key1 = StorageTestData(version=self.version, - id=1, lifetime=0x00000001, - type='PSA_KEY_TYPE_RAW_DATA', bits=8, - expected_usage=usage_flags, - without_implicit_usage=not test_implicit_usage, - usage=usage_flags, alg=0, alg2=0, - material=b'K', - description=description) - if short is None: - usage_expr = key1.expected_usage.string - key1.description += crypto_knowledge.short_expression(usage_expr) - else: - key1.description += short - return key1 - - def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]: - """Generate test keys covering usage flags.""" - known_flags = sorted(self.constructors.key_usage_flags) - yield self.key_for_usage_flags(['0'], **kwargs) - for usage_flag in known_flags: - yield self.key_for_usage_flags([usage_flag], **kwargs) - for flag1, flag2 in zip(known_flags, - known_flags[1:] + [known_flags[0]]): - yield self.key_for_usage_flags([flag1, flag2], **kwargs) - - def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]: - known_flags = sorted(self.constructors.key_usage_flags) - yield self.key_for_usage_flags(known_flags, short='all known') - - def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]: - yield from self.generate_keys_for_usage_flags() - yield from self.generate_key_for_all_usage_flags() - - def key_for_type_and_alg( - self, - kt: crypto_knowledge.KeyType, - bits: int, - alg: Optional[crypto_knowledge.Algorithm] = None, - ) -> StorageTestData: - """Construct a test key of the given type. - - If alg is not None, this key allows it. - """ - usage_flags = ['PSA_KEY_USAGE_EXPORT'] - alg1 = 0 #type: psa_storage.Exprable - alg2 = 0 - if alg is not None: - alg1 = alg.expression - usage_flags += alg.usage_flags(public=kt.is_public()) - key_material = kt.key_material(bits) - description = 'type: {} {}-bit'.format(kt.short_expression(1), bits) - if alg is not None: - description += ', ' + alg.short_expression(1) - key = StorageTestData(version=self.version, - id=1, lifetime=0x00000001, - type=kt.expression, bits=bits, - usage=usage_flags, alg=alg1, alg2=alg2, - material=key_material, - description=description) - return key - - def keys_for_type( - self, - key_type: str, - all_algorithms: List[crypto_knowledge.Algorithm], - ) -> Iterator[StorageTestData]: - """Generate test keys for the given key type.""" - kt = crypto_knowledge.KeyType(key_type) - for bits in kt.sizes_to_test(): - # Test a non-exercisable key, as well as exercisable keys for - # each compatible algorithm. - # To do: test reading a key from storage with an incompatible - # or unsupported algorithm. - yield self.key_for_type_and_alg(kt, bits) - compatible_algorithms = [alg for alg in all_algorithms - if kt.can_do(alg)] - for alg in compatible_algorithms: - yield self.key_for_type_and_alg(kt, bits, alg) - - def all_keys_for_types(self) -> Iterator[StorageTestData]: - """Generate test keys covering key types and their representations.""" - key_types = sorted(self.constructors.key_types) - all_algorithms = [crypto_knowledge.Algorithm(alg) - for alg in self.constructors.generate_expressions( - sorted(self.constructors.algorithms) - )] - for key_type in self.constructors.generate_expressions(key_types): - yield from self.keys_for_type(key_type, all_algorithms) - - def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]: - """Generate test keys for the encoding of the specified algorithm.""" - # These test cases only validate the encoding of algorithms, not - # whether the key read from storage is suitable for an operation. - # `keys_for_types` generate read tests with an algorithm and a - # compatible key. - descr = crypto_knowledge.short_expression(alg, 1) - usage = ['PSA_KEY_USAGE_EXPORT'] - key1 = StorageTestData(version=self.version, - id=1, lifetime=0x00000001, - type='PSA_KEY_TYPE_RAW_DATA', bits=8, - usage=usage, alg=alg, alg2=0, - material=b'K', - description='alg: ' + descr) - yield key1 - key2 = StorageTestData(version=self.version, - id=1, lifetime=0x00000001, - type='PSA_KEY_TYPE_RAW_DATA', bits=8, - usage=usage, alg=0, alg2=alg, - material=b'L', - description='alg2: ' + descr) - yield key2 - - def all_keys_for_algorithms(self) -> Iterator[StorageTestData]: - """Generate test keys covering algorithm encodings.""" - algorithms = sorted(self.constructors.algorithms) - for alg in self.constructors.generate_expressions(algorithms): - yield from self.keys_for_algorithm(alg) - - def generate_all_keys(self) -> Iterator[StorageTestData]: - """Generate all keys for the test cases.""" - yield from self.all_keys_for_lifetimes() - yield from self.all_keys_for_usage_flags() - yield from self.all_keys_for_types() - yield from self.all_keys_for_algorithms() - - def all_test_cases(self) -> Iterator[test_case.TestCase]: - """Generate all storage format test cases.""" - # First build a list of all keys, then construct all the corresponding - # test cases. This allows all required information to be obtained in - # one go, which is a significant performance gain as the information - # includes numerical values obtained by compiling a C program. - all_keys = list(self.generate_all_keys()) - for key in all_keys: - if key.location_value() != 0: - # Skip keys with a non-default location, because they - # require a driver and we currently have no mechanism to - # determine whether a driver is available. - continue - yield self.make_test_case(key) - -class StorageFormatForward(StorageFormat): - """Storage format stability test cases for forward compatibility.""" - - def __init__(self, info: psa_information.Information, version: int) -> None: - super().__init__(info, version, True) - -class StorageFormatV0(StorageFormat): - """Storage format stability test cases for version 0 compatibility.""" - - def __init__(self, info: psa_information.Information) -> None: - super().__init__(info, 0, False) - - def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]: - """Generate test keys covering usage flags.""" - yield from super().all_keys_for_usage_flags() - yield from self.generate_keys_for_usage_flags(test_implicit_usage=False) - - def keys_for_implicit_usage( - self, - implyer_usage: str, - alg: str, - key_type: crypto_knowledge.KeyType - ) -> StorageTestData: - # pylint: disable=too-many-locals - """Generate test keys for the specified implicit usage flag, - algorithm and key type combination. - """ - bits = key_type.sizes_to_test()[0] - implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage] - usage_flags = ['PSA_KEY_USAGE_EXPORT'] - material_usage_flags = usage_flags + [implyer_usage] - expected_usage_flags = material_usage_flags + [implicit_usage] - alg2 = 0 - key_material = key_type.key_material(bits) - usage_expression = crypto_knowledge.short_expression(implyer_usage, 1) - alg_expression = crypto_knowledge.short_expression(alg, 1) - key_type_expression = key_type.short_expression(1) - description = 'implied by {}: {} {} {}-bit'.format( - usage_expression, alg_expression, key_type_expression, bits) - key = StorageTestData(version=self.version, - id=1, lifetime=0x00000001, - type=key_type.expression, bits=bits, - usage=material_usage_flags, - expected_usage=expected_usage_flags, - without_implicit_usage=True, - alg=alg, alg2=alg2, - material=key_material, - description=description) - return key - - def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]: - # pylint: disable=too-many-locals - """Match possible key types for sign algorithms.""" - # To create a valid combination both the algorithms and key types - # must be filtered. Pair them with keywords created from its names. - incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE']) - incompatible_key_type_keywords = frozenset(['MONTGOMERY']) - keyword_translation = { - 'ECDSA': 'ECC', - 'ED[0-9]*.*' : 'EDWARDS' - } - exclusive_keywords = { - 'EDWARDS': 'ECC' - } - key_types = set(self.constructors.generate_expressions(self.constructors.key_types)) - algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms)) - alg_with_keys = {} #type: Dict[str, List[str]] - translation_table = str.maketrans('(', '_', ')') - for alg in algorithms: - # Generate keywords from the name of the algorithm - alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:]) - # Translate keywords for better matching with the key types - for keyword in alg_keywords.copy(): - for pattern, replace in keyword_translation.items(): - if re.match(pattern, keyword): - alg_keywords.remove(keyword) - alg_keywords.add(replace) - # Filter out incompatible algorithms - if not alg_keywords.isdisjoint(incompatible_alg_keyword): - continue - - for key_type in key_types: - # Generate keywords from the of the key type - key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:]) - - # Remove ambiguous keywords - for keyword1, keyword2 in exclusive_keywords.items(): - if keyword1 in key_type_keywords: - key_type_keywords.remove(keyword2) - - if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\ - not key_type_keywords.isdisjoint(alg_keywords): - if alg in alg_with_keys: - alg_with_keys[alg].append(key_type) - else: - alg_with_keys[alg] = [key_type] - return alg_with_keys - - def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]: - """Generate test keys for usage flag extensions.""" - # Generate a key type and algorithm pair for each extendable usage - # flag to generate a valid key for exercising. The key is generated - # without usage extension to check the extension compatibility. - alg_with_keys = self.gather_key_types_for_sign_alg() - - for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str): - for alg in sorted(alg_with_keys): - for key_type in sorted(alg_with_keys[alg]): - # The key types must be filtered to fit the specific usage flag. - kt = crypto_knowledge.KeyType(key_type) - if kt.is_public() and '_SIGN_' in usage: - # Can't sign with a public key - continue - yield self.keys_for_implicit_usage(usage, alg, kt) - - def generate_all_keys(self) -> Iterator[StorageTestData]: - yield from super().generate_all_keys() - yield from self.all_keys_for_implicit_usage() - - -class PSATestGenerator(test_data_generation.TestGenerator): - """Test generator subclass including PSA targets and info.""" - # Note that targets whose names contain 'test_format' have their content - # validated by `abi_check.py`. - targets = { - 'test_suite_psa_crypto_generate_key.generated': - lambda info: KeyGenerate(info).test_cases_for_key_generation(), - 'test_suite_psa_crypto_not_supported.generated': - lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(), - 'test_suite_psa_crypto_low_hash.generated': - lambda info: crypto_data_tests.HashPSALowLevel(info).all_test_cases(), - 'test_suite_psa_crypto_op_fail.generated': - lambda info: OpFail(info).all_test_cases(), - 'test_suite_psa_crypto_storage_format.current': - lambda info: StorageFormatForward(info, 0).all_test_cases(), - 'test_suite_psa_crypto_storage_format.v0': - lambda info: StorageFormatV0(info).all_test_cases(), - } #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]] - - def __init__(self, options): - super().__init__(options) - self.info = psa_information.Information() - - def generate_target(self, name: str, *target_args) -> None: - super().generate_target(name, self.info) - - -if __name__ == '__main__': - test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator) diff --git a/tests/scripts/generate_psa_wrappers.py b/tests/scripts/generate_psa_wrappers.py deleted file mode 100755 index 500693bdae..0000000000 --- a/tests/scripts/generate_psa_wrappers.py +++ /dev/null @@ -1,257 +0,0 @@ -#!/usr/bin/env python3 -"""Generate wrapper functions for PSA function calls. -""" - -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -### WARNING: the code in this file has not been extensively reviewed yet. -### We do not think it is harmful, but it may be below our normal standards -### for robustness and maintainability. - -import argparse -import itertools -import os -from typing import Iterator, List, Optional, Tuple - -import scripts_path #pylint: disable=unused-import -from mbedtls_framework import build_tree -from mbedtls_framework import c_parsing_helper -from mbedtls_framework import c_wrapper_generator -from mbedtls_framework import typing_util - - -class BufferParameter: - """Description of an input or output buffer parameter sequence to a PSA function.""" - #pylint: disable=too-few-public-methods - - def __init__(self, i: int, is_output: bool, - buffer_name: str, size_name: str) -> None: - """Initialize the parameter information. - - i is the index of the function argument that is the pointer to the buffer. - The size is argument i+1. For a variable-size output, the actual length - goes in argument i+2. - - buffer_name and size_names are the names of arguments i and i+1. - This class does not yet help with the output length. - """ - self.index = i - self.buffer_name = buffer_name - self.size_name = size_name - self.is_output = is_output - - -class PSAWrapperGenerator(c_wrapper_generator.Base): - """Generate a C source file containing wrapper functions for PSA Crypto API calls.""" - - _CPP_GUARDS = ('defined(MBEDTLS_PSA_CRYPTO_C) && ' + - 'defined(MBEDTLS_TEST_HOOKS) && \\\n ' + - '!defined(RECORD_PSA_STATUS_COVERAGE_LOG)') - _WRAPPER_NAME_PREFIX = 'mbedtls_test_wrap_' - _WRAPPER_NAME_SUFFIX = '' - - def gather_data(self) -> None: - root_dir = build_tree.guess_mbedtls_root() - for header_name in ['crypto.h', 'crypto_extra.h']: - header_path = os.path.join(root_dir, 'include', 'psa', header_name) - c_parsing_helper.read_function_declarations(self.functions, header_path) - - _SKIP_FUNCTIONS = frozenset([ - 'mbedtls_psa_external_get_random', # not a library function - 'psa_get_key_domain_parameters', # client-side function - 'psa_get_key_slot_number', # client-side function - 'psa_key_derivation_verify_bytes', # not implemented yet - 'psa_key_derivation_verify_key', # not implemented yet - 'psa_set_key_domain_parameters', # client-side function - ]) - - def _skip_function(self, function: c_wrapper_generator.FunctionInfo) -> bool: - if function.return_type != 'psa_status_t': - return True - if function.name in self._SKIP_FUNCTIONS: - return True - return False - - # PAKE stuff: not implemented yet - _PAKE_STUFF = frozenset([ - 'psa_crypto_driver_pake_inputs_t *', - 'psa_pake_cipher_suite_t *', - ]) - - def _return_variable_name(self, - function: c_wrapper_generator.FunctionInfo) -> str: - """The name of the variable that will contain the return value.""" - if function.return_type == 'psa_status_t': - return 'status' - return super()._return_variable_name(function) - - _FUNCTION_GUARDS = c_wrapper_generator.Base._FUNCTION_GUARDS.copy() \ - #pylint: disable=protected-access - _FUNCTION_GUARDS.update({ - 'mbedtls_psa_register_se_key': 'defined(MBEDTLS_PSA_CRYPTO_SE_C)', - 'mbedtls_psa_inject_entropy': 'defined(MBEDTLS_PSA_INJECT_ENTROPY)', - 'mbedtls_psa_external_get_random': 'defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG)', - 'mbedtls_psa_platform_get_builtin_key': 'defined(MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS)', - }) - - @staticmethod - def _detect_buffer_parameters(arguments: List[c_parsing_helper.ArgumentInfo], - argument_names: List[str]) -> Iterator[BufferParameter]: - """Detect function arguments that are buffers (pointer, size [,length]).""" - types = ['' if arg.suffix else arg.type for arg in arguments] - # pairs = list of (type_of_arg_N, type_of_arg_N+1) - # where each type_of_arg_X is the empty string if the type is an array - # or there is no argument X. - pairs = enumerate(itertools.zip_longest(types, types[1:], fillvalue='')) - for i, t01 in pairs: - if (t01[0] == 'const uint8_t *' or t01[0] == 'uint8_t *') and \ - t01[1] == 'size_t': - yield BufferParameter(i, not t01[0].startswith('const '), - argument_names[i], argument_names[i+1]) - - @staticmethod - def _write_poison_buffer_parameter(out: typing_util.Writable, - param: BufferParameter, - poison: bool) -> None: - """Write poisoning or unpoisoning code for a buffer parameter. - - Write poisoning code if poison is true, unpoisoning code otherwise. - """ - out.write(' MBEDTLS_TEST_MEMORY_{}({}, {});\n'.format( - 'POISON' if poison else 'UNPOISON', - param.buffer_name, param.size_name - )) - - def _write_poison_buffer_parameters(self, out: typing_util.Writable, - buffer_parameters: List[BufferParameter], - poison: bool) -> None: - """Write poisoning or unpoisoning code for the buffer parameters. - - Write poisoning code if poison is true, unpoisoning code otherwise. - """ - if not buffer_parameters: - return - out.write('#if !defined(MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS)\n') - for param in buffer_parameters: - self._write_poison_buffer_parameter(out, param, poison) - out.write('#endif /* !defined(MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS) */\n') - - @staticmethod - def _parameter_should_be_copied(function_name: str, - _buffer_name: Optional[str]) -> bool: - """Whether the specified buffer argument to a PSA function should be copied. - """ - # False-positives that do not need buffer copying - if function_name in ('mbedtls_psa_inject_entropy', - 'psa_crypto_driver_pake_get_password', - 'psa_crypto_driver_pake_get_user', - 'psa_crypto_driver_pake_get_peer'): - return False - - return True - - def _write_function_call(self, out: typing_util.Writable, - function: c_wrapper_generator.FunctionInfo, - argument_names: List[str]) -> None: - buffer_parameters = list( - param - for param in self._detect_buffer_parameters(function.arguments, - argument_names) - if self._parameter_should_be_copied(function.name, - function.arguments[param.index].name)) - self._write_poison_buffer_parameters(out, buffer_parameters, True) - super()._write_function_call(out, function, argument_names) - self._write_poison_buffer_parameters(out, buffer_parameters, False) - - def _write_prologue(self, out: typing_util.Writable, header: bool) -> None: - super()._write_prologue(out, header) - out.write(""" -#if {} - -#include - -#include -#include -#include -""" - .format(self._CPP_GUARDS)) - - def _write_epilogue(self, out: typing_util.Writable, header: bool) -> None: - out.write(""" -#endif /* {} */ -""" - .format(self._CPP_GUARDS)) - super()._write_epilogue(out, header) - - -class PSALoggingWrapperGenerator(PSAWrapperGenerator, c_wrapper_generator.Logging): - """Generate a C source file containing wrapper functions that log PSA Crypto API calls.""" - - def __init__(self, stream: str) -> None: - super().__init__() - self.set_stream(stream) - - _PRINTF_TYPE_CAST = c_wrapper_generator.Logging._PRINTF_TYPE_CAST.copy() - _PRINTF_TYPE_CAST.update({ - 'mbedtls_svc_key_id_t': 'unsigned', - 'psa_algorithm_t': 'unsigned', - 'psa_drv_slot_number_t': 'unsigned long long', - 'psa_key_derivation_step_t': 'int', - 'psa_key_id_t': 'unsigned', - 'psa_key_slot_number_t': 'unsigned long long', - 'psa_key_lifetime_t': 'unsigned', - 'psa_key_type_t': 'unsigned', - 'psa_key_usage_flags_t': 'unsigned', - 'psa_pake_role_t': 'int', - 'psa_pake_step_t': 'int', - 'psa_status_t': 'int', - }) - - def _printf_parameters(self, typ: str, var: str) -> Tuple[str, List[str]]: - if typ.startswith('const '): - typ = typ[6:] - if typ == 'uint8_t *': - # Skip buffers - return '', [] - if typ.endswith('operation_t *'): - return '', [] - if typ in self._PAKE_STUFF: - return '', [] - if typ == 'psa_key_attributes_t *': - return (var + '={id=%u, lifetime=0x%08x, type=0x%08x, bits=%u, alg=%08x, usage=%08x}', - ['(unsigned) psa_get_key_{}({})'.format(field, var) - for field in ['id', 'lifetime', 'type', 'bits', 'algorithm', 'usage_flags']]) - return super()._printf_parameters(typ, var) - - -DEFAULT_C_OUTPUT_FILE_NAME = 'tests/src/psa_test_wrappers.c' -DEFAULT_H_OUTPUT_FILE_NAME = 'tests/include/test/psa_test_wrappers.h' - -def main() -> None: - parser = argparse.ArgumentParser(description=globals()['__doc__']) - parser.add_argument('--log', - help='Stream to log to (default: no logging code)') - parser.add_argument('--output-c', - metavar='FILENAME', - default=DEFAULT_C_OUTPUT_FILE_NAME, - help=('Output .c file path (default: {}; skip .c output if empty)' - .format(DEFAULT_C_OUTPUT_FILE_NAME))) - parser.add_argument('--output-h', - metavar='FILENAME', - default=DEFAULT_H_OUTPUT_FILE_NAME, - help=('Output .h file path (default: {}; skip .h output if empty)' - .format(DEFAULT_H_OUTPUT_FILE_NAME))) - options = parser.parse_args() - if options.log: - generator = PSALoggingWrapperGenerator(options.log) #type: PSAWrapperGenerator - else: - generator = PSAWrapperGenerator() - generator.gather_data() - if options.output_h: - generator.write_h_file(options.output_h) - if options.output_c: - generator.write_c_file(options.output_c) - -if __name__ == '__main__': - main() diff --git a/tests/scripts/generate_test_cert_macros.py b/tests/scripts/generate_test_cert_macros.py deleted file mode 100755 index 14270e0603..0000000000 --- a/tests/scripts/generate_test_cert_macros.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 - -""" -Generate `tests/src/test_certs.h` which includes certficaties/keys/certificate list for testing. -""" - -# -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - - -import os -import sys -import argparse -import jinja2 -import scripts_path # pylint: disable=unused-import -from mbedtls_framework.build_tree import guess_project_root - -TEST_DIR = os.path.join(guess_project_root(), 'tests') -DATA_FILES_PATH = os.path.join(TEST_DIR, 'data_files') - -INPUT_ARGS = [ - ("string", "TEST_CA_CRT_EC_PEM", DATA_FILES_PATH + "/test-ca2.crt"), - ("binary", "TEST_CA_CRT_EC_DER", DATA_FILES_PATH + "/test-ca2.crt.der"), - ("string", "TEST_CA_KEY_EC_PEM", DATA_FILES_PATH + "/test-ca2.key.enc"), - ("password", "TEST_CA_PWD_EC_PEM", "PolarSSLTest"), - ("binary", "TEST_CA_KEY_EC_DER", DATA_FILES_PATH + "/test-ca2.key.der"), - ("string", "TEST_CA_CRT_RSA_SHA256_PEM", DATA_FILES_PATH + "/test-ca-sha256.crt"), - ("binary", "TEST_CA_CRT_RSA_SHA256_DER", DATA_FILES_PATH + "/test-ca-sha256.crt.der"), - ("string", "TEST_CA_CRT_RSA_SHA1_PEM", DATA_FILES_PATH + "/test-ca-sha1.crt"), - ("binary", "TEST_CA_CRT_RSA_SHA1_DER", DATA_FILES_PATH + "/test-ca-sha1.crt.der"), - ("string", "TEST_CA_KEY_RSA_PEM", DATA_FILES_PATH + "/test-ca.key"), - ("password", "TEST_CA_PWD_RSA_PEM", "PolarSSLTest"), - ("binary", "TEST_CA_KEY_RSA_DER", DATA_FILES_PATH + "/test-ca.key.der"), - ("string", "TEST_SRV_CRT_EC_PEM", DATA_FILES_PATH + "/server5.crt"), - ("binary", "TEST_SRV_CRT_EC_DER", DATA_FILES_PATH + "/server5.crt.der"), - ("string", "TEST_SRV_KEY_EC_PEM", DATA_FILES_PATH + "/server5.key"), - ("binary", "TEST_SRV_KEY_EC_DER", DATA_FILES_PATH + "/server5.key.der"), - ("string", "TEST_SRV_CRT_RSA_SHA256_PEM", DATA_FILES_PATH + "/server2-sha256.crt"), - ("binary", "TEST_SRV_CRT_RSA_SHA256_DER", DATA_FILES_PATH + "/server2-sha256.crt.der"), - ("string", "TEST_SRV_CRT_RSA_SHA1_PEM", DATA_FILES_PATH + "/server2.crt"), - ("binary", "TEST_SRV_CRT_RSA_SHA1_DER", DATA_FILES_PATH + "/server2.crt.der"), - ("string", "TEST_SRV_KEY_RSA_PEM", DATA_FILES_PATH + "/server2.key"), - ("binary", "TEST_SRV_KEY_RSA_DER", DATA_FILES_PATH + "/server2.key.der"), - ("string", "TEST_CLI_CRT_EC_PEM", DATA_FILES_PATH + "/cli2.crt"), - ("binary", "TEST_CLI_CRT_EC_DER", DATA_FILES_PATH + "/cli2.crt.der"), - ("string", "TEST_CLI_KEY_EC_PEM", DATA_FILES_PATH + "/cli2.key"), - ("binary", "TEST_CLI_KEY_EC_DER", DATA_FILES_PATH + "/cli2.key.der"), - ("string", "TEST_CLI_CRT_RSA_PEM", DATA_FILES_PATH + "/cli-rsa-sha256.crt"), - ("binary", "TEST_CLI_CRT_RSA_DER", DATA_FILES_PATH + "/cli-rsa-sha256.crt.der"), - ("string", "TEST_CLI_KEY_RSA_PEM", DATA_FILES_PATH + "/cli-rsa.key"), - ("binary", "TEST_CLI_KEY_RSA_DER", DATA_FILES_PATH + "/cli-rsa.key.der"), -] - -def main(): - parser = argparse.ArgumentParser() - default_output_path = os.path.join(TEST_DIR, 'src', 'test_certs.h') - parser.add_argument('--output', type=str, default=default_output_path) - parser.add_argument('--list-dependencies', action='store_true') - args = parser.parse_args() - - if args.list_dependencies: - files_list = [arg[2] for arg in INPUT_ARGS] - print(" ".join(files_list)) - return - - generate(INPUT_ARGS, output=args.output) - -#pylint: disable=dangerous-default-value, unused-argument -def generate(values=[], output=None): - """Generate C header file. - """ - template_loader = jinja2.FileSystemLoader(DATA_FILES_PATH) - template_env = jinja2.Environment( - loader=template_loader, lstrip_blocks=True, trim_blocks=True, - keep_trailing_newline=True) - - def read_as_c_array(filename): - with open(filename, 'rb') as f: - data = f.read(12) - while data: - yield ', '.join(['{:#04x}'.format(b) for b in data]) - data = f.read(12) - - def read_lines(filename): - with open(filename) as f: - try: - for line in f: - yield line.strip() - except: - print(filename) - raise - - def put_to_column(value, position=0): - return ' '*position + value - - template_env.filters['read_as_c_array'] = read_as_c_array - template_env.filters['read_lines'] = read_lines - template_env.filters['put_to_column'] = put_to_column - - template = template_env.get_template('test_certs.h.jinja2') - - with open(output, 'w') as f: - f.write(template.render(macros=values)) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/tests/scripts/generate_test_code.py b/tests/scripts/generate_test_code.py deleted file mode 100755 index 5f711bfb19..0000000000 --- a/tests/scripts/generate_test_code.py +++ /dev/null @@ -1,1277 +0,0 @@ -#!/usr/bin/env python3 -# Test suites code generator. -# -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -""" -This script is a key part of Mbed TLS test suites framework. For -understanding the script it is important to understand the -framework. This doc string contains a summary of the framework -and explains the function of this script. - -Mbed TLS test suites: -===================== -Scope: ------- -The test suites focus on unit testing the crypto primitives and also -include x509 parser tests. Tests can be added to test any Mbed TLS -module. However, the framework is not capable of testing SSL -protocol, since that requires full stack execution and that is best -tested as part of the system test. - -Test case definition: ---------------------- -Tests are defined in a test_suite_[.].data -file. A test definition contains: - test name - optional build macro dependencies - test function - test parameters - -Test dependencies are build macros that can be specified to indicate -the build config in which the test is valid. For example if a test -depends on a feature that is only enabled by defining a macro. Then -that macro should be specified as a dependency of the test. - -Test function is the function that implements the test steps. This -function is specified for different tests that perform same steps -with different parameters. - -Test parameters are specified in string form separated by ':'. -Parameters can be of type string, binary data specified as hex -string and integer constants specified as integer, macro or -as an expression. Following is an example test definition: - - AES 128 GCM Encrypt and decrypt 8 bytes - depends_on:MBEDTLS_AES_C:MBEDTLS_GCM_C - enc_dec_buf:MBEDTLS_CIPHER_AES_128_GCM:"AES-128-GCM":128:8:-1 - -Test functions: ---------------- -Test functions are coded in C in test_suite_.function files. -Functions file is itself not compilable and contains special -format patterns to specify test suite dependencies, start and end -of functions and function dependencies. Check any existing functions -file for example. - -Execution: ----------- -Tests are executed in 3 steps: -- Generating test_suite_[.].c file - for each corresponding .data file. -- Building each source file into executables. -- Running each executable and printing report. - -Generating C test source requires more than just the test functions. -Following extras are required: -- Process main() -- Reading .data file and dispatching test cases. -- Platform specific test case execution -- Dependency checking -- Integer expression evaluation -- Test function dispatch - -Build dependencies and integer expressions (in the test parameters) -are specified as strings in the .data file. Their run time value is -not known at the generation stage. Hence, they need to be translated -into run time evaluations. This script generates the run time checks -for dependencies and integer expressions. - -Similarly, function names have to be translated into function calls. -This script also generates code for function dispatch. - -The extra code mentioned here is either generated by this script -or it comes from the input files: helpers file, platform file and -the template file. - -Helper file: ------------- -Helpers file contains common helper/utility functions and data. - -Platform file: --------------- -Platform file contains platform specific setup code and test case -dispatch code. For example, host_test.function reads test data -file from host's file system and dispatches tests. - -Template file: ---------- -Template file for example main_test.function is a template C file in -which generated code and code from input files is substituted to -generate a compilable C file. It also contains skeleton functions for -dependency checks, expression evaluation and function dispatch. These -functions are populated with checks and return codes by this script. - -Template file contains "replacement" fields that are formatted -strings processed by Python string.Template.substitute() method. - -This script: -============ -Core function of this script is to fill the template file with -code that is generated or read from helpers and platform files. - -This script replaces following fields in the template and generates -the test source file: - -__MBEDTLS_TEST_TEMPLATE__TEST_COMMON_HELPERS - All common code from helpers.function - is substituted here. -__MBEDTLS_TEST_TEMPLATE__FUNCTIONS_CODE - Test functions are substituted here - from the input test_suit_xyz.function - file. C preprocessor checks are generated - for the build dependencies specified - in the input file. This script also - generates wrappers for the test - functions with code to expand the - string parameters read from the data - file. -__MBEDTLS_TEST_TEMPLATE__EXPRESSION_CODE - This script enumerates the - expressions in the .data file and - generates code to handle enumerated - expression Ids and return the values. -__MBEDTLS_TEST_TEMPLATE__DEP_CHECK_CODE - This script enumerates all - build dependencies and generate - code to handle enumerated build - dependency Id and return status: if - the dependency is defined or not. -__MBEDTLS_TEST_TEMPLATE__DISPATCH_CODE - This script enumerates the functions - specified in the input test data file - and generates the initializer for the - function table in the template - file. -__MBEDTLS_TEST_TEMPLATE__PLATFORM_CODE - Platform specific setup and test - dispatch code. - -""" - - -import os -import re -import sys -import string -import argparse - - -# Types recognized as signed integer arguments in test functions. -SIGNED_INTEGER_TYPES = frozenset([ - 'char', - 'short', - 'short int', - 'int', - 'int8_t', - 'int16_t', - 'int32_t', - 'int64_t', - 'intmax_t', - 'long', - 'long int', - 'long long int', - 'mbedtls_mpi_sint', - 'psa_status_t', -]) -# Types recognized as string arguments in test functions. -STRING_TYPES = frozenset(['char*', 'const char*', 'char const*']) -# Types recognized as hex data arguments in test functions. -DATA_TYPES = frozenset(['data_t*', 'const data_t*', 'data_t const*']) - -BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/' -END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/' - -BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/' -END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/' - -BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES' -END_DEP_REGEX = r'END_DEPENDENCIES' - -BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P.*?)\s*\*/' -END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/' - -DEPENDENCY_REGEX = r'depends_on:(?P.*)' -C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*' -CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?' -# forbid 0ddd which might be accidentally octal or accidentally decimal -CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)' -CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX, - CONDITION_OPERATOR_REGEX, - CONDITION_VALUE_REGEX) -TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P\w+)\s*\(' -FUNCTION_ARG_LIST_END_REGEX = r'.*\)' -EXIT_LABEL_REGEX = r'^exit:' - - -class GeneratorInputError(Exception): - """ - Exception to indicate error in the input files to this script. - This includes missing patterns, test function names and other - parsing errors. - """ - pass - - -class FileWrapper: - """ - This class extends the file object with attribute line_no, - that indicates line number for the line that is read. - """ - - def __init__(self, file_name) -> None: - """ - Instantiate the file object and initialize the line number to 0. - - :param file_name: File path to open. - """ - # private mix-in file object - self._f = open(file_name, 'rb') - self._line_no = 0 - - def __iter__(self): - return self - - def __next__(self): - """ - This method makes FileWrapper iterable. - It counts the line numbers as each line is read. - - :return: Line read from file. - """ - line = self._f.__next__() - self._line_no += 1 - # Convert byte array to string with correct encoding and - # strip any whitespaces added in the decoding process. - return line.decode(sys.getdefaultencoding()).rstrip()+ '\n' - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self._f.__exit__(exc_type, exc_val, exc_tb) - - @property - def line_no(self): - """ - Property that indicates line number for the line that is read. - """ - return self._line_no - - @property - def name(self): - """ - Property that indicates name of the file that is read. - """ - return self._f.name - - -def split_dep(dep): - """ - Split NOT character '!' from dependency. Used by gen_dependencies() - - :param dep: Dependency list - :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for - MACRO. - """ - return ('!', dep[1:]) if dep[0] == '!' else ('', dep) - - -def gen_dependencies(dependencies): - """ - Test suite data and functions specifies compile time dependencies. - This function generates C preprocessor code from the input - dependency list. Caller uses the generated preprocessor code to - wrap dependent code. - A dependency in the input list can have a leading '!' character - to negate a condition. '!' is separated from the dependency using - function split_dep() and proper preprocessor check is generated - accordingly. - - :param dependencies: List of dependencies. - :return: if defined and endif code with macro annotations for - readability. - """ - dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in - map(split_dep, dependencies)]) - dep_end = ''.join(['#endif /* %s */\n' % - x for x in reversed(dependencies)]) - - return dep_start, dep_end - - -def gen_dependencies_one_line(dependencies): - """ - Similar to gen_dependencies() but generates dependency checks in one line. - Useful for generating code with #else block. - - :param dependencies: List of dependencies. - :return: Preprocessor check code - """ - defines = '#if ' if dependencies else '' - defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map( - split_dep, dependencies)]) - return defines - - -def gen_function_wrapper(name, local_vars, args_dispatch): - """ - Creates test function wrapper code. A wrapper has the code to - unpack parameters from parameters[] array. - - :param name: Test function name - :param local_vars: Local variables declaration code - :param args_dispatch: List of dispatch arguments. - Ex: ['(char *) params[0]', '*((int *) params[1])'] - :return: Test function wrapper. - """ - # Then create the wrapper - wrapper = ''' -void {name}_wrapper( void ** params ) -{{ -{unused_params}{locals} - {name}( {args} ); -}} -'''.format(name=name, - unused_params='' if args_dispatch else ' (void)params;\n', - args=', '.join(args_dispatch), - locals=local_vars) - return wrapper - - -def gen_dispatch(name, dependencies): - """ - Test suite code template main_test.function defines a C function - array to contain test case functions. This function generates an - initializer entry for a function in that array. The entry is - composed of a compile time check for the test function - dependencies. At compile time the test function is assigned when - dependencies are met, else NULL is assigned. - - :param name: Test function name - :param dependencies: List of dependencies - :return: Dispatch code. - """ - if dependencies: - preprocessor_check = gen_dependencies_one_line(dependencies) - dispatch_code = ''' -{preprocessor_check} - {name}_wrapper, -#else - NULL, -#endif -'''.format(preprocessor_check=preprocessor_check, name=name) - else: - dispatch_code = ''' - {name}_wrapper, -'''.format(name=name) - - return dispatch_code - - -def parse_until_pattern(funcs_f, end_regex): - """ - Matches pattern end_regex to the lines read from the file object. - Returns the lines read until end pattern is matched. - - :param funcs_f: file object for .function file - :param end_regex: Pattern to stop parsing - :return: Lines read before the end pattern - """ - headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name) - for line in funcs_f: - if re.search(end_regex, line): - break - headers += line - else: - raise GeneratorInputError("file: %s - end pattern [%s] not found!" % - (funcs_f.name, end_regex)) - - return headers - - -def validate_dependency(dependency): - """ - Validates a C macro and raises GeneratorInputError on invalid input. - :param dependency: Input macro dependency - :return: input dependency stripped of leading & trailing white spaces. - """ - dependency = dependency.strip() - if not re.match(CONDITION_REGEX, dependency, re.I): - raise GeneratorInputError('Invalid dependency %s' % dependency) - return dependency - - -def parse_dependencies(inp_str): - """ - Parses dependencies out of inp_str, validates them and returns a - list of macros. - - :param inp_str: Input string with macros delimited by ':'. - :return: list of dependencies - """ - dependencies = list(map(validate_dependency, inp_str.split(':'))) - return dependencies - - -def parse_suite_dependencies(funcs_f): - """ - Parses test suite dependencies specified at the top of a - .function file, that starts with pattern BEGIN_DEPENDENCIES - and end with END_DEPENDENCIES. Dependencies are specified - after pattern 'depends_on:' and are delimited by ':'. - - :param funcs_f: file object for .function file - :return: List of test suite dependencies. - """ - dependencies = [] - for line in funcs_f: - match = re.search(DEPENDENCY_REGEX, line.strip()) - if match: - try: - dependencies = parse_dependencies(match.group('dependencies')) - except GeneratorInputError as error: - raise GeneratorInputError( - str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no)) - if re.search(END_DEP_REGEX, line): - break - else: - raise GeneratorInputError("file: %s - end dependency pattern [%s]" - " not found!" % (funcs_f.name, - END_DEP_REGEX)) - - return dependencies - - -def parse_function_dependencies(line): - """ - Parses function dependencies, that are in the same line as - comment BEGIN_CASE. Dependencies are specified after pattern - 'depends_on:' and are delimited by ':'. - - :param line: Line from .function file that has dependencies. - :return: List of dependencies. - """ - dependencies = [] - match = re.search(BEGIN_CASE_REGEX, line) - dep_str = match.group('depends_on') - if dep_str: - match = re.search(DEPENDENCY_REGEX, dep_str) - if match: - dependencies += parse_dependencies(match.group('dependencies')) - - return dependencies - - -ARGUMENT_DECLARATION_REGEX = re.compile(r'(.+?) ?(?:\bconst\b)? ?(\w+)\Z', re.S) -def parse_function_argument(arg, arg_idx, args, local_vars, args_dispatch): - """ - Parses one test function's argument declaration. - - :param arg: argument declaration. - :param arg_idx: current wrapper argument index. - :param args: accumulator of arguments' internal types. - :param local_vars: accumulator of internal variable declarations. - :param args_dispatch: accumulator of argument usage expressions. - :return: the number of new wrapper arguments, - or None if the argument declaration is invalid. - """ - # Normalize whitespace - arg = arg.strip() - arg = re.sub(r'\s*\*\s*', r'*', arg) - arg = re.sub(r'\s+', r' ', arg) - # Extract name and type - m = ARGUMENT_DECLARATION_REGEX.search(arg) - if not m: - # E.g. "int x[42]" - return None - typ, _ = m.groups() - if typ in SIGNED_INTEGER_TYPES: - args.append('int') - args_dispatch.append('((mbedtls_test_argument_t *) params[%d])->sint' % arg_idx) - return 1 - if typ in STRING_TYPES: - args.append('char*') - args_dispatch.append('(char *) params[%d]' % arg_idx) - return 1 - if typ in DATA_TYPES: - args.append('hex') - # create a structure - pointer_initializer = '(uint8_t *) params[%d]' % arg_idx - len_initializer = '((mbedtls_test_argument_t *) params[%d])->len' % (arg_idx+1) - local_vars.append(' data_t data%d = {%s, %s};\n' % - (arg_idx, pointer_initializer, len_initializer)) - args_dispatch.append('&data%d' % arg_idx) - return 2 - return None - -ARGUMENT_LIST_REGEX = re.compile(r'\((.*?)\)', re.S) -def parse_function_arguments(line): - """ - Parses test function signature for validation and generates - a dispatch wrapper function that translates input test vectors - read from the data file into test function arguments. - - :param line: Line from .function file that has a function - signature. - :return: argument list, local variables for - wrapper function and argument dispatch code. - """ - # Process arguments, ex: arg1, arg2 ) - # This script assumes that the argument list is terminated by ')' - # i.e. the test functions will not have a function pointer - # argument. - m = ARGUMENT_LIST_REGEX.search(line) - arg_list = m.group(1).strip() - if arg_list in ['', 'void']: - return [], '', [] - args = [] - local_vars = [] - args_dispatch = [] - arg_idx = 0 - for arg in arg_list.split(','): - indexes = parse_function_argument(arg, arg_idx, - args, local_vars, args_dispatch) - if indexes is None: - raise ValueError("Test function arguments can only be 'int', " - "'char *' or 'data_t'\n%s" % line) - arg_idx += indexes - - return args, ''.join(local_vars), args_dispatch - - -def generate_function_code(name, code, local_vars, args_dispatch, - dependencies): - """ - Generate function code with preprocessor checks and parameter dispatch - wrapper. - - :param name: Function name - :param code: Function code - :param local_vars: Local variables for function wrapper - :param args_dispatch: Argument dispatch code - :param dependencies: Preprocessor dependencies list - :return: Final function code - """ - # Add exit label if not present - if code.find('exit:') == -1: - split_code = code.rsplit('}', 1) - if len(split_code) == 2: - code = """exit: - ; -}""".join(split_code) - - code += gen_function_wrapper(name, local_vars, args_dispatch) - preprocessor_check_start, preprocessor_check_end = \ - gen_dependencies(dependencies) - return preprocessor_check_start + code + preprocessor_check_end - -COMMENT_START_REGEX = re.compile(r'/[*/]') - -def skip_comments(line, stream): - """Remove comments in line. - - If the line contains an unfinished comment, read more lines from stream - until the line that contains the comment. - - :return: The original line with inner comments replaced by spaces. - Trailing comments and whitespace may be removed completely. - """ - pos = 0 - while True: - opening = COMMENT_START_REGEX.search(line, pos) - if not opening: - break - if line[opening.start(0) + 1] == '/': # //... - continuation = line - # Count the number of line breaks, to keep line numbers aligned - # in the output. - line_count = 1 - while continuation.endswith('\\\n'): - # This errors out if the file ends with an unfinished line - # comment. That's acceptable to not complicate the code further. - continuation = next(stream) - line_count += 1 - return line[:opening.start(0)].rstrip() + '\n' * line_count - # Parsing /*...*/, looking for the end - closing = line.find('*/', opening.end(0)) - while closing == -1: - # This errors out if the file ends with an unfinished block - # comment. That's acceptable to not complicate the code further. - line += next(stream) - closing = line.find('*/', opening.end(0)) - pos = closing + 2 - # Replace inner comment by spaces. There needs to be at least one space - # for things like 'int/*ihatespaces*/foo'. Go further and preserve the - # width of the comment and line breaks, this way positions in error - # messages remain correct. - line = (line[:opening.start(0)] + - re.sub(r'.', r' ', line[opening.start(0):pos]) + - line[pos:]) - # Strip whitespace at the end of lines (it's irrelevant to error messages). - return re.sub(r' +(\n|\Z)', r'\1', line) - -def parse_function_code(funcs_f, dependencies, suite_dependencies): - """ - Parses out a function from function file object and generates - function and dispatch code. - - :param funcs_f: file object of the functions file. - :param dependencies: List of dependencies - :param suite_dependencies: List of test suite dependencies - :return: Function name, arguments, function code and dispatch code. - """ - line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name) - code = '' - has_exit_label = False - for line in funcs_f: - # Check function signature. Function signature may be split - # across multiple lines. Here we try to find the start of - # arguments list, then remove '\n's and apply the regex to - # detect function start. - line = skip_comments(line, funcs_f) - up_to_arg_list_start = code + line[:line.find('(') + 1] - match = re.match(TEST_FUNCTION_VALIDATION_REGEX, - up_to_arg_list_start.replace('\n', ' '), re.I) - if match: - # check if we have full signature i.e. split in more lines - name = match.group('func_name') - if not re.match(FUNCTION_ARG_LIST_END_REGEX, line): - for lin in funcs_f: - line += skip_comments(lin, funcs_f) - if re.search(FUNCTION_ARG_LIST_END_REGEX, line): - break - args, local_vars, args_dispatch = parse_function_arguments( - line) - code += line - break - code += line - else: - raise GeneratorInputError("file: %s - Test functions not found!" % - funcs_f.name) - - # Prefix test function name with 'test_' - code = code.replace(name, 'test_' + name, 1) - name = 'test_' + name - - # If a test function has no arguments then add 'void' argument to - # avoid "-Wstrict-prototypes" warnings from clang - if len(args) == 0: - code = code.replace('()', '(void)', 1) - - for line in funcs_f: - if re.search(END_CASE_REGEX, line): - break - if not has_exit_label: - has_exit_label = \ - re.search(EXIT_LABEL_REGEX, line.strip()) is not None - code += line - else: - raise GeneratorInputError("file: %s - end case pattern [%s] not " - "found!" % (funcs_f.name, END_CASE_REGEX)) - - code = line_directive + code - code = generate_function_code(name, code, local_vars, args_dispatch, - dependencies) - dispatch_code = gen_dispatch(name, suite_dependencies + dependencies) - return (name, args, code, dispatch_code) - - -def parse_functions(funcs_f): - """ - Parses a test_suite_xxx.function file and returns information - for generating a C source file for the test suite. - - :param funcs_f: file object of the functions file. - :return: List of test suite dependencies, test function dispatch - code, function code and a dict with function identifiers - and arguments info. - """ - suite_helpers = '' - suite_dependencies = [] - suite_functions = '' - func_info = {} - function_idx = 0 - dispatch_code = '' - for line in funcs_f: - if re.search(BEGIN_HEADER_REGEX, line): - suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX) - elif re.search(BEGIN_SUITE_HELPERS_REGEX, line): - suite_helpers += parse_until_pattern(funcs_f, - END_SUITE_HELPERS_REGEX) - elif re.search(BEGIN_DEP_REGEX, line): - suite_dependencies += parse_suite_dependencies(funcs_f) - elif re.search(BEGIN_CASE_REGEX, line): - try: - dependencies = parse_function_dependencies(line) - except GeneratorInputError as error: - raise GeneratorInputError( - "%s:%d: %s" % (funcs_f.name, funcs_f.line_no, - str(error))) - func_name, args, func_code, func_dispatch =\ - parse_function_code(funcs_f, dependencies, suite_dependencies) - suite_functions += func_code - # Generate dispatch code and enumeration info - if func_name in func_info: - raise GeneratorInputError( - "file: %s - function %s re-declared at line %d" % - (funcs_f.name, func_name, funcs_f.line_no)) - func_info[func_name] = (function_idx, args) - dispatch_code += '/* Function Id: %d */\n' % function_idx - dispatch_code += func_dispatch - function_idx += 1 - - func_code = (suite_helpers + - suite_functions).join(gen_dependencies(suite_dependencies)) - return suite_dependencies, dispatch_code, func_code, func_info - - -def escaped_split(inp_str, split_char): - """ - Split inp_str on character split_char but ignore if escaped. - Since, return value is used to write back to the intermediate - data file, any escape characters in the input are retained in the - output. - - :param inp_str: String to split - :param split_char: Split character - :return: List of splits - """ - if len(split_char) > 1: - raise ValueError('Expected split character. Found string!') - out = re.sub(r'(\\.)|' + split_char, - lambda m: m.group(1) or '\n', inp_str, - len(inp_str)).split('\n') - out = [x for x in out if x] - return out - - -def parse_test_data(data_f): - """ - Parses .data file for each test case name, test function name, - test dependencies and test arguments. This information is - correlated with the test functions file for generating an - intermediate data file replacing the strings for test function - names, dependencies and integer constant expressions with - identifiers. Mainly for optimising space for on-target - execution. - - :param data_f: file object of the data file. - :return: Generator that yields line number, test name, function name, - dependency list and function argument list. - """ - __state_read_name = 0 - __state_read_args = 1 - state = __state_read_name - dependencies = [] - name = '' - for line in data_f: - line = line.strip() - # Skip comments - if line.startswith('#'): - continue - - # Blank line indicates end of test - if not line: - if state == __state_read_args: - raise GeneratorInputError("[%s:%d] Newline before arguments. " - "Test function and arguments " - "missing for %s" % - (data_f.name, data_f.line_no, name)) - continue - - if state == __state_read_name: - # Read test name - name = line - state = __state_read_args - elif state == __state_read_args: - # Check dependencies - match = re.search(DEPENDENCY_REGEX, line) - if match: - try: - dependencies = parse_dependencies( - match.group('dependencies')) - except GeneratorInputError as error: - raise GeneratorInputError( - str(error) + " - %s:%d" % - (data_f.name, data_f.line_no)) - else: - # Read test vectors - parts = escaped_split(line, ':') - test_function = parts[0] - args = parts[1:] - yield data_f.line_no, name, test_function, dependencies, args - dependencies = [] - state = __state_read_name - if state == __state_read_args: - raise GeneratorInputError("[%s:%d] Newline before arguments. " - "Test function and arguments missing for " - "%s" % (data_f.name, data_f.line_no, name)) - - -def gen_dep_check(dep_id, dep): - """ - Generate code for checking dependency with the associated - identifier. - - :param dep_id: Dependency identifier - :param dep: Dependency macro - :return: Dependency check code - """ - if dep_id < 0: - raise GeneratorInputError("Dependency Id should be a positive " - "integer.") - _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep) - if not dep: - raise GeneratorInputError("Dependency should not be an empty string.") - - dependency = re.match(CONDITION_REGEX, dep, re.I) - if not dependency: - raise GeneratorInputError('Invalid dependency %s' % dep) - - _defined = '' if dependency.group(2) else 'defined' - _cond = dependency.group(2) if dependency.group(2) else '' - _value = dependency.group(3) if dependency.group(3) else '' - - dep_check = ''' - case {id}: - {{ -#if {_not}{_defined}({macro}{_cond}{_value}) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - }} - break;'''.format(_not=_not, _defined=_defined, - macro=dependency.group(1), id=dep_id, - _cond=_cond, _value=_value) - return dep_check - - -def gen_expression_check(exp_id, exp): - """ - Generates code for evaluating an integer expression using - associated expression Id. - - :param exp_id: Expression Identifier - :param exp: Expression/Macro - :return: Expression check code - """ - if exp_id < 0: - raise GeneratorInputError("Expression Id should be a positive " - "integer.") - if not exp: - raise GeneratorInputError("Expression should not be an empty string.") - exp_code = ''' - case {exp_id}: - {{ - *out_value = {expression}; - }} - break;'''.format(exp_id=exp_id, expression=exp) - return exp_code - - -def write_dependencies(out_data_f, test_dependencies, unique_dependencies): - """ - Write dependencies to intermediate test data file, replacing - the string form with identifiers. Also, generates dependency - check code. - - :param out_data_f: Output intermediate data file - :param test_dependencies: Dependencies - :param unique_dependencies: Mutable list to track unique dependencies - that are global to this re-entrant function. - :return: returns dependency check code. - """ - dep_check_code = '' - if test_dependencies: - out_data_f.write('depends_on') - for dep in test_dependencies: - if dep not in unique_dependencies: - unique_dependencies.append(dep) - dep_id = unique_dependencies.index(dep) - dep_check_code += gen_dep_check(dep_id, dep) - else: - dep_id = unique_dependencies.index(dep) - out_data_f.write(':' + str(dep_id)) - out_data_f.write('\n') - return dep_check_code - - -INT_VAL_REGEX = re.compile(r'-?(\d+|0x[0-9a-f]+)$', re.I) -def val_is_int(val: str) -> bool: - """Whether val is suitable as an 'int' parameter in the .datax file.""" - if not INT_VAL_REGEX.match(val): - return False - # Limit the range to what is guaranteed to get through strtol() - return abs(int(val, 0)) <= 0x7fffffff - -def write_parameters(out_data_f, test_args, func_args, unique_expressions): - """ - Writes test parameters to the intermediate data file, replacing - the string form with identifiers. Also, generates expression - check code. - - :param out_data_f: Output intermediate data file - :param test_args: Test parameters - :param func_args: Function arguments - :param unique_expressions: Mutable list to track unique - expressions that are global to this re-entrant function. - :return: Returns expression check code. - """ - expression_code = '' - for i, _ in enumerate(test_args): - typ = func_args[i] - val = test_args[i] - - # Pass small integer constants literally. This reduces the size of - # the C code. Register anything else as an expression. - if typ == 'int' and not val_is_int(val): - typ = 'exp' - if val not in unique_expressions: - unique_expressions.append(val) - # exp_id can be derived from len(). But for - # readability and consistency with case of existing - # let's use index(). - exp_id = unique_expressions.index(val) - expression_code += gen_expression_check(exp_id, val) - val = exp_id - else: - val = unique_expressions.index(val) - out_data_f.write(':' + typ + ':' + str(val)) - out_data_f.write('\n') - return expression_code - - -def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code): - """ - Generates preprocessor checks for test suite dependencies. - - :param suite_dependencies: Test suite dependencies read from the - .function file. - :param dep_check_code: Dependency check code - :param expression_code: Expression check code - :return: Dependency and expression code guarded by test suite - dependencies. - """ - if suite_dependencies: - preprocessor_check = gen_dependencies_one_line(suite_dependencies) - dep_check_code = ''' -{preprocessor_check} -{code} -#endif -'''.format(preprocessor_check=preprocessor_check, code=dep_check_code) - expression_code = ''' -{preprocessor_check} -{code} -#endif -'''.format(preprocessor_check=preprocessor_check, code=expression_code) - return dep_check_code, expression_code - - -def get_function_info(func_info, function_name, line_no): - """Look up information about a test function by name. - - Raise an informative expression if function_name is not found. - - :param func_info: dictionary mapping function names to their information. - :param function_name: the function name as written in the .function and - .data files. - :param line_no: line number for error messages. - :return Function information (id, args). - """ - test_function_name = 'test_' + function_name - if test_function_name not in func_info: - raise GeneratorInputError("%d: Function %s not found!" % - (line_no, test_function_name)) - return func_info[test_function_name] - - -def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies): - """ - This function reads test case name, dependencies and test vectors - from the .data file. This information is correlated with the test - functions file for generating an intermediate data file replacing - the strings for test function names, dependencies and integer - constant expressions with identifiers. Mainly for optimising - space for on-target execution. - It also generates test case dependency check code and expression - evaluation code. - - :param data_f: Data file object - :param out_data_f: Output intermediate data file - :param func_info: Dict keyed by function and with function id - and arguments info - :param suite_dependencies: Test suite dependencies - :return: Returns dependency and expression check code - """ - unique_dependencies = [] - unique_expressions = [] - dep_check_code = '' - expression_code = '' - for line_no, test_name, function_name, test_dependencies, test_args in \ - parse_test_data(data_f): - out_data_f.write(test_name + '\n') - - # Write dependencies - dep_check_code += write_dependencies(out_data_f, test_dependencies, - unique_dependencies) - - # Write test function name - func_id, func_args = \ - get_function_info(func_info, function_name, line_no) - out_data_f.write(str(func_id)) - - # Write parameters - if len(test_args) != len(func_args): - raise GeneratorInputError("%d: Invalid number of arguments in test " - "%s. See function %s signature." % - (line_no, test_name, function_name)) - expression_code += write_parameters(out_data_f, test_args, func_args, - unique_expressions) - - # Write a newline as test case separator - out_data_f.write('\n') - - dep_check_code, expression_code = gen_suite_dep_checks( - suite_dependencies, dep_check_code, expression_code) - return dep_check_code, expression_code - - -def add_input_info(funcs_file, data_file, template_file, - c_file, snippets): - """ - Add generator input info in snippets. - - :param funcs_file: Functions file object - :param data_file: Data file object - :param template_file: Template file object - :param c_file: Output C file object - :param snippets: Dictionary to contain code pieces to be - substituted in the template. - :return: - """ - snippets['test_file'] = c_file - snippets['test_main_file'] = template_file - snippets['test_case_file'] = funcs_file - snippets['test_case_data_file'] = data_file - - -def read_code_from_input_files(platform_file, helpers_file, - out_data_file, snippets): - """ - Read code from input files and create substitutions for replacement - strings in the template file. - - :param platform_file: Platform file object - :param helpers_file: Helper functions file object - :param out_data_file: Output intermediate data file object - :param snippets: Dictionary to contain code pieces to be - substituted in the template. - :return: - """ - # Read helpers - with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \ - platform_f: - snippets['test_common_helper_file'] = helpers_file - snippets['test_common_helpers'] = help_f.read() - snippets['test_platform_file'] = platform_file - snippets['platform_code'] = platform_f.read().replace( - 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\' - - -def write_test_source_file(template_file, c_file, snippets): - """ - Write output source file with generated source code. - - :param template_file: Template file name - :param c_file: Output source file - :param snippets: Generated and code snippets - :return: - """ - - # Create a placeholder pattern with the correct named capture groups - # to override the default provided with Template. - # Match nothing (no way of escaping placeholders). - escaped = "(?P(?!))" - # Match the "__MBEDTLS_TEST_TEMPLATE__PLACEHOLDER_NAME" pattern. - named = "__MBEDTLS_TEST_TEMPLATE__(?P[A-Z][_A-Z0-9]*)" - # Match nothing (no braced placeholder syntax). - braced = "(?P(?!))" - # If not already matched, a "__MBEDTLS_TEST_TEMPLATE__" prefix is invalid. - invalid = "(?P__MBEDTLS_TEST_TEMPLATE__)" - placeholder_pattern = re.compile("|".join([escaped, named, braced, invalid])) - - with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f: - for line_no, line in enumerate(template_f.readlines(), 1): - # Update line number. +1 as #line directive sets next line number - snippets['line_no'] = line_no + 1 - template = string.Template(line) - template.pattern = placeholder_pattern - snippets = {k.upper():v for (k, v) in snippets.items()} - code = template.substitute(**snippets) - c_f.write(code) - - -def parse_function_file(funcs_file, snippets): - """ - Parse function file and generate function dispatch code. - - :param funcs_file: Functions file name - :param snippets: Dictionary to contain code pieces to be - substituted in the template. - :return: - """ - with FileWrapper(funcs_file) as funcs_f: - suite_dependencies, dispatch_code, func_code, func_info = \ - parse_functions(funcs_f) - snippets['functions_code'] = func_code - snippets['dispatch_code'] = dispatch_code - return suite_dependencies, func_info - - -def generate_intermediate_data_file(data_file, out_data_file, - suite_dependencies, func_info, snippets): - """ - Generates intermediate data file from input data file and - information read from functions file. - - :param data_file: Data file name - :param out_data_file: Output/Intermediate data file - :param suite_dependencies: List of suite dependencies. - :param func_info: Function info parsed from functions file. - :param snippets: Dictionary to contain code pieces to be - substituted in the template. - :return: - """ - with FileWrapper(data_file) as data_f, \ - open(out_data_file, 'w') as out_data_f: - dep_check_code, expression_code = gen_from_test_data( - data_f, out_data_f, func_info, suite_dependencies) - snippets['dep_check_code'] = dep_check_code - snippets['expression_code'] = expression_code - - -def generate_code(**input_info): - """ - Generates C source code from test suite file, data file, common - helpers file and platform file. - - input_info expands to following parameters: - funcs_file: Functions file object - data_file: Data file object - template_file: Template file object - platform_file: Platform file object - helpers_file: Helper functions file object - suites_dir: Test suites dir - c_file: Output C file object - out_data_file: Output intermediate data file object - :return: - """ - funcs_file = input_info['funcs_file'] - data_file = input_info['data_file'] - template_file = input_info['template_file'] - platform_file = input_info['platform_file'] - helpers_file = input_info['helpers_file'] - suites_dir = input_info['suites_dir'] - c_file = input_info['c_file'] - out_data_file = input_info['out_data_file'] - for name, path in [('Functions file', funcs_file), - ('Data file', data_file), - ('Template file', template_file), - ('Platform file', platform_file), - ('Helpers code file', helpers_file), - ('Suites dir', suites_dir)]: - if not os.path.exists(path): - raise IOError("ERROR: %s [%s] not found!" % (name, path)) - - snippets = {'generator_script': os.path.basename(__file__)} - read_code_from_input_files(platform_file, helpers_file, - out_data_file, snippets) - add_input_info(funcs_file, data_file, template_file, - c_file, snippets) - suite_dependencies, func_info = parse_function_file(funcs_file, snippets) - generate_intermediate_data_file(data_file, out_data_file, - suite_dependencies, func_info, snippets) - write_test_source_file(template_file, c_file, snippets) - - -def main(): - """ - Command line parser. - - :return: - """ - parser = argparse.ArgumentParser( - description='Dynamically generate test suite code.') - - parser.add_argument("-f", "--functions-file", - dest="funcs_file", - help="Functions file", - metavar="FUNCTIONS_FILE", - required=True) - - parser.add_argument("-d", "--data-file", - dest="data_file", - help="Data file", - metavar="DATA_FILE", - required=True) - - parser.add_argument("-t", "--template-file", - dest="template_file", - help="Template file", - metavar="TEMPLATE_FILE", - required=True) - - parser.add_argument("-s", "--suites-dir", - dest="suites_dir", - help="Suites dir", - metavar="SUITES_DIR", - required=True) - - parser.add_argument("--helpers-file", - dest="helpers_file", - help="Helpers file", - metavar="HELPERS_FILE", - required=True) - - parser.add_argument("-p", "--platform-file", - dest="platform_file", - help="Platform code file", - metavar="PLATFORM_FILE", - required=True) - - parser.add_argument("-o", "--out-dir", - dest="out_dir", - help="Dir where generated code and scripts are copied", - metavar="OUT_DIR", - required=True) - - args = parser.parse_args() - - data_file_name = os.path.basename(args.data_file) - data_name = os.path.splitext(data_file_name)[0] - - out_c_file = os.path.join(args.out_dir, data_name + '.c') - out_data_file = os.path.join(args.out_dir, data_name + '.datax') - - out_c_file_dir = os.path.dirname(out_c_file) - out_data_file_dir = os.path.dirname(out_data_file) - for directory in [out_c_file_dir, out_data_file_dir]: - if not os.path.exists(directory): - os.makedirs(directory) - - generate_code(funcs_file=args.funcs_file, data_file=args.data_file, - template_file=args.template_file, - platform_file=args.platform_file, - helpers_file=args.helpers_file, suites_dir=args.suites_dir, - c_file=out_c_file, out_data_file=out_data_file) - - -if __name__ == "__main__": - try: - main() - except GeneratorInputError as err: - sys.exit("%s: input error: %s" % - (os.path.basename(sys.argv[0]), str(err))) diff --git a/tests/scripts/generate_test_keys.py b/tests/scripts/generate_test_keys.py deleted file mode 100755 index 9946c24976..0000000000 --- a/tests/scripts/generate_test_keys.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -"""Module generating EC and RSA keys to be used in test_suite_pk instead of -generating the required key at run time. This helps speeding up testing.""" - -from typing import Iterator, List, Tuple -import re -import argparse -import scripts_path # pylint: disable=unused-import -from mbedtls_framework.asymmetric_key_data import ASYMMETRIC_KEY_DATA -from mbedtls_framework.build_tree import guess_project_root - -BYTES_PER_LINE = 16 - -def c_byte_array_literal_content(array_name: str, key_data: bytes) -> Iterator[str]: - yield 'const unsigned char ' - yield array_name - yield '[] = {' - for index in range(0, len(key_data), BYTES_PER_LINE): - yield '\n ' - for b in key_data[index:index + BYTES_PER_LINE]: - yield ' {:#04x},'.format(b) - yield '\n};' - -def convert_der_to_c(array_name: str, key_data: bytes) -> str: - return ''.join(c_byte_array_literal_content(array_name, key_data)) - -def get_key_type(key: str) -> str: - if re.match('PSA_KEY_TYPE_RSA_.*', key): - return "rsa" - elif re.match('PSA_KEY_TYPE_ECC_.*', key): - return "ec" - else: - print("Unhandled key type {}".format(key)) - return "unknown" - -def get_ec_key_family(key: str) -> str: - match = re.search(r'.*\((.*)\)', key) - if match is None: - raise Exception("Unable to get EC family from {}".format(key)) - return match.group(1) - -# Legacy EC group ID do not support all the key types that PSA does, so the -# following dictionaries are used for: -# - getting prefix/suffix for legacy curve names -# - understand if the curve is supported in legacy symbols (MBEDTLS_ECP_DP_...) -EC_NAME_CONVERSION = { - 'PSA_ECC_FAMILY_SECP_K1': { - 192: ('secp', 'k1'), - 224: ('secp', 'k1'), - 256: ('secp', 'k1') - }, - 'PSA_ECC_FAMILY_SECP_R1': { - 192: ('secp', 'r1'), - 224: ('secp', 'r1'), - 256: ('secp', 'r1'), - 384: ('secp', 'r1'), - 521: ('secp', 'r1') - }, - 'PSA_ECC_FAMILY_BRAINPOOL_P_R1': { - 256: ('bp', 'r1'), - 384: ('bp', 'r1'), - 512: ('bp', 'r1') - }, - 'PSA_ECC_FAMILY_MONTGOMERY': { - 255: ('curve', '19'), - 448: ('curve', '') - } -} - -def get_ec_curve_name(priv_key: str, bits: int) -> str: - ec_family = get_ec_key_family(priv_key) - try: - prefix = EC_NAME_CONVERSION[ec_family][bits][0] - suffix = EC_NAME_CONVERSION[ec_family][bits][1] - except KeyError: - return "" - return prefix + str(bits) + suffix - -def get_look_up_table_entry(key_type: str, group_id_or_keybits: str, - priv_array_name: str, pub_array_name: str) -> Iterator[str]: - if key_type == "ec": - yield " {{ {}, 0,\n".format(group_id_or_keybits) - else: - yield " {{ 0, {},\n".format(group_id_or_keybits) - yield " {0}, sizeof({0}),\n".format(priv_array_name) - yield " {0}, sizeof({0}) }},".format(pub_array_name) - - -def write_output_file(output_file_name: str, arrays: str, look_up_table: str): - with open(output_file_name, 'wt') as output: - output.write("""\ -/********************************************************************************* - * This file was automatically generated from tests/scripts/generate_test_keys.py. - * Please do not edit it manually. - *********************************************************************************/ -""") - output.write(arrays) - output.write(""" -struct predefined_key_element {{ - int group_id; // EC group ID; 0 for RSA keys - int keybits; // bits size of RSA key; 0 for EC keys - const unsigned char *priv_key; - size_t priv_key_len; - const unsigned char *pub_key; - size_t pub_key_len; -}}; - -struct predefined_key_element predefined_keys[] = {{ -{} -}}; - -/* End of generated file */ -""".format(look_up_table)) - -def collect_keys() -> Tuple[str, str]: - """" - This function reads key data from ASYMMETRIC_KEY_DATA and, only for the - keys supported in legacy ECP/RSA modules, it returns 2 strings: - - the 1st contains C arrays declaration of these keys and - - the 2nd contains the final look-up table for all these arrays. - """ - arrays = [] - look_up_table = [] - - # Get a list of private keys only in order to get a single item for every - # (key type, key bits) pair. We know that ASYMMETRIC_KEY_DATA - # contains also the public counterpart. - priv_keys = [key for key in ASYMMETRIC_KEY_DATA if '_KEY_PAIR' in key] - priv_keys = sorted(priv_keys) - - for priv_key in priv_keys: - key_type = get_key_type(priv_key) - # Ignore keys which are not EC or RSA - if key_type == "unknown": - continue - - pub_key = re.sub('_KEY_PAIR', '_PUBLIC_KEY', priv_key) - - for bits in ASYMMETRIC_KEY_DATA[priv_key]: - if key_type == "ec": - curve = get_ec_curve_name(priv_key, bits) - # Ignore EC curves unsupported in legacy symbols - if curve == "": - continue - # Create output array name - if key_type == "rsa": - array_name_base = "_".join(["test", key_type, str(bits)]) - else: - array_name_base = "_".join(["test", key_type, curve]) - array_name_priv = array_name_base + "_priv" - array_name_pub = array_name_base + "_pub" - # Convert bytearray to C array - c_array_priv = convert_der_to_c(array_name_priv, ASYMMETRIC_KEY_DATA[priv_key][bits]) - c_array_pub = convert_der_to_c(array_name_pub, ASYMMETRIC_KEY_DATA[pub_key][bits]) - # Write the C array to the output file - arrays.append(''.join(["\n", c_array_priv, "\n", c_array_pub, "\n"])) - # Update the lookup table - if key_type == "ec": - group_id_or_keybits = "MBEDTLS_ECP_DP_" + curve.upper() - else: - group_id_or_keybits = str(bits) - look_up_table.append(''.join(get_look_up_table_entry(key_type, group_id_or_keybits, - array_name_priv, array_name_pub))) - - return ''.join(arrays), '\n'.join(look_up_table) - -def main() -> None: - default_output_path = guess_project_root() + "/tests/src/test_keys.h" - - argparser = argparse.ArgumentParser() - argparser.add_argument("--output", help="Output file", default=default_output_path) - args = argparser.parse_args() - - output_file = args.output - - arrays, look_up_table = collect_keys() - - write_output_file(output_file, arrays, look_up_table) - -if __name__ == '__main__': - main() diff --git a/tests/scripts/test_generate_test_code.py b/tests/scripts/test_generate_test_code.py deleted file mode 100755 index abc46a7291..0000000000 --- a/tests/scripts/test_generate_test_code.py +++ /dev/null @@ -1,1915 +0,0 @@ -#!/usr/bin/env python3 -# Unit test for generate_test_code.py -# -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -""" -Unit tests for generate_test_code.py -""" - -from io import StringIO -from unittest import TestCase, main as unittest_main -from unittest.mock import patch - -from generate_test_code import gen_dependencies, gen_dependencies_one_line -from generate_test_code import gen_function_wrapper, gen_dispatch -from generate_test_code import parse_until_pattern, GeneratorInputError -from generate_test_code import parse_suite_dependencies -from generate_test_code import parse_function_dependencies -from generate_test_code import parse_function_arguments, parse_function_code -from generate_test_code import parse_functions, END_HEADER_REGEX -from generate_test_code import END_SUITE_HELPERS_REGEX, escaped_split -from generate_test_code import parse_test_data, gen_dep_check -from generate_test_code import gen_expression_check, write_dependencies -from generate_test_code import write_parameters, gen_suite_dep_checks -from generate_test_code import gen_from_test_data - - -class GenDep(TestCase): - """ - Test suite for function gen_dep() - """ - - def test_dependencies_list(self): - """ - Test that gen_dep() correctly creates dependencies for given - dependency list. - :return: - """ - dependencies = ['DEP1', 'DEP2'] - dep_start, dep_end = gen_dependencies(dependencies) - preprocessor1, preprocessor2 = dep_start.splitlines() - endif1, endif2 = dep_end.splitlines() - self.assertEqual(preprocessor1, '#if defined(DEP1)', - 'Preprocessor generated incorrectly') - self.assertEqual(preprocessor2, '#if defined(DEP2)', - 'Preprocessor generated incorrectly') - self.assertEqual(endif1, '#endif /* DEP2 */', - 'Preprocessor generated incorrectly') - self.assertEqual(endif2, '#endif /* DEP1 */', - 'Preprocessor generated incorrectly') - - def test_disabled_dependencies_list(self): - """ - Test that gen_dep() correctly creates dependencies for given - dependency list. - :return: - """ - dependencies = ['!DEP1', '!DEP2'] - dep_start, dep_end = gen_dependencies(dependencies) - preprocessor1, preprocessor2 = dep_start.splitlines() - endif1, endif2 = dep_end.splitlines() - self.assertEqual(preprocessor1, '#if !defined(DEP1)', - 'Preprocessor generated incorrectly') - self.assertEqual(preprocessor2, '#if !defined(DEP2)', - 'Preprocessor generated incorrectly') - self.assertEqual(endif1, '#endif /* !DEP2 */', - 'Preprocessor generated incorrectly') - self.assertEqual(endif2, '#endif /* !DEP1 */', - 'Preprocessor generated incorrectly') - - def test_mixed_dependencies_list(self): - """ - Test that gen_dep() correctly creates dependencies for given - dependency list. - :return: - """ - dependencies = ['!DEP1', 'DEP2'] - dep_start, dep_end = gen_dependencies(dependencies) - preprocessor1, preprocessor2 = dep_start.splitlines() - endif1, endif2 = dep_end.splitlines() - self.assertEqual(preprocessor1, '#if !defined(DEP1)', - 'Preprocessor generated incorrectly') - self.assertEqual(preprocessor2, '#if defined(DEP2)', - 'Preprocessor generated incorrectly') - self.assertEqual(endif1, '#endif /* DEP2 */', - 'Preprocessor generated incorrectly') - self.assertEqual(endif2, '#endif /* !DEP1 */', - 'Preprocessor generated incorrectly') - - def test_empty_dependencies_list(self): - """ - Test that gen_dep() correctly creates dependencies for given - dependency list. - :return: - """ - dependencies = [] - dep_start, dep_end = gen_dependencies(dependencies) - self.assertEqual(dep_start, '', 'Preprocessor generated incorrectly') - self.assertEqual(dep_end, '', 'Preprocessor generated incorrectly') - - def test_large_dependencies_list(self): - """ - Test that gen_dep() correctly creates dependencies for given - dependency list. - :return: - """ - dependencies = [] - count = 10 - for i in range(count): - dependencies.append('DEP%d' % i) - dep_start, dep_end = gen_dependencies(dependencies) - self.assertEqual(len(dep_start.splitlines()), count, - 'Preprocessor generated incorrectly') - self.assertEqual(len(dep_end.splitlines()), count, - 'Preprocessor generated incorrectly') - - -class GenDepOneLine(TestCase): - """ - Test Suite for testing gen_dependencies_one_line() - """ - - def test_dependencies_list(self): - """ - Test that gen_dep() correctly creates dependencies for given - dependency list. - :return: - """ - dependencies = ['DEP1', 'DEP2'] - dep_str = gen_dependencies_one_line(dependencies) - self.assertEqual(dep_str, '#if defined(DEP1) && defined(DEP2)', - 'Preprocessor generated incorrectly') - - def test_disabled_dependencies_list(self): - """ - Test that gen_dep() correctly creates dependencies for given - dependency list. - :return: - """ - dependencies = ['!DEP1', '!DEP2'] - dep_str = gen_dependencies_one_line(dependencies) - self.assertEqual(dep_str, '#if !defined(DEP1) && !defined(DEP2)', - 'Preprocessor generated incorrectly') - - def test_mixed_dependencies_list(self): - """ - Test that gen_dep() correctly creates dependencies for given - dependency list. - :return: - """ - dependencies = ['!DEP1', 'DEP2'] - dep_str = gen_dependencies_one_line(dependencies) - self.assertEqual(dep_str, '#if !defined(DEP1) && defined(DEP2)', - 'Preprocessor generated incorrectly') - - def test_empty_dependencies_list(self): - """ - Test that gen_dep() correctly creates dependencies for given - dependency list. - :return: - """ - dependencies = [] - dep_str = gen_dependencies_one_line(dependencies) - self.assertEqual(dep_str, '', 'Preprocessor generated incorrectly') - - def test_large_dependencies_list(self): - """ - Test that gen_dep() correctly creates dependencies for given - dependency list. - :return: - """ - dependencies = [] - count = 10 - for i in range(count): - dependencies.append('DEP%d' % i) - dep_str = gen_dependencies_one_line(dependencies) - expected = '#if ' + ' && '.join(['defined(%s)' % - x for x in dependencies]) - self.assertEqual(dep_str, expected, - 'Preprocessor generated incorrectly') - - -class GenFunctionWrapper(TestCase): - """ - Test Suite for testing gen_function_wrapper() - """ - - def test_params_unpack(self): - """ - Test that params are properly unpacked in the function call. - - :return: - """ - code = gen_function_wrapper('test_a', '', ('a', 'b', 'c', 'd')) - expected = ''' -void test_a_wrapper( void ** params ) -{ - - test_a( a, b, c, d ); -} -''' - self.assertEqual(code, expected) - - def test_local(self): - """ - Test that params are properly unpacked in the function call. - - :return: - """ - code = gen_function_wrapper('test_a', - 'int x = 1;', ('x', 'b', 'c', 'd')) - expected = ''' -void test_a_wrapper( void ** params ) -{ -int x = 1; - test_a( x, b, c, d ); -} -''' - self.assertEqual(code, expected) - - def test_empty_params(self): - """ - Test that params are properly unpacked in the function call. - - :return: - """ - code = gen_function_wrapper('test_a', '', ()) - expected = ''' -void test_a_wrapper( void ** params ) -{ - (void)params; - - test_a( ); -} -''' - self.assertEqual(code, expected) - - -class GenDispatch(TestCase): - """ - Test suite for testing gen_dispatch() - """ - - def test_dispatch(self): - """ - Test that dispatch table entry is generated correctly. - :return: - """ - code = gen_dispatch('test_a', ['DEP1', 'DEP2']) - expected = ''' -#if defined(DEP1) && defined(DEP2) - test_a_wrapper, -#else - NULL, -#endif -''' - self.assertEqual(code, expected) - - def test_empty_dependencies(self): - """ - Test empty dependency list. - :return: - """ - code = gen_dispatch('test_a', []) - expected = ''' - test_a_wrapper, -''' - self.assertEqual(code, expected) - - -class StringIOWrapper(StringIO): - """ - file like class to mock file object in tests. - """ - def __init__(self, file_name, data, line_no=0): - """ - Init file handle. - - :param file_name: - :param data: - :param line_no: - """ - super(StringIOWrapper, self).__init__(data) - self.line_no = line_no - self.name = file_name - - def next(self): - """ - Iterator method. This method overrides base class's - next method and extends the next method to count the line - numbers as each line is read. - - :return: Line read from file. - """ - parent = super(StringIOWrapper, self) - line = parent.__next__() - return line - - def readline(self, _length=0): - """ - Wrap the base class readline. - - :param length: - :return: - """ - line = super(StringIOWrapper, self).readline() - if line is not None: - self.line_no += 1 - return line - - -class ParseUntilPattern(TestCase): - """ - Test Suite for testing parse_until_pattern(). - """ - - def test_suite_headers(self): - """ - Test that suite headers are parsed correctly. - - :return: - """ - data = '''#include "mbedtls/ecp.h" - -#define ECP_PF_UNKNOWN -1 -/* END_HEADER */ -''' - expected = '''#line 1 "test_suite_ut.function" -#include "mbedtls/ecp.h" - -#define ECP_PF_UNKNOWN -1 -''' - stream = StringIOWrapper('test_suite_ut.function', data, line_no=0) - headers = parse_until_pattern(stream, END_HEADER_REGEX) - self.assertEqual(headers, expected) - - def test_line_no(self): - """ - Test that #line is set to correct line no. in source .function file. - - :return: - """ - data = '''#include "mbedtls/ecp.h" - -#define ECP_PF_UNKNOWN -1 -/* END_HEADER */ -''' - offset_line_no = 5 - expected = '''#line %d "test_suite_ut.function" -#include "mbedtls/ecp.h" - -#define ECP_PF_UNKNOWN -1 -''' % (offset_line_no + 1) - stream = StringIOWrapper('test_suite_ut.function', data, - offset_line_no) - headers = parse_until_pattern(stream, END_HEADER_REGEX) - self.assertEqual(headers, expected) - - def test_no_end_header_comment(self): - """ - Test that InvalidFileFormat is raised when end header comment is - missing. - :return: - """ - data = '''#include "mbedtls/ecp.h" - -#define ECP_PF_UNKNOWN -1 - -''' - stream = StringIOWrapper('test_suite_ut.function', data) - self.assertRaises(GeneratorInputError, parse_until_pattern, stream, - END_HEADER_REGEX) - - -class ParseSuiteDependencies(TestCase): - """ - Test Suite for testing parse_suite_dependencies(). - """ - - def test_suite_dependencies(self): - """ - - :return: - """ - data = ''' - * depends_on:MBEDTLS_ECP_C - * END_DEPENDENCIES - */ -''' - expected = ['MBEDTLS_ECP_C'] - stream = StringIOWrapper('test_suite_ut.function', data) - dependencies = parse_suite_dependencies(stream) - self.assertEqual(dependencies, expected) - - def test_no_end_dep_comment(self): - """ - Test that InvalidFileFormat is raised when end dep comment is missing. - :return: - """ - data = ''' -* depends_on:MBEDTLS_ECP_C -''' - stream = StringIOWrapper('test_suite_ut.function', data) - self.assertRaises(GeneratorInputError, parse_suite_dependencies, - stream) - - def test_dependencies_split(self): - """ - Test that InvalidFileFormat is raised when end dep comment is missing. - :return: - """ - data = ''' - * depends_on:MBEDTLS_ECP_C:A:B: C : D :F : G: !H - * END_DEPENDENCIES - */ -''' - expected = ['MBEDTLS_ECP_C', 'A', 'B', 'C', 'D', 'F', 'G', '!H'] - stream = StringIOWrapper('test_suite_ut.function', data) - dependencies = parse_suite_dependencies(stream) - self.assertEqual(dependencies, expected) - - -class ParseFuncDependencies(TestCase): - """ - Test Suite for testing parse_function_dependencies() - """ - - def test_function_dependencies(self): - """ - Test that parse_function_dependencies() correctly parses function - dependencies. - :return: - """ - line = '/* BEGIN_CASE ' \ - 'depends_on:MBEDTLS_ENTROPY_NV_SEED:MBEDTLS_FS_IO */' - expected = ['MBEDTLS_ENTROPY_NV_SEED', 'MBEDTLS_FS_IO'] - dependencies = parse_function_dependencies(line) - self.assertEqual(dependencies, expected) - - def test_no_dependencies(self): - """ - Test that parse_function_dependencies() correctly parses function - dependencies. - :return: - """ - line = '/* BEGIN_CASE */' - dependencies = parse_function_dependencies(line) - self.assertEqual(dependencies, []) - - def test_tolerance(self): - """ - Test that parse_function_dependencies() correctly parses function - dependencies. - :return: - """ - line = '/* BEGIN_CASE depends_on:MBEDTLS_FS_IO: A : !B:C : F*/' - dependencies = parse_function_dependencies(line) - self.assertEqual(dependencies, ['MBEDTLS_FS_IO', 'A', '!B', 'C', 'F']) - - -class ParseFuncSignature(TestCase): - """ - Test Suite for parse_function_arguments(). - """ - - def test_int_and_char_params(self): - """ - Test int and char parameters parsing - :return: - """ - line = 'void entropy_threshold( char * a, int b, int result )' - args, local, arg_dispatch = parse_function_arguments(line) - self.assertEqual(args, ['char*', 'int', 'int']) - self.assertEqual(local, '') - self.assertEqual(arg_dispatch, - ['(char *) params[0]', - '((mbedtls_test_argument_t *) params[1])->sint', - '((mbedtls_test_argument_t *) params[2])->sint']) - - def test_hex_params(self): - """ - Test hex parameters parsing - :return: - """ - line = 'void entropy_threshold( char * a, data_t * h, int result )' - args, local, arg_dispatch = parse_function_arguments(line) - self.assertEqual(args, ['char*', 'hex', 'int']) - self.assertEqual(local, - ' data_t data1 = {(uint8_t *) params[1], ' - '((mbedtls_test_argument_t *) params[2])->len};\n') - self.assertEqual(arg_dispatch, ['(char *) params[0]', - '&data1', - '((mbedtls_test_argument_t *) params[3])->sint']) - - def test_unsupported_arg(self): - """ - Test unsupported argument type - :return: - """ - line = 'void entropy_threshold( char * a, data_t * h, unknown_t result )' - self.assertRaises(ValueError, parse_function_arguments, line) - - def test_empty_params(self): - """ - Test no parameters (nothing between parentheses). - :return: - """ - line = 'void entropy_threshold()' - args, local, arg_dispatch = parse_function_arguments(line) - self.assertEqual(args, []) - self.assertEqual(local, '') - self.assertEqual(arg_dispatch, []) - - def test_blank_params(self): - """ - Test no parameters (space between parentheses). - :return: - """ - line = 'void entropy_threshold( )' - args, local, arg_dispatch = parse_function_arguments(line) - self.assertEqual(args, []) - self.assertEqual(local, '') - self.assertEqual(arg_dispatch, []) - - def test_void_params(self): - """ - Test no parameters (void keyword). - :return: - """ - line = 'void entropy_threshold(void)' - args, local, arg_dispatch = parse_function_arguments(line) - self.assertEqual(args, []) - self.assertEqual(local, '') - self.assertEqual(arg_dispatch, []) - - def test_void_space_params(self): - """ - Test no parameters (void with spaces). - :return: - """ - line = 'void entropy_threshold( void )' - args, local, arg_dispatch = parse_function_arguments(line) - self.assertEqual(args, []) - self.assertEqual(local, '') - self.assertEqual(arg_dispatch, []) - - -class ParseFunctionCode(TestCase): - """ - Test suite for testing parse_function_code() - """ - - def test_no_function(self): - """ - Test no test function found. - :return: - """ - data = ''' -No -test -function -''' - stream = StringIOWrapper('test_suite_ut.function', data) - err_msg = 'file: test_suite_ut.function - Test functions not found!' - self.assertRaisesRegex(GeneratorInputError, err_msg, - parse_function_code, stream, [], []) - - def test_no_end_case_comment(self): - """ - Test missing end case. - :return: - """ - data = ''' -void test_func() -{ -} -''' - stream = StringIOWrapper('test_suite_ut.function', data) - err_msg = r'file: test_suite_ut.function - '\ - 'end case pattern .*? not found!' - self.assertRaisesRegex(GeneratorInputError, err_msg, - parse_function_code, stream, [], []) - - @patch("generate_test_code.parse_function_arguments") - def test_function_called(self, - parse_function_arguments_mock): - """ - Test parse_function_code() - :return: - """ - parse_function_arguments_mock.return_value = ([], '', []) - data = ''' -void test_func() -{ -} -''' - stream = StringIOWrapper('test_suite_ut.function', data) - self.assertRaises(GeneratorInputError, parse_function_code, - stream, [], []) - self.assertTrue(parse_function_arguments_mock.called) - parse_function_arguments_mock.assert_called_with('void test_func()\n') - - @patch("generate_test_code.gen_dispatch") - @patch("generate_test_code.gen_dependencies") - @patch("generate_test_code.gen_function_wrapper") - @patch("generate_test_code.parse_function_arguments") - def test_return(self, parse_function_arguments_mock, - gen_function_wrapper_mock, - gen_dependencies_mock, - gen_dispatch_mock): - """ - Test generated code. - :return: - """ - parse_function_arguments_mock.return_value = ([], '', []) - gen_function_wrapper_mock.return_value = '' - gen_dependencies_mock.side_effect = gen_dependencies - gen_dispatch_mock.side_effect = gen_dispatch - data = ''' -void func() -{ - ba ba black sheep - have you any wool -} -/* END_CASE */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - name, arg, code, dispatch_code = parse_function_code(stream, [], []) - - self.assertTrue(parse_function_arguments_mock.called) - parse_function_arguments_mock.assert_called_with('void func()\n') - gen_function_wrapper_mock.assert_called_with('test_func', '', []) - self.assertEqual(name, 'test_func') - self.assertEqual(arg, []) - expected = '''#line 1 "test_suite_ut.function" - -void test_func(void) -{ - ba ba black sheep - have you any wool -exit: - ; -} -''' - self.assertEqual(code, expected) - self.assertEqual(dispatch_code, "\n test_func_wrapper,\n") - - @patch("generate_test_code.gen_dispatch") - @patch("generate_test_code.gen_dependencies") - @patch("generate_test_code.gen_function_wrapper") - @patch("generate_test_code.parse_function_arguments") - def test_with_exit_label(self, parse_function_arguments_mock, - gen_function_wrapper_mock, - gen_dependencies_mock, - gen_dispatch_mock): - """ - Test when exit label is present. - :return: - """ - parse_function_arguments_mock.return_value = ([], '', []) - gen_function_wrapper_mock.return_value = '' - gen_dependencies_mock.side_effect = gen_dependencies - gen_dispatch_mock.side_effect = gen_dispatch - data = ''' -void func() -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -/* END_CASE */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - _, _, code, _ = parse_function_code(stream, [], []) - - expected = '''#line 1 "test_suite_ut.function" - -void test_func(void) -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -''' - self.assertEqual(code, expected) - - def test_non_void_function(self): - """ - Test invalid signature (non void). - :return: - """ - data = 'int entropy_threshold( char * a, data_t * h, int result )' - err_msg = 'file: test_suite_ut.function - Test functions not found!' - stream = StringIOWrapper('test_suite_ut.function', data) - self.assertRaisesRegex(GeneratorInputError, err_msg, - parse_function_code, stream, [], []) - - @patch("generate_test_code.gen_dispatch") - @patch("generate_test_code.gen_dependencies") - @patch("generate_test_code.gen_function_wrapper") - @patch("generate_test_code.parse_function_arguments") - def test_function_name_on_newline(self, parse_function_arguments_mock, - gen_function_wrapper_mock, - gen_dependencies_mock, - gen_dispatch_mock): - """ - Test with line break before the function name. - :return: - """ - parse_function_arguments_mock.return_value = ([], '', []) - gen_function_wrapper_mock.return_value = '' - gen_dependencies_mock.side_effect = gen_dependencies - gen_dispatch_mock.side_effect = gen_dispatch - data = ''' -void - - -func() -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -/* END_CASE */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - _, _, code, _ = parse_function_code(stream, [], []) - - expected = '''#line 1 "test_suite_ut.function" - -void - - -test_func(void) -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -''' - self.assertEqual(code, expected) - - @patch("generate_test_code.gen_dispatch") - @patch("generate_test_code.gen_dependencies") - @patch("generate_test_code.gen_function_wrapper") - @patch("generate_test_code.parse_function_arguments") - def test_case_starting_with_comment(self, parse_function_arguments_mock, - gen_function_wrapper_mock, - gen_dependencies_mock, - gen_dispatch_mock): - """ - Test with comments before the function signature - :return: - """ - parse_function_arguments_mock.return_value = ([], '', []) - gen_function_wrapper_mock.return_value = '' - gen_dependencies_mock.side_effect = gen_dependencies - gen_dispatch_mock.side_effect = gen_dispatch - data = '''/* comment */ -/* more - * comment */ -// this is\\ -still \\ -a comment -void func() -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -/* END_CASE */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - _, _, code, _ = parse_function_code(stream, [], []) - - expected = '''#line 1 "test_suite_ut.function" - - - - - - -void test_func(void) -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -''' - self.assertEqual(code, expected) - - @patch("generate_test_code.gen_dispatch") - @patch("generate_test_code.gen_dependencies") - @patch("generate_test_code.gen_function_wrapper") - @patch("generate_test_code.parse_function_arguments") - def test_comment_in_prototype(self, parse_function_arguments_mock, - gen_function_wrapper_mock, - gen_dependencies_mock, - gen_dispatch_mock): - """ - Test with comments in the function prototype - :return: - """ - parse_function_arguments_mock.return_value = ([], '', []) - gen_function_wrapper_mock.return_value = '' - gen_dependencies_mock.side_effect = gen_dependencies - gen_dispatch_mock.side_effect = gen_dispatch - data = ''' -void func( int x, // (line \\ - comment) - int y /* lone closing parenthesis) */ ) -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -/* END_CASE */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - _, _, code, _ = parse_function_code(stream, [], []) - - expected = '''#line 1 "test_suite_ut.function" - -void test_func( int x, - - int y ) -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -''' - self.assertEqual(code, expected) - - @patch("generate_test_code.gen_dispatch") - @patch("generate_test_code.gen_dependencies") - @patch("generate_test_code.gen_function_wrapper") - @patch("generate_test_code.parse_function_arguments") - def test_line_comment_in_block_comment(self, parse_function_arguments_mock, - gen_function_wrapper_mock, - gen_dependencies_mock, - gen_dispatch_mock): - """ - Test with line comment in block comment. - :return: - """ - parse_function_arguments_mock.return_value = ([], '', []) - gen_function_wrapper_mock.return_value = '' - gen_dependencies_mock.side_effect = gen_dependencies - gen_dispatch_mock.side_effect = gen_dispatch - data = ''' -void func( int x /* // */ ) -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -/* END_CASE */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - _, _, code, _ = parse_function_code(stream, [], []) - - expected = '''#line 1 "test_suite_ut.function" - -void test_func( int x ) -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -''' - self.assertEqual(code, expected) - - @patch("generate_test_code.gen_dispatch") - @patch("generate_test_code.gen_dependencies") - @patch("generate_test_code.gen_function_wrapper") - @patch("generate_test_code.parse_function_arguments") - def test_block_comment_in_line_comment(self, parse_function_arguments_mock, - gen_function_wrapper_mock, - gen_dependencies_mock, - gen_dispatch_mock): - """ - Test with block comment in line comment. - :return: - """ - parse_function_arguments_mock.return_value = ([], '', []) - gen_function_wrapper_mock.return_value = '' - gen_dependencies_mock.side_effect = gen_dependencies - gen_dispatch_mock.side_effect = gen_dispatch - data = ''' -// /* -void func( int x ) -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -/* END_CASE */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - _, _, code, _ = parse_function_code(stream, [], []) - - expected = '''#line 1 "test_suite_ut.function" - - -void test_func( int x ) -{ - ba ba black sheep - have you any wool -exit: - yes sir yes sir - 3 bags full -} -''' - self.assertEqual(code, expected) - - -class ParseFunction(TestCase): - """ - Test Suite for testing parse_functions() - """ - - @patch("generate_test_code.parse_until_pattern") - def test_begin_header(self, parse_until_pattern_mock): - """ - Test that begin header is checked and parse_until_pattern() is called. - :return: - """ - def stop(*_unused): - """Stop when parse_until_pattern is called.""" - raise Exception - parse_until_pattern_mock.side_effect = stop - data = '''/* BEGIN_HEADER */ -#include "mbedtls/ecp.h" - -#define ECP_PF_UNKNOWN -1 -/* END_HEADER */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - self.assertRaises(Exception, parse_functions, stream) - parse_until_pattern_mock.assert_called_with(stream, END_HEADER_REGEX) - self.assertEqual(stream.line_no, 1) - - @patch("generate_test_code.parse_until_pattern") - def test_begin_helper(self, parse_until_pattern_mock): - """ - Test that begin helper is checked and parse_until_pattern() is called. - :return: - """ - def stop(*_unused): - """Stop when parse_until_pattern is called.""" - raise Exception - parse_until_pattern_mock.side_effect = stop - data = '''/* BEGIN_SUITE_HELPERS */ -void print_hello_world() -{ - printf("Hello World!\n"); -} -/* END_SUITE_HELPERS */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - self.assertRaises(Exception, parse_functions, stream) - parse_until_pattern_mock.assert_called_with(stream, - END_SUITE_HELPERS_REGEX) - self.assertEqual(stream.line_no, 1) - - @patch("generate_test_code.parse_suite_dependencies") - def test_begin_dep(self, parse_suite_dependencies_mock): - """ - Test that begin dep is checked and parse_suite_dependencies() is - called. - :return: - """ - def stop(*_unused): - """Stop when parse_until_pattern is called.""" - raise Exception - parse_suite_dependencies_mock.side_effect = stop - data = '''/* BEGIN_DEPENDENCIES - * depends_on:MBEDTLS_ECP_C - * END_DEPENDENCIES - */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - self.assertRaises(Exception, parse_functions, stream) - parse_suite_dependencies_mock.assert_called_with(stream) - self.assertEqual(stream.line_no, 1) - - @patch("generate_test_code.parse_function_dependencies") - def test_begin_function_dep(self, func_mock): - """ - Test that begin dep is checked and parse_function_dependencies() is - called. - :return: - """ - def stop(*_unused): - """Stop when parse_until_pattern is called.""" - raise Exception - func_mock.side_effect = stop - - dependencies_str = '/* BEGIN_CASE ' \ - 'depends_on:MBEDTLS_ENTROPY_NV_SEED:MBEDTLS_FS_IO */\n' - data = '''%svoid test_func() -{ -} -''' % dependencies_str - stream = StringIOWrapper('test_suite_ut.function', data) - self.assertRaises(Exception, parse_functions, stream) - func_mock.assert_called_with(dependencies_str) - self.assertEqual(stream.line_no, 1) - - @patch("generate_test_code.parse_function_code") - @patch("generate_test_code.parse_function_dependencies") - def test_return(self, func_mock1, func_mock2): - """ - Test that begin case is checked and parse_function_code() is called. - :return: - """ - func_mock1.return_value = [] - in_func_code = '''void test_func() -{ -} -''' - func_dispatch = ''' - test_func_wrapper, -''' - func_mock2.return_value = 'test_func', [],\ - in_func_code, func_dispatch - dependencies_str = '/* BEGIN_CASE ' \ - 'depends_on:MBEDTLS_ENTROPY_NV_SEED:MBEDTLS_FS_IO */\n' - data = '''%svoid test_func() -{ -} -''' % dependencies_str - stream = StringIOWrapper('test_suite_ut.function', data) - suite_dependencies, dispatch_code, func_code, func_info = \ - parse_functions(stream) - func_mock1.assert_called_with(dependencies_str) - func_mock2.assert_called_with(stream, [], []) - self.assertEqual(stream.line_no, 5) - self.assertEqual(suite_dependencies, []) - expected_dispatch_code = '''/* Function Id: 0 */ - - test_func_wrapper, -''' - self.assertEqual(dispatch_code, expected_dispatch_code) - self.assertEqual(func_code, in_func_code) - self.assertEqual(func_info, {'test_func': (0, [])}) - - def test_parsing(self): - """ - Test case parsing. - :return: - """ - data = '''/* BEGIN_HEADER */ -#include "mbedtls/ecp.h" - -#define ECP_PF_UNKNOWN -1 -/* END_HEADER */ - -/* BEGIN_DEPENDENCIES - * depends_on:MBEDTLS_ECP_C - * END_DEPENDENCIES - */ - -/* BEGIN_CASE depends_on:MBEDTLS_ENTROPY_NV_SEED:MBEDTLS_FS_IO */ -void func1() -{ -} -/* END_CASE */ - -/* BEGIN_CASE depends_on:MBEDTLS_ENTROPY_NV_SEED:MBEDTLS_FS_IO */ -void func2() -{ -} -/* END_CASE */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - suite_dependencies, dispatch_code, func_code, func_info = \ - parse_functions(stream) - self.assertEqual(stream.line_no, 23) - self.assertEqual(suite_dependencies, ['MBEDTLS_ECP_C']) - - expected_dispatch_code = '''/* Function Id: 0 */ - -#if defined(MBEDTLS_ECP_C) && defined(MBEDTLS_ENTROPY_NV_SEED) && defined(MBEDTLS_FS_IO) - test_func1_wrapper, -#else - NULL, -#endif -/* Function Id: 1 */ - -#if defined(MBEDTLS_ECP_C) && defined(MBEDTLS_ENTROPY_NV_SEED) && defined(MBEDTLS_FS_IO) - test_func2_wrapper, -#else - NULL, -#endif -''' - self.assertEqual(dispatch_code, expected_dispatch_code) - expected_func_code = '''#if defined(MBEDTLS_ECP_C) -#line 2 "test_suite_ut.function" -#include "mbedtls/ecp.h" - -#define ECP_PF_UNKNOWN -1 -#if defined(MBEDTLS_ENTROPY_NV_SEED) -#if defined(MBEDTLS_FS_IO) -#line 13 "test_suite_ut.function" -void test_func1(void) -{ -exit: - ; -} - -void test_func1_wrapper( void ** params ) -{ - (void)params; - - test_func1( ); -} -#endif /* MBEDTLS_FS_IO */ -#endif /* MBEDTLS_ENTROPY_NV_SEED */ -#if defined(MBEDTLS_ENTROPY_NV_SEED) -#if defined(MBEDTLS_FS_IO) -#line 19 "test_suite_ut.function" -void test_func2(void) -{ -exit: - ; -} - -void test_func2_wrapper( void ** params ) -{ - (void)params; - - test_func2( ); -} -#endif /* MBEDTLS_FS_IO */ -#endif /* MBEDTLS_ENTROPY_NV_SEED */ -#endif /* MBEDTLS_ECP_C */ -''' - self.assertEqual(func_code, expected_func_code) - self.assertEqual(func_info, {'test_func1': (0, []), - 'test_func2': (1, [])}) - - def test_same_function_name(self): - """ - Test name conflict. - :return: - """ - data = '''/* BEGIN_HEADER */ -#include "mbedtls/ecp.h" - -#define ECP_PF_UNKNOWN -1 -/* END_HEADER */ - -/* BEGIN_DEPENDENCIES - * depends_on:MBEDTLS_ECP_C - * END_DEPENDENCIES - */ - -/* BEGIN_CASE depends_on:MBEDTLS_ENTROPY_NV_SEED:MBEDTLS_FS_IO */ -void func() -{ -} -/* END_CASE */ - -/* BEGIN_CASE depends_on:MBEDTLS_ENTROPY_NV_SEED:MBEDTLS_FS_IO */ -void func() -{ -} -/* END_CASE */ -''' - stream = StringIOWrapper('test_suite_ut.function', data) - self.assertRaises(GeneratorInputError, parse_functions, stream) - - -class EscapedSplit(TestCase): - """ - Test suite for testing escaped_split(). - Note: Since escaped_split() output is used to write back to the - intermediate data file. Any escape characters in the input are - retained in the output. - """ - - def test_invalid_input(self): - """ - Test when input split character is not a character. - :return: - """ - self.assertRaises(ValueError, escaped_split, '', 'string') - - def test_empty_string(self): - """ - Test empty string input. - :return: - """ - splits = escaped_split('', ':') - self.assertEqual(splits, []) - - def test_no_escape(self): - """ - Test with no escape character. The behaviour should be same as - str.split() - :return: - """ - test_str = 'yahoo:google' - splits = escaped_split(test_str, ':') - self.assertEqual(splits, test_str.split(':')) - - def test_escaped_input(self): - """ - Test input that has escaped delimiter. - :return: - """ - test_str = r'yahoo\:google:facebook' - splits = escaped_split(test_str, ':') - self.assertEqual(splits, [r'yahoo\:google', 'facebook']) - - def test_escaped_escape(self): - """ - Test input that has escaped delimiter. - :return: - """ - test_str = r'yahoo\\:google:facebook' - splits = escaped_split(test_str, ':') - self.assertEqual(splits, [r'yahoo\\', 'google', 'facebook']) - - def test_all_at_once(self): - """ - Test input that has escaped delimiter. - :return: - """ - test_str = r'yahoo\\:google:facebook\:instagram\\:bbc\\:wikipedia' - splits = escaped_split(test_str, ':') - self.assertEqual(splits, [r'yahoo\\', r'google', - r'facebook\:instagram\\', - r'bbc\\', r'wikipedia']) - - -class ParseTestData(TestCase): - """ - Test suite for parse test data. - """ - - def test_parser(self): - """ - Test that tests are parsed correctly from data file. - :return: - """ - data = """ -Diffie-Hellman full exchange #1 -dhm_do_dhm:10:"23":10:"5" - -Diffie-Hellman full exchange #2 -dhm_do_dhm:10:"93450983094850938450983409623":10:"9345098304850938450983409622" - -Diffie-Hellman full exchange #3 -dhm_do_dhm:10:"9345098382739712938719287391879381271":10:"9345098792137312973297123912791271" - -Diffie-Hellman selftest -dhm_selftest: -""" - stream = StringIOWrapper('test_suite_ut.function', data) - # List of (name, function_name, dependencies, args) - tests = list(parse_test_data(stream)) - test1, test2, test3, test4 = tests - self.assertEqual(test1[0], 3) - self.assertEqual(test1[1], 'Diffie-Hellman full exchange #1') - self.assertEqual(test1[2], 'dhm_do_dhm') - self.assertEqual(test1[3], []) - self.assertEqual(test1[4], ['10', '"23"', '10', '"5"']) - - self.assertEqual(test2[0], 6) - self.assertEqual(test2[1], 'Diffie-Hellman full exchange #2') - self.assertEqual(test2[2], 'dhm_do_dhm') - self.assertEqual(test2[3], []) - self.assertEqual(test2[4], ['10', '"93450983094850938450983409623"', - '10', '"9345098304850938450983409622"']) - - self.assertEqual(test3[0], 9) - self.assertEqual(test3[1], 'Diffie-Hellman full exchange #3') - self.assertEqual(test3[2], 'dhm_do_dhm') - self.assertEqual(test3[3], []) - self.assertEqual(test3[4], ['10', - '"9345098382739712938719287391879381271"', - '10', - '"9345098792137312973297123912791271"']) - - self.assertEqual(test4[0], 12) - self.assertEqual(test4[1], 'Diffie-Hellman selftest') - self.assertEqual(test4[2], 'dhm_selftest') - self.assertEqual(test4[3], []) - self.assertEqual(test4[4], []) - - def test_with_dependencies(self): - """ - Test that tests with dependencies are parsed. - :return: - """ - data = """ -Diffie-Hellman full exchange #1 -depends_on:YAHOO -dhm_do_dhm:10:"23":10:"5" - -Diffie-Hellman full exchange #2 -dhm_do_dhm:10:"93450983094850938450983409623":10:"9345098304850938450983409622" - -""" - stream = StringIOWrapper('test_suite_ut.function', data) - # List of (name, function_name, dependencies, args) - tests = list(parse_test_data(stream)) - test1, test2 = tests - self.assertEqual(test1[0], 4) - self.assertEqual(test1[1], 'Diffie-Hellman full exchange #1') - self.assertEqual(test1[2], 'dhm_do_dhm') - self.assertEqual(test1[3], ['YAHOO']) - self.assertEqual(test1[4], ['10', '"23"', '10', '"5"']) - - self.assertEqual(test2[0], 7) - self.assertEqual(test2[1], 'Diffie-Hellman full exchange #2') - self.assertEqual(test2[2], 'dhm_do_dhm') - self.assertEqual(test2[3], []) - self.assertEqual(test2[4], ['10', '"93450983094850938450983409623"', - '10', '"9345098304850938450983409622"']) - - def test_no_args(self): - """ - Test GeneratorInputError is raised when test function name and - args line is missing. - :return: - """ - data = """ -Diffie-Hellman full exchange #1 -depends_on:YAHOO - - -Diffie-Hellman full exchange #2 -dhm_do_dhm:10:"93450983094850938450983409623":10:"9345098304850938450983409622" - -""" - stream = StringIOWrapper('test_suite_ut.function', data) - err = None - try: - for _, _, _, _, _ in parse_test_data(stream): - pass - except GeneratorInputError as err: - self.assertEqual(type(err), GeneratorInputError) - - def test_incomplete_data(self): - """ - Test GeneratorInputError is raised when test function name - and args line is missing. - :return: - """ - data = """ -Diffie-Hellman full exchange #1 -depends_on:YAHOO -""" - stream = StringIOWrapper('test_suite_ut.function', data) - err = None - try: - for _, _, _, _, _ in parse_test_data(stream): - pass - except GeneratorInputError as err: - self.assertEqual(type(err), GeneratorInputError) - - -class GenDepCheck(TestCase): - """ - Test suite for gen_dep_check(). It is assumed this function is - called with valid inputs. - """ - - def test_gen_dep_check(self): - """ - Test that dependency check code generated correctly. - :return: - """ - expected = """ - case 5: - { -#if defined(YAHOO) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break;""" - out = gen_dep_check(5, 'YAHOO') - self.assertEqual(out, expected) - - def test_not_defined_dependency(self): - """ - Test dependency with !. - :return: - """ - expected = """ - case 5: - { -#if !defined(YAHOO) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break;""" - out = gen_dep_check(5, '!YAHOO') - self.assertEqual(out, expected) - - def test_empty_dependency(self): - """ - Test invalid dependency input. - :return: - """ - self.assertRaises(GeneratorInputError, gen_dep_check, 5, '!') - - def test_negative_dep_id(self): - """ - Test invalid dependency input. - :return: - """ - self.assertRaises(GeneratorInputError, gen_dep_check, -1, 'YAHOO') - - -class GenExpCheck(TestCase): - """ - Test suite for gen_expression_check(). It is assumed this function - is called with valid inputs. - """ - - def test_gen_exp_check(self): - """ - Test that expression check code generated correctly. - :return: - """ - expected = """ - case 5: - { - *out_value = YAHOO; - } - break;""" - out = gen_expression_check(5, 'YAHOO') - self.assertEqual(out, expected) - - def test_invalid_expression(self): - """ - Test invalid expression input. - :return: - """ - self.assertRaises(GeneratorInputError, gen_expression_check, 5, '') - - def test_negative_exp_id(self): - """ - Test invalid expression id. - :return: - """ - self.assertRaises(GeneratorInputError, gen_expression_check, - -1, 'YAHOO') - - -class WriteDependencies(TestCase): - """ - Test suite for testing write_dependencies. - """ - - def test_no_test_dependencies(self): - """ - Test when test dependencies input is empty. - :return: - """ - stream = StringIOWrapper('test_suite_ut.data', '') - unique_dependencies = [] - dep_check_code = write_dependencies(stream, [], unique_dependencies) - self.assertEqual(dep_check_code, '') - self.assertEqual(len(unique_dependencies), 0) - self.assertEqual(stream.getvalue(), '') - - def test_unique_dep_ids(self): - """ - - :return: - """ - stream = StringIOWrapper('test_suite_ut.data', '') - unique_dependencies = [] - dep_check_code = write_dependencies(stream, ['DEP3', 'DEP2', 'DEP1'], - unique_dependencies) - expect_dep_check_code = ''' - case 0: - { -#if defined(DEP3) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break; - case 1: - { -#if defined(DEP2) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break; - case 2: - { -#if defined(DEP1) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break;''' - self.assertEqual(dep_check_code, expect_dep_check_code) - self.assertEqual(len(unique_dependencies), 3) - self.assertEqual(stream.getvalue(), 'depends_on:0:1:2\n') - - def test_dep_id_repeat(self): - """ - - :return: - """ - stream = StringIOWrapper('test_suite_ut.data', '') - unique_dependencies = [] - dep_check_code = '' - dep_check_code += write_dependencies(stream, ['DEP3', 'DEP2'], - unique_dependencies) - dep_check_code += write_dependencies(stream, ['DEP2', 'DEP1'], - unique_dependencies) - dep_check_code += write_dependencies(stream, ['DEP1', 'DEP3'], - unique_dependencies) - expect_dep_check_code = ''' - case 0: - { -#if defined(DEP3) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break; - case 1: - { -#if defined(DEP2) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break; - case 2: - { -#if defined(DEP1) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break;''' - self.assertEqual(dep_check_code, expect_dep_check_code) - self.assertEqual(len(unique_dependencies), 3) - self.assertEqual(stream.getvalue(), - 'depends_on:0:1\ndepends_on:1:2\ndepends_on:2:0\n') - - -class WriteParams(TestCase): - """ - Test Suite for testing write_parameters(). - """ - - def test_no_params(self): - """ - Test with empty test_args - :return: - """ - stream = StringIOWrapper('test_suite_ut.data', '') - unique_expressions = [] - expression_code = write_parameters(stream, [], [], unique_expressions) - self.assertEqual(len(unique_expressions), 0) - self.assertEqual(expression_code, '') - self.assertEqual(stream.getvalue(), '\n') - - def test_no_exp_param(self): - """ - Test when there is no macro or expression in the params. - :return: - """ - stream = StringIOWrapper('test_suite_ut.data', '') - unique_expressions = [] - expression_code = write_parameters(stream, ['"Yahoo"', '"abcdef00"', - '0'], - ['char*', 'hex', 'int'], - unique_expressions) - self.assertEqual(len(unique_expressions), 0) - self.assertEqual(expression_code, '') - self.assertEqual(stream.getvalue(), - ':char*:"Yahoo":hex:"abcdef00":int:0\n') - - def test_hex_format_int_param(self): - """ - Test int parameter in hex format. - :return: - """ - stream = StringIOWrapper('test_suite_ut.data', '') - unique_expressions = [] - expression_code = write_parameters(stream, - ['"Yahoo"', '"abcdef00"', '0xAA'], - ['char*', 'hex', 'int'], - unique_expressions) - self.assertEqual(len(unique_expressions), 0) - self.assertEqual(expression_code, '') - self.assertEqual(stream.getvalue(), - ':char*:"Yahoo":hex:"abcdef00":int:0xAA\n') - - def test_with_exp_param(self): - """ - Test when there is macro or expression in the params. - :return: - """ - stream = StringIOWrapper('test_suite_ut.data', '') - unique_expressions = [] - expression_code = write_parameters(stream, - ['"Yahoo"', '"abcdef00"', '0', - 'MACRO1', 'MACRO2', 'MACRO3'], - ['char*', 'hex', 'int', - 'int', 'int', 'int'], - unique_expressions) - self.assertEqual(len(unique_expressions), 3) - self.assertEqual(unique_expressions, ['MACRO1', 'MACRO2', 'MACRO3']) - expected_expression_code = ''' - case 0: - { - *out_value = MACRO1; - } - break; - case 1: - { - *out_value = MACRO2; - } - break; - case 2: - { - *out_value = MACRO3; - } - break;''' - self.assertEqual(expression_code, expected_expression_code) - self.assertEqual(stream.getvalue(), - ':char*:"Yahoo":hex:"abcdef00":int:0:exp:0:exp:1' - ':exp:2\n') - - def test_with_repeat_calls(self): - """ - Test when write_parameter() is called with same macro or expression. - :return: - """ - stream = StringIOWrapper('test_suite_ut.data', '') - unique_expressions = [] - expression_code = '' - expression_code += write_parameters(stream, - ['"Yahoo"', 'MACRO1', 'MACRO2'], - ['char*', 'int', 'int'], - unique_expressions) - expression_code += write_parameters(stream, - ['"abcdef00"', 'MACRO2', 'MACRO3'], - ['hex', 'int', 'int'], - unique_expressions) - expression_code += write_parameters(stream, - ['0', 'MACRO3', 'MACRO1'], - ['int', 'int', 'int'], - unique_expressions) - self.assertEqual(len(unique_expressions), 3) - self.assertEqual(unique_expressions, ['MACRO1', 'MACRO2', 'MACRO3']) - expected_expression_code = ''' - case 0: - { - *out_value = MACRO1; - } - break; - case 1: - { - *out_value = MACRO2; - } - break; - case 2: - { - *out_value = MACRO3; - } - break;''' - self.assertEqual(expression_code, expected_expression_code) - expected_data_file = ''':char*:"Yahoo":exp:0:exp:1 -:hex:"abcdef00":exp:1:exp:2 -:int:0:exp:2:exp:0 -''' - self.assertEqual(stream.getvalue(), expected_data_file) - - -class GenTestSuiteDependenciesChecks(TestCase): - """ - Test suite for testing gen_suite_dep_checks() - """ - def test_empty_suite_dependencies(self): - """ - Test with empty suite_dependencies list. - - :return: - """ - dep_check_code, expression_code = \ - gen_suite_dep_checks([], 'DEP_CHECK_CODE', 'EXPRESSION_CODE') - self.assertEqual(dep_check_code, 'DEP_CHECK_CODE') - self.assertEqual(expression_code, 'EXPRESSION_CODE') - - def test_suite_dependencies(self): - """ - Test with suite_dependencies list. - - :return: - """ - dep_check_code, expression_code = \ - gen_suite_dep_checks(['SUITE_DEP'], 'DEP_CHECK_CODE', - 'EXPRESSION_CODE') - expected_dep_check_code = ''' -#if defined(SUITE_DEP) -DEP_CHECK_CODE -#endif -''' - expected_expression_code = ''' -#if defined(SUITE_DEP) -EXPRESSION_CODE -#endif -''' - self.assertEqual(dep_check_code, expected_dep_check_code) - self.assertEqual(expression_code, expected_expression_code) - - def test_no_dep_no_exp(self): - """ - Test when there are no dependency and expression code. - :return: - """ - dep_check_code, expression_code = gen_suite_dep_checks([], '', '') - self.assertEqual(dep_check_code, '') - self.assertEqual(expression_code, '') - - -class GenFromTestData(TestCase): - """ - Test suite for gen_from_test_data() - """ - - @staticmethod - @patch("generate_test_code.write_dependencies") - @patch("generate_test_code.write_parameters") - @patch("generate_test_code.gen_suite_dep_checks") - def test_intermediate_data_file(func_mock1, - write_parameters_mock, - write_dependencies_mock): - """ - Test that intermediate data file is written with expected data. - :return: - """ - data = ''' -My test -depends_on:DEP1 -func1:0 -''' - data_f = StringIOWrapper('test_suite_ut.data', data) - out_data_f = StringIOWrapper('test_suite_ut.datax', '') - func_info = {'test_func1': (1, ('int',))} - suite_dependencies = [] - write_parameters_mock.side_effect = write_parameters - write_dependencies_mock.side_effect = write_dependencies - func_mock1.side_effect = gen_suite_dep_checks - gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies) - write_dependencies_mock.assert_called_with(out_data_f, - ['DEP1'], ['DEP1']) - write_parameters_mock.assert_called_with(out_data_f, ['0'], - ('int',), []) - expected_dep_check_code = ''' - case 0: - { -#if defined(DEP1) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break;''' - func_mock1.assert_called_with( - suite_dependencies, expected_dep_check_code, '') - - def test_function_not_found(self): - """ - Test that AssertError is raised when function info in not found. - :return: - """ - data = ''' -My test -depends_on:DEP1 -func1:0 -''' - data_f = StringIOWrapper('test_suite_ut.data', data) - out_data_f = StringIOWrapper('test_suite_ut.datax', '') - func_info = {'test_func2': (1, ('int',))} - suite_dependencies = [] - self.assertRaises(GeneratorInputError, gen_from_test_data, - data_f, out_data_f, func_info, suite_dependencies) - - def test_different_func_args(self): - """ - Test that AssertError is raised when no. of parameters and - function args differ. - :return: - """ - data = ''' -My test -depends_on:DEP1 -func1:0 -''' - data_f = StringIOWrapper('test_suite_ut.data', data) - out_data_f = StringIOWrapper('test_suite_ut.datax', '') - func_info = {'test_func2': (1, ('int', 'hex'))} - suite_dependencies = [] - self.assertRaises(GeneratorInputError, gen_from_test_data, data_f, - out_data_f, func_info, suite_dependencies) - - def test_output(self): - """ - Test that intermediate data file is written with expected data. - :return: - """ - data = ''' -My test 1 -depends_on:DEP1 -func1:0:0xfa:MACRO1:MACRO2 - -My test 2 -depends_on:DEP1:DEP2 -func2:"yahoo":88:MACRO1 -''' - data_f = StringIOWrapper('test_suite_ut.data', data) - out_data_f = StringIOWrapper('test_suite_ut.datax', '') - func_info = {'test_func1': (0, ('int', 'int', 'int', 'int')), - 'test_func2': (1, ('char*', 'int', 'int'))} - suite_dependencies = [] - dep_check_code, expression_code = \ - gen_from_test_data(data_f, out_data_f, func_info, - suite_dependencies) - expected_dep_check_code = ''' - case 0: - { -#if defined(DEP1) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break; - case 1: - { -#if defined(DEP2) - ret = DEPENDENCY_SUPPORTED; -#else - ret = DEPENDENCY_NOT_SUPPORTED; -#endif - } - break;''' - expected_data = '''My test 1 -depends_on:0 -0:int:0:int:0xfa:exp:0:exp:1 - -My test 2 -depends_on:0:1 -1:char*:"yahoo":int:88:exp:0 - -''' - expected_expression_code = ''' - case 0: - { - *out_value = MACRO1; - } - break; - case 1: - { - *out_value = MACRO2; - } - break;''' - self.assertEqual(dep_check_code, expected_dep_check_code) - self.assertEqual(out_data_f.getvalue(), expected_data) - self.assertEqual(expression_code, expected_expression_code) - - -if __name__ == '__main__': - unittest_main() diff --git a/tests/suites/test_suite_pk.function b/tests/suites/test_suite_pk.function index 1188137b33..23f5cdabd5 100644 --- a/tests/suites/test_suite_pk.function +++ b/tests/suites/test_suite_pk.function @@ -243,7 +243,7 @@ psa_status_t pk_psa_import_key(const unsigned char *key_data, size_t key_len, /** Setup the provided PK context. * * Predefined keys used for the setup are taken from "test/src/test_keys.h" - * which is automatically generated using "tests/scripts/generate_test_keys.py". + * which is automatically generated using "framework/scripts/generate_test_keys.py". * * \param pk The PK object to fill. It must have been initialized * (mbedtls_pk_init()), but not setup (mbedtls_pk_setup()).