blob: a9730708a49b832d1f869798e596eccea6ca0ac7 [file] [log] [blame]
Azim Khan951a2c82018-06-29 03:47:08 +01001# Greentea host test script for Mbed TLS on-target test suite testing.
Azim Khanf0e42fb2017-08-02 14:47:13 +01002#
Azim Khan8d686bf2018-07-04 23:29:46 +01003# Copyright (C) 2018, Arm Limited, All Rights Reserved
Azim Khanf0e42fb2017-08-02 14:47:13 +01004# SPDX-License-Identifier: Apache-2.0
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
Azim Khanb31aa442018-07-03 11:57:54 +010018# This file is part of Mbed TLS (https://tls.mbed.org)
Azim Khanf0e42fb2017-08-02 14:47:13 +010019
20
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010021"""
Azim Khan8d686bf2018-07-04 23:29:46 +010022Mbed TLS on-target test suite tests are implemented as Greentea
Azim Khan951a2c82018-06-29 03:47:08 +010023tests. Greentea tests are implemented in two parts: target test and
24host test. Target test is a C application that is built for the
25target platform and executes on the target. Host test is a Python
26class derived from mbed_host_tests.BaseHostTest. Target communicates
Azim Khan8d686bf2018-07-04 23:29:46 +010027with the host over serial for the test data and sends back the result.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010028
Azim Khanb31aa442018-07-03 11:57:54 +010029Python tool mbedgt (Greentea) is responsible for flashing the test
Azim Khan8d686bf2018-07-04 23:29:46 +010030binary on to the target and dynamically loading this host test module.
Azim Khan951a2c82018-06-29 03:47:08 +010031
Azim Khan8d686bf2018-07-04 23:29:46 +010032Greentea documentation can be found here:
33https://github.com/ARMmbed/greentea
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010034"""
35
Azim Khanf0e42fb2017-08-02 14:47:13 +010036
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010037import re
38import os
Azim Khan663d4702017-07-07 15:40:26 +010039import binascii
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010040from mbed_host_tests import BaseHostTest, event_callback
41
42
Azim Khanb98e6ee2018-06-28 17:11:33 +010043class TestDataParserError(Exception):
44 """Indicates error in test data, read from .data file."""
45 pass
46
47
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010048class TestDataParser(object):
49 """
Azim Khan951a2c82018-06-29 03:47:08 +010050 Parses test name, dependencies, test function name and test parameters
51 from the data file.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010052 """
53
54 def __init__(self):
55 """
56 Constructor
57 """
58 self.tests = []
59
60 def parse(self, data_file):
61 """
Azim Khanf0e42fb2017-08-02 14:47:13 +010062 Data file parser.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010063
Azim Khanf0e42fb2017-08-02 14:47:13 +010064 :param data_file: Data file path
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010065 """
Azim Khanb31aa442018-07-03 11:57:54 +010066 with open(data_file, 'r') as data_f:
67 self.__parse(data_f)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010068
69 @staticmethod
Azim Khanb31aa442018-07-03 11:57:54 +010070 def __escaped_split(inp_str, split_char):
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010071 """
Azim Khanb31aa442018-07-03 11:57:54 +010072 Splits inp_str on split_char except when escaped.
Azim Khanf0e42fb2017-08-02 14:47:13 +010073
Azim Khanb31aa442018-07-03 11:57:54 +010074 :param inp_str: String to split
75 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +010076 :return: List of splits
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010077 """
Azim Khanb31aa442018-07-03 11:57:54 +010078 if len(split_char) > 1:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010079 raise ValueError('Expected split character. Found string!')
80 out = []
81 part = ''
82 escape = False
Azim Khanb31aa442018-07-03 11:57:54 +010083 for character in inp_str:
84 if not escape and character == split_char:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010085 out.append(part)
86 part = ''
87 else:
Azim Khanb31aa442018-07-03 11:57:54 +010088 part += character
89 escape = not escape and character == '\\'
90 if part:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010091 out.append(part)
92 return out
93
Azim Khanb31aa442018-07-03 11:57:54 +010094 def __parse(self, data_f):
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010095 """
Azim Khanf0e42fb2017-08-02 14:47:13 +010096 Parses data file using supplied file object.
97
Azim Khanb31aa442018-07-03 11:57:54 +010098 :param data_f: Data file object
Azim Khanf0e42fb2017-08-02 14:47:13 +010099 :return:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100100 """
Azim Khanb31aa442018-07-03 11:57:54 +0100101 for line in data_f:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100102 line = line.strip()
Azim Khanb31aa442018-07-03 11:57:54 +0100103 if not line:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100104 continue
105 # Read test name
106 name = line
107
108 # Check dependencies
Azim Khanb31aa442018-07-03 11:57:54 +0100109 dependencies = []
110 line = data_f.next().strip()
111 match = re.search('depends_on:(.*)', line)
112 if match:
113 dependencies = [int(x) for x in match.group(1).split(':')]
114 line = data_f.next().strip()
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100115
116 # Read test vectors
Azim Khan663d4702017-07-07 15:40:26 +0100117 line = line.replace('\\n', '\n')
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100118 parts = self.__escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100119 function_name = int(parts[0])
120 args = parts[1:]
121 args_count = len(args)
122 if args_count % 2 != 0:
Azim Khanb98e6ee2018-06-28 17:11:33 +0100123 raise TestDataParserError("Number of test arguments should "
124 "be even: %s" % line)
Azim Khanb31aa442018-07-03 11:57:54 +0100125 grouped_args = [(args[i * 2], args[(i * 2) + 1])
126 for i in range(len(args)/2)]
127 self.tests.append((name, function_name, dependencies,
128 grouped_args))
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100129
130 def get_test_data(self):
131 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100132 Returns test data.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100133 """
134 return self.tests
135
136
137class MbedTlsTest(BaseHostTest):
138 """
Azim Khanb31aa442018-07-03 11:57:54 +0100139 Host test for Mbed TLS unit tests. This script is loaded at
140 run time by Greentea for executing Mbed TLS test suites. Each
Azim Khan951a2c82018-06-29 03:47:08 +0100141 communication from the target is received in this object as
142 an event, which is then handled by the event handler method
143 decorated by the associated event. Ex: @event_callback('GO').
144
145 Target test sends requests for dispatching next test. It reads
146 tests from the intermediate data file and sends test function
147 identifier, dependency identifiers, expression identifiers and
Azim Khanb31aa442018-07-03 11:57:54 +0100148 the test data in binary form. Target test checks dependencies
Azim Khan951a2c82018-06-29 03:47:08 +0100149 , evaluate integer constant expressions and dispatches the test
Azim Khan8d686bf2018-07-04 23:29:46 +0100150 function with received test parameters. After test function is
151 finished, target sends the result. This class handles the result
152 event and prints verdict in the form that Greentea understands.
Azim Khan951a2c82018-06-29 03:47:08 +0100153
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100154 """
Azim Khan951a2c82018-06-29 03:47:08 +0100155 # status/error codes from suites/helpers.function
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100156 DEPENDENCY_SUPPORTED = 0
157 KEY_VALUE_MAPPING_FOUND = DEPENDENCY_SUPPORTED
158 DISPATCH_TEST_SUCCESS = DEPENDENCY_SUPPORTED
159
Azim Khan951a2c82018-06-29 03:47:08 +0100160 KEY_VALUE_MAPPING_NOT_FOUND = -1 # Expression Id not found.
161 DEPENDENCY_NOT_SUPPORTED = -2 # Dependency not supported.
162 DISPATCH_TEST_FN_NOT_FOUND = -3 # Test function not found.
163 DISPATCH_INVALID_TEST_DATA = -4 # Invalid parameter type.
164 DISPATCH_UNSUPPORTED_SUITE = -5 # Test suite not supported/enabled.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100165
166 def __init__(self):
167 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100168 Constructor initialises test index to 0.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100169 """
170 super(MbedTlsTest, self).__init__()
171 self.tests = []
172 self.test_index = -1
173 self.dep_index = 0
174 self.error_str = dict()
Azim Khanb31aa442018-07-03 11:57:54 +0100175 self.error_str[self.DEPENDENCY_SUPPORTED] = \
176 'DEPENDENCY_SUPPORTED'
177 self.error_str[self.KEY_VALUE_MAPPING_NOT_FOUND] = \
178 'KEY_VALUE_MAPPING_NOT_FOUND'
179 self.error_str[self.DEPENDENCY_NOT_SUPPORTED] = \
180 'DEPENDENCY_NOT_SUPPORTED'
181 self.error_str[self.DISPATCH_TEST_FN_NOT_FOUND] = \
182 'DISPATCH_TEST_FN_NOT_FOUND'
183 self.error_str[self.DISPATCH_INVALID_TEST_DATA] = \
184 'DISPATCH_INVALID_TEST_DATA'
185 self.error_str[self.DISPATCH_UNSUPPORTED_SUITE] = \
186 'DISPATCH_UNSUPPORTED_SUITE'
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100187
188 def setup(self):
189 """
Azim Khan951a2c82018-06-29 03:47:08 +0100190 Setup hook implementation. Reads test suite data file and parses out
191 tests.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100192 """
193 binary_path = self.get_config_item('image_path')
194 script_dir = os.path.split(os.path.abspath(__file__))[0]
195 suite_name = os.path.splitext(os.path.basename(binary_path))[0]
196 data_file = ".".join((suite_name, 'data'))
Azim Khan951a2c82018-06-29 03:47:08 +0100197 data_file = os.path.join(script_dir, '..', 'mbedtls',
198 suite_name, data_file)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100199 if os.path.exists(data_file):
200 self.log("Running tests from %s" % data_file)
201 parser = TestDataParser()
202 parser.parse(data_file)
203 self.tests = parser.get_test_data()
204 self.print_test_info()
205 else:
206 self.log("Data file not found: %s" % data_file)
207 self.notify_complete(False)
208
209 def print_test_info(self):
210 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100211 Prints test summary read by Greentea to detect test cases.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100212 """
213 self.log('{{__testcase_count;%d}}' % len(self.tests))
214 for name, _, _, _ in self.tests:
215 self.log('{{__testcase_name;%s}}' % name)
216
217 @staticmethod
Azim Khanb31aa442018-07-03 11:57:54 +0100218 def align_32bit(data_bytes):
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100219 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100220 4 byte aligns input byte array.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100221
222 :return:
223 """
Azim Khanb31aa442018-07-03 11:57:54 +0100224 data_bytes += bytearray((4 - (len(data_bytes))) % 4)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100225
Azim Khand59391a2017-06-01 14:04:17 +0100226 @staticmethod
227 def hex_str_bytes(hex_str):
228 """
229 Converts Hex string representation to byte array
230
Azim Khanf0e42fb2017-08-02 14:47:13 +0100231 :param hex_str: Hex in string format.
232 :return: Output Byte array
Azim Khand59391a2017-06-01 14:04:17 +0100233 """
Azim Khanb98e6ee2018-06-28 17:11:33 +0100234 if hex_str[0] != '"' or hex_str[len(hex_str) - 1] != '"':
235 raise TestDataParserError("HEX test parameter missing '\"':"
236 " %s" % hex_str)
Azim Khand59391a2017-06-01 14:04:17 +0100237 hex_str = hex_str.strip('"')
Azim Khanb98e6ee2018-06-28 17:11:33 +0100238 if len(hex_str) % 2 != 0:
239 raise TestDataParserError("HEX parameter len should be mod of "
240 "2: %s" % hex_str)
Azim Khand59391a2017-06-01 14:04:17 +0100241
Azim Khanb31aa442018-07-03 11:57:54 +0100242 data_bytes = binascii.unhexlify(hex_str)
243 return data_bytes
Azim Khand59391a2017-06-01 14:04:17 +0100244
Azim Khan663d4702017-07-07 15:40:26 +0100245 @staticmethod
Azim Khanb31aa442018-07-03 11:57:54 +0100246 def int32_to_big_endian_bytes(i):
Azim Khan663d4702017-07-07 15:40:26 +0100247 """
Azim Khanb31aa442018-07-03 11:57:54 +0100248 Coverts i to byte array in big endian format.
Azim Khan663d4702017-07-07 15:40:26 +0100249
Azim Khanf0e42fb2017-08-02 14:47:13 +0100250 :param i: Input integer
251 :return: Output bytes array in big endian or network order
Azim Khan663d4702017-07-07 15:40:26 +0100252 """
Azim Khanb31aa442018-07-03 11:57:54 +0100253 data_bytes = bytearray([((i >> x) & 0xff) for x in [24, 16, 8, 0]])
254 return data_bytes
Azim Khan663d4702017-07-07 15:40:26 +0100255
Azim Khanb31aa442018-07-03 11:57:54 +0100256 def test_vector_to_bytes(self, function_id, dependencies, parameters):
Azim Khan663d4702017-07-07 15:40:26 +0100257 """
258 Converts test vector into a byte array that can be sent to the target.
259
Azim Khanf0e42fb2017-08-02 14:47:13 +0100260 :param function_id: Test Function Identifier
Azim Khanb31aa442018-07-03 11:57:54 +0100261 :param dependencies: Dependency list
Azim Khanf0e42fb2017-08-02 14:47:13 +0100262 :param parameters: Test function input parameters
263 :return: Byte array and its length
Azim Khan663d4702017-07-07 15:40:26 +0100264 """
Azim Khanb31aa442018-07-03 11:57:54 +0100265 data_bytes = bytearray([len(dependencies)])
266 if dependencies:
267 data_bytes += bytearray(dependencies)
268 data_bytes += bytearray([function_id, len(parameters)])
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100269 for typ, param in parameters:
270 if typ == 'int' or typ == 'exp':
271 i = int(param)
Azim Khanb31aa442018-07-03 11:57:54 +0100272 data_bytes += 'I' if typ == 'int' else 'E'
273 self.align_32bit(data_bytes)
274 data_bytes += self.int32_to_big_endian_bytes(i)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100275 elif typ == 'char*':
276 param = param.strip('"')
277 i = len(param) + 1 # + 1 for null termination
Azim Khanb31aa442018-07-03 11:57:54 +0100278 data_bytes += 'S'
279 self.align_32bit(data_bytes)
280 data_bytes += self.int32_to_big_endian_bytes(i)
281 data_bytes += bytearray(list(param))
282 data_bytes += '\0' # Null terminate
Azim Khand59391a2017-06-01 14:04:17 +0100283 elif typ == 'hex':
Azim Khanb31aa442018-07-03 11:57:54 +0100284 binary_data = self.hex_str_bytes(param)
285 data_bytes += 'H'
286 self.align_32bit(data_bytes)
287 i = len(binary_data)
288 data_bytes += self.int32_to_big_endian_bytes(i)
289 data_bytes += binary_data
290 length = self.int32_to_big_endian_bytes(len(data_bytes))
291 return data_bytes, length
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100292
293 def run_next_test(self):
294 """
Azim Khan951a2c82018-06-29 03:47:08 +0100295 Fetch next test information and execute the test.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100296
297 """
298 self.test_index += 1
299 self.dep_index = 0
300 if self.test_index < len(self.tests):
Azim Khanb31aa442018-07-03 11:57:54 +0100301 name, function_id, dependencies, args = self.tests[self.test_index]
302 self.run_test(name, function_id, dependencies, args)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100303 else:
304 self.notify_complete(True)
305
Azim Khanb31aa442018-07-03 11:57:54 +0100306 def run_test(self, name, function_id, dependencies, args):
Azim Khan663d4702017-07-07 15:40:26 +0100307 """
Azim Khan951a2c82018-06-29 03:47:08 +0100308 Execute the test on target by sending next test information.
Azim Khan663d4702017-07-07 15:40:26 +0100309
Azim Khanf0e42fb2017-08-02 14:47:13 +0100310 :param name: Test name
311 :param function_id: function identifier
Azim Khanb31aa442018-07-03 11:57:54 +0100312 :param dependencies: Dependencies list
Azim Khanf0e42fb2017-08-02 14:47:13 +0100313 :param args: test parameters
Azim Khan663d4702017-07-07 15:40:26 +0100314 :return:
315 """
316 self.log("Running: %s" % name)
317
Azim Khanb31aa442018-07-03 11:57:54 +0100318 param_bytes, length = self.test_vector_to_bytes(function_id,
319 dependencies, args)
320 self.send_kv(length, param_bytes)
Azim Khan663d4702017-07-07 15:40:26 +0100321
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100322 @staticmethod
323 def get_result(value):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100324 """
325 Converts result from string type to integer
326 :param value: Result code in string
Azim Khan8d686bf2018-07-04 23:29:46 +0100327 :return: Integer result code. Value is from the test status
328 constants defined under the MbedTlsTest class.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100329 """
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100330 try:
331 return int(value)
332 except ValueError:
Azim Khanb31aa442018-07-03 11:57:54 +0100333 ValueError("Result should return error number. "
334 "Instead received %s" % value)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100335
336 @event_callback('GO')
Azim Khanb31aa442018-07-03 11:57:54 +0100337 def on_go(self, _key, _value, _timestamp):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100338 """
Azim Khan951a2c82018-06-29 03:47:08 +0100339 Sent by the target to start first test.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100340
Azim Khanb31aa442018-07-03 11:57:54 +0100341 :param _key: Event key
342 :param _value: Value. ignored
343 :param _timestamp: Timestamp ignored.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100344 :return:
345 """
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100346 self.run_next_test()
347
348 @event_callback("R")
Azim Khanb31aa442018-07-03 11:57:54 +0100349 def on_result(self, _key, value, _timestamp):
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100350 """
Azim Khan951a2c82018-06-29 03:47:08 +0100351 Handle result. Prints test start, finish required by Greentea
352 to detect test execution.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100353
Azim Khanb31aa442018-07-03 11:57:54 +0100354 :param _key: Event key
Azim Khanf0e42fb2017-08-02 14:47:13 +0100355 :param value: Value. ignored
Azim Khanb31aa442018-07-03 11:57:54 +0100356 :param _timestamp: Timestamp ignored.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100357 :return:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100358 """
359 int_val = self.get_result(value)
Azim Khanb31aa442018-07-03 11:57:54 +0100360 name, _, _, _ = self.tests[self.test_index]
Azim Khan5e7f8df2017-05-31 20:33:39 +0100361 self.log('{{__testcase_start;%s}}' % name)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100362 self.log('{{__testcase_finish;%s;%d;%d}}' % (name, int_val == 0,
363 int_val != 0))
364 self.run_next_test()
365
366 @event_callback("F")
Azim Khanb31aa442018-07-03 11:57:54 +0100367 def on_failure(self, _key, value, _timestamp):
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100368 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100369 Handles test execution failure. That means dependency not supported or
370 Test function not supported. Hence marking test as skipped.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100371
Azim Khanb31aa442018-07-03 11:57:54 +0100372 :param _key: Event key
Azim Khanf0e42fb2017-08-02 14:47:13 +0100373 :param value: Value. ignored
Azim Khanb31aa442018-07-03 11:57:54 +0100374 :param _timestamp: Timestamp ignored.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100375 :return:
376 """
377 int_val = self.get_result(value)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100378 if int_val in self.error_str:
379 err = self.error_str[int_val]
380 else:
381 err = 'Unknown error'
382 # For skip status, do not write {{__testcase_finish;...}}
383 self.log("Error: %s" % err)
384 self.run_next_test()