#!/usr/bin/env python3 # Greentea host test script for Mbed TLS on-target test suite testing. # # Copyright The Mbed TLS Contributors # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Mbed TLS on-target test suite tests are implemented as Greentea tests. Greentea tests are implemented in two parts: target test and host test. Target test is a C application that is built for the target platform and executes on the target. Host test is a Python class derived from mbed_host_tests.BaseHostTest. Target communicates with the host over serial for the test data and sends back the result. Python tool mbedgt (Greentea) is responsible for flashing the test binary on to the target and dynamically loading this host test module. Greentea documentation can be found here: https://github.com/ARMmbed/greentea """ import re import os import binascii from mbed_host_tests import BaseHostTest, event_callback # pylint: disable=import-error class TestDataParserError(Exception): """Indicates error in test data, read from .data file.""" pass class TestDataParser: """ Parses test name, dependencies, test function name and test parameters from the data file. """ def __init__(self): """ Constructor """ self.tests = [] def parse(self, data_file): """ Data file parser. :param data_file: Data file path """ with open(data_file, 'r') as data_f: self.__parse(data_f) @staticmethod def __escaped_split(inp_str, split_char): """ Splits inp_str on split_char except when escaped. :param inp_str: String to split :param split_char: Split character :return: List of splits """ split_colon_fn = lambda x: re.sub(r'\\' + split_char, split_char, x) if len(split_char) > 1: raise ValueError('Expected split character. Found string!') out = list(map(split_colon_fn, re.split(r'(?> x) & 0xff) for x in [24, 16, 8, 0]]) return data_bytes def test_vector_to_bytes(self, function_id, dependencies, parameters): """ Converts test vector into a byte array that can be sent to the target. :param function_id: Test Function Identifier :param dependencies: Dependency list :param parameters: Test function input parameters :return: Byte array and its length """ data_bytes = bytearray([len(dependencies)]) if dependencies: data_bytes += bytearray(dependencies) data_bytes += bytearray([function_id, len(parameters)]) for typ, param in parameters: if typ in ('int', 'exp'): i = int(param, 0) data_bytes += b'I' if typ == 'int' else b'E' self.align_32bit(data_bytes) data_bytes += self.int32_to_big_endian_bytes(i) elif typ == 'char*': param = param.strip('"') i = len(param) + 1 # + 1 for null termination data_bytes += b'S' self.align_32bit(data_bytes) data_bytes += self.int32_to_big_endian_bytes(i) data_bytes += bytearray(param, encoding='ascii') data_bytes += b'\0' # Null terminate elif typ == 'hex': binary_data = self.hex_str_bytes(param) data_bytes += b'H' self.align_32bit(data_bytes) i = len(binary_data) data_bytes += self.int32_to_big_endian_bytes(i) data_bytes += binary_data length = self.int32_to_big_endian_bytes(len(data_bytes)) return data_bytes, length def run_next_test(self): """ Fetch next test information and execute the test. """ self.test_index += 1 self.dep_index = 0 if self.test_index < len(self.tests): name, function_id, dependencies, args = self.tests[self.test_index] self.run_test(name, function_id, dependencies, args) else: self.notify_complete(self.suite_passed) def run_test(self, name, function_id, dependencies, args): """ Execute the test on target by sending next test information. :param name: Test name :param function_id: function identifier :param dependencies: Dependencies list :param args: test parameters :return: """ self.log("Running: %s" % name) param_bytes, length = self.test_vector_to_bytes(function_id, dependencies, args) self.send_kv( ''.join('{:02x}'.format(x) for x in length), ''.join('{:02x}'.format(x) for x in param_bytes) ) @staticmethod def get_result(value): """ Converts result from string type to integer :param value: Result code in string :return: Integer result code. Value is from the test status constants defined under the MbedTlsTest class. """ try: return int(value) except ValueError: ValueError("Result should return error number. " "Instead received %s" % value) @event_callback('GO') def on_go(self, _key, _value, _timestamp): """ Sent by the target to start first test. :param _key: Event key :param _value: Value. ignored :param _timestamp: Timestamp ignored. :return: """ self.run_next_test() @event_callback("R") def on_result(self, _key, value, _timestamp): """ Handle result. Prints test start, finish required by Greentea to detect test execution. :param _key: Event key :param value: Value. ignored :param _timestamp: Timestamp ignored. :return: """ int_val = self.get_result(value) name, _, _, _ = self.tests[self.test_index] self.log('{{__testcase_start;%s}}' % name) self.log('{{__testcase_finish;%s;%d;%d}}' % (name, int_val == 0, int_val != 0)) if int_val != 0: self.suite_passed = False self.run_next_test() @event_callback("F") def on_failure(self, _key, value, _timestamp): """ Handles test execution failure. That means dependency not supported or Test function not supported. Hence marking test as skipped. :param _key: Event key :param value: Value. ignored :param _timestamp: Timestamp ignored. :return: """ int_val = self.get_result(value) if int_val in self.error_str: err = self.error_str[int_val] else: err = 'Unknown error' # For skip status, do not write {{__testcase_finish;...}} self.log("Error: %s" % err) self.run_next_test()