blob: b825f135966c15e2ca7f8e0d9257400fdce06a94 [file] [log] [blame]
Azim Khanf0e42fb2017-08-02 14:47:13 +01001# Greentea host test script for on-target tests.
2#
Mohammad Azim Khan78befd92018-03-06 11:49:41 +00003# 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#
18# This file is part of mbed TLS (https://tls.mbed.org)
19
20
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010021"""
Azim Khanf0e42fb2017-08-02 14:47:13 +010022Greentea host test script for on-target tests.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010023
Azim Khanf0e42fb2017-08-02 14:47:13 +010024Host test script for testing mbed TLS test suites on target. Implements
25BaseHostTest to handle key,value pairs (events) coming from mbed TLS
26tests. Reads data file corresponding to the executing binary and dispatches
27test cases.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010028"""
29
Azim Khanf0e42fb2017-08-02 14:47:13 +010030
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010031import re
32import os
Azim Khan663d4702017-07-07 15:40:26 +010033import binascii
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010034from mbed_host_tests import BaseHostTest, event_callback
35
36
37class TestDataParser(object):
38 """
39 parser for mbedtls test data files.
40 """
41
42 def __init__(self):
43 """
44 Constructor
45 """
46 self.tests = []
47
48 def parse(self, data_file):
49 """
Azim Khanf0e42fb2017-08-02 14:47:13 +010050 Data file parser.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010051
Azim Khanf0e42fb2017-08-02 14:47:13 +010052 :param data_file: Data file path
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010053 """
54 with open(data_file, 'r') as f:
55 self.__parse(f)
56
57 @staticmethod
58 def __escaped_split(str, ch):
59 """
Azim Khanf0e42fb2017-08-02 14:47:13 +010060 Splits str on ch except when escaped.
61
62 :param str: String to split
63 :param ch: Split character
64 :return: List of splits
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010065 """
66 if len(ch) > 1:
67 raise ValueError('Expected split character. Found string!')
68 out = []
69 part = ''
70 escape = False
71 for i in range(len(str)):
72 if not escape and str[i] == ch:
73 out.append(part)
74 part = ''
75 else:
76 part += str[i]
77 escape = not escape and str[i] == '\\'
78 if len(part):
79 out.append(part)
80 return out
81
82 def __parse(self, file):
83 """
Azim Khanf0e42fb2017-08-02 14:47:13 +010084 Parses data file using supplied file object.
85
86 :param file: Data file object
87 :return:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010088 """
Azim Khan663d4702017-07-07 15:40:26 +010089 for line in file:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010090 line = line.strip()
91 if len(line) == 0:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010092 continue
93 # Read test name
94 name = line
95
96 # Check dependencies
97 deps = []
Azim Khan663d4702017-07-07 15:40:26 +010098 line = file.next().strip()
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010099 m = re.search('depends_on\:(.*)', line)
100 if m:
101 deps = [int(x) for x in m.group(1).split(':')]
Azim Khan663d4702017-07-07 15:40:26 +0100102 line = file.next().strip()
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100103
104 # Read test vectors
Azim Khan663d4702017-07-07 15:40:26 +0100105 line = line.replace('\\n', '\n')
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100106 parts = self.__escaped_split(line, ':')
107 function = int(parts[0])
108 x = parts[1:]
109 l = len(x)
110 assert l % 2 == 0, "Number of test arguments should be even: %s" % line
111 args = [(x[i * 2], x[(i * 2) + 1]) for i in range(len(x)/2)]
112 self.tests.append((name, function, deps, args))
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100113
114 def get_test_data(self):
115 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100116 Returns test data.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100117 """
118 return self.tests
119
120
121class MbedTlsTest(BaseHostTest):
122 """
Azim Khan663d4702017-07-07 15:40:26 +0100123 Event handler for mbedtls unit tests. This script is loaded at run time
124 by htrun while executing mbedtls unit tests.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100125 """
126 # From suites/helpers.function
127 DEPENDENCY_SUPPORTED = 0
128 KEY_VALUE_MAPPING_FOUND = DEPENDENCY_SUPPORTED
129 DISPATCH_TEST_SUCCESS = DEPENDENCY_SUPPORTED
130
131 KEY_VALUE_MAPPING_NOT_FOUND = -1
132 DEPENDENCY_NOT_SUPPORTED = -2
133 DISPATCH_TEST_FN_NOT_FOUND = -3
134 DISPATCH_INVALID_TEST_DATA = -4
135 DISPATCH_UNSUPPORTED_SUITE = -5
136
137 def __init__(self):
138 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100139 Constructor initialises test index to 0.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100140 """
141 super(MbedTlsTest, self).__init__()
142 self.tests = []
143 self.test_index = -1
144 self.dep_index = 0
145 self.error_str = dict()
146 self.error_str[self.DEPENDENCY_SUPPORTED] = 'DEPENDENCY_SUPPORTED'
147 self.error_str[self.KEY_VALUE_MAPPING_NOT_FOUND] = 'KEY_VALUE_MAPPING_NOT_FOUND'
148 self.error_str[self.DEPENDENCY_NOT_SUPPORTED] = 'DEPENDENCY_NOT_SUPPORTED'
149 self.error_str[self.DISPATCH_TEST_FN_NOT_FOUND] = 'DISPATCH_TEST_FN_NOT_FOUND'
150 self.error_str[self.DISPATCH_INVALID_TEST_DATA] = 'DISPATCH_INVALID_TEST_DATA'
151 self.error_str[self.DISPATCH_UNSUPPORTED_SUITE] = 'DISPATCH_UNSUPPORTED_SUITE'
152
153 def setup(self):
154 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100155 Setup hook implementation. Reads test suite data file and parses out tests.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100156 """
157 binary_path = self.get_config_item('image_path')
158 script_dir = os.path.split(os.path.abspath(__file__))[0]
159 suite_name = os.path.splitext(os.path.basename(binary_path))[0]
160 data_file = ".".join((suite_name, 'data'))
161 data_file = os.path.join(script_dir, '..', 'mbedtls', suite_name, data_file)
162 if os.path.exists(data_file):
163 self.log("Running tests from %s" % data_file)
164 parser = TestDataParser()
165 parser.parse(data_file)
166 self.tests = parser.get_test_data()
167 self.print_test_info()
168 else:
169 self.log("Data file not found: %s" % data_file)
170 self.notify_complete(False)
171
172 def print_test_info(self):
173 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100174 Prints test summary read by Greentea to detect test cases.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100175 """
176 self.log('{{__testcase_count;%d}}' % len(self.tests))
177 for name, _, _, _ in self.tests:
178 self.log('{{__testcase_name;%s}}' % name)
179
180 @staticmethod
181 def align_32bit(b):
182 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100183 4 byte aligns input byte array.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100184
185 :return:
186 """
187 b += bytearray((4 - (len(b))) % 4)
188
Azim Khand59391a2017-06-01 14:04:17 +0100189 @staticmethod
190 def hex_str_bytes(hex_str):
191 """
192 Converts Hex string representation to byte array
193
Azim Khanf0e42fb2017-08-02 14:47:13 +0100194 :param hex_str: Hex in string format.
195 :return: Output Byte array
Azim Khand59391a2017-06-01 14:04:17 +0100196 """
197 assert hex_str[0] == '"' and hex_str[len(hex_str) - 1] == '"', \
198 "HEX test parameter missing '\"': %s" % hex_str
199 hex_str = hex_str.strip('"')
200 assert len(hex_str) % 2 == 0, "HEX parameter len should be mod of 2: %s" % hex_str
Azim Khand59391a2017-06-01 14:04:17 +0100201
Azim Khan663d4702017-07-07 15:40:26 +0100202 b = binascii.unhexlify(hex_str)
Azim Khand59391a2017-06-01 14:04:17 +0100203 return b
204
Azim Khan663d4702017-07-07 15:40:26 +0100205 @staticmethod
206 def int32_to_bigendian_bytes(i):
207 """
208 Coverts i to bytearray in big endian format.
209
Azim Khanf0e42fb2017-08-02 14:47:13 +0100210 :param i: Input integer
211 :return: Output bytes array in big endian or network order
Azim Khan663d4702017-07-07 15:40:26 +0100212 """
213 b = bytearray([((i >> x) & 0xff) for x in [24, 16, 8, 0]])
214 return b
215
216 def test_vector_to_bytes(self, function_id, deps, parameters):
217 """
218 Converts test vector into a byte array that can be sent to the target.
219
Azim Khanf0e42fb2017-08-02 14:47:13 +0100220 :param function_id: Test Function Identifier
221 :param deps: Dependency list
222 :param parameters: Test function input parameters
223 :return: Byte array and its length
Azim Khan663d4702017-07-07 15:40:26 +0100224 """
225 b = bytearray([len(deps)])
226 if len(deps):
227 b += bytearray(deps)
228 b += bytearray([function_id, len(parameters)])
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100229 for typ, param in parameters:
230 if typ == 'int' or typ == 'exp':
231 i = int(param)
232 b += 'I' if typ == 'int' else 'E'
233 self.align_32bit(b)
Azim Khan663d4702017-07-07 15:40:26 +0100234 b += self.int32_to_bigendian_bytes(i)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100235 elif typ == 'char*':
236 param = param.strip('"')
237 i = len(param) + 1 # + 1 for null termination
238 b += 'S'
239 self.align_32bit(b)
Azim Khan663d4702017-07-07 15:40:26 +0100240 b += self.int32_to_bigendian_bytes(i)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100241 b += bytearray(list(param))
242 b += '\0' # Null terminate
Azim Khand59391a2017-06-01 14:04:17 +0100243 elif typ == 'hex':
244 hb = self.hex_str_bytes(param)
245 b += 'H'
246 self.align_32bit(b)
247 i = len(hb)
Azim Khan663d4702017-07-07 15:40:26 +0100248 b += self.int32_to_bigendian_bytes(i)
Azim Khand59391a2017-06-01 14:04:17 +0100249 b += hb
Azim Khan663d4702017-07-07 15:40:26 +0100250 length = self.int32_to_bigendian_bytes(len(b))
251 return b, length
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100252
253 def run_next_test(self):
254 """
255 Send next test function to the target.
256
257 """
258 self.test_index += 1
259 self.dep_index = 0
260 if self.test_index < len(self.tests):
Azim Khan663d4702017-07-07 15:40:26 +0100261 name, function_id, deps, args = self.tests[self.test_index]
262 self.run_test(name, function_id, deps, args)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100263 else:
264 self.notify_complete(True)
265
Azim Khan663d4702017-07-07 15:40:26 +0100266 def run_test(self, name, function_id, deps, args):
267 """
268 Runs the test.
269
Azim Khanf0e42fb2017-08-02 14:47:13 +0100270 :param name: Test name
271 :param function_id: function identifier
272 :param deps: Dependencies list
273 :param args: test parameters
Azim Khan663d4702017-07-07 15:40:26 +0100274 :return:
275 """
276 self.log("Running: %s" % name)
277
278 bytes, length = self.test_vector_to_bytes(function_id, deps, args)
279 self.send_kv(length, bytes)
280
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100281 @staticmethod
282 def get_result(value):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100283 """
284 Converts result from string type to integer
285 :param value: Result code in string
286 :return: Integer result code
287 """
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100288 try:
289 return int(value)
290 except ValueError:
291 ValueError("Result should return error number. Instead received %s" % value)
292 return 0
293
294 @event_callback('GO')
295 def on_go(self, key, value, timestamp):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100296 """
297 Called on key "GO". Kicks off test execution.
298
299 :param key: Event key
300 :param value: Value. ignored
301 :param timestamp: Timestamp ignored.
302 :return:
303 """
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100304 self.run_next_test()
305
306 @event_callback("R")
307 def on_result(self, key, value, timestamp):
308 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100309 Handle result. Prints test start, finish prints required by Greentea to detect test execution.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100310
Azim Khanf0e42fb2017-08-02 14:47:13 +0100311 :param key: Event key
312 :param value: Value. ignored
313 :param timestamp: Timestamp ignored.
314 :return:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100315 """
316 int_val = self.get_result(value)
317 name, function, deps, args = self.tests[self.test_index]
Azim Khan5e7f8df2017-05-31 20:33:39 +0100318 self.log('{{__testcase_start;%s}}' % name)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100319 self.log('{{__testcase_finish;%s;%d;%d}}' % (name, int_val == 0,
320 int_val != 0))
321 self.run_next_test()
322
323 @event_callback("F")
324 def on_failure(self, key, value, timestamp):
325 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100326 Handles test execution failure. That means dependency not supported or
327 Test function not supported. Hence marking test as skipped.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100328
Azim Khanf0e42fb2017-08-02 14:47:13 +0100329 :param key: Event key
330 :param value: Value. ignored
331 :param timestamp: Timestamp ignored.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100332 :return:
333 """
334 int_val = self.get_result(value)
335 name, function, deps, args = self.tests[self.test_index]
336 if int_val in self.error_str:
337 err = self.error_str[int_val]
338 else:
339 err = 'Unknown error'
340 # For skip status, do not write {{__testcase_finish;...}}
341 self.log("Error: %s" % err)
342 self.run_next_test()