blob: 58020f1001dd1f7f128299540eadf93fc027bf81 [file] [log] [blame]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001"""
Mohammad Azim Khan95402612017-07-19 10:15:54 +01002 Test suites code generator.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01003
Mohammad Azim Khan95402612017-07-19 10:15:54 +01004 Copyright (C) 2006-2017, ARM Limited, All Rights Reserved
5 SPDX-License-Identifier: Apache-2.0
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01006
Mohammad Azim Khan95402612017-07-19 10:15:54 +01007 Licensed under the Apache License, Version 2.0 (the "License"); you may
8 not use this file except in compliance with the License.
9 You may obtain a copy of the License at
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010010
Mohammad Azim Khan95402612017-07-19 10:15:54 +010011 http://www.apache.org/licenses/LICENSE-2.0
12
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 See the License for the specific language governing permissions and
17 limitations under the License.
18
19 This file is part of mbed TLS (https://tls.mbed.org)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010020"""
21
22import os
23import re
24import argparse
25import shutil
26
27
28"""
29Generates code in following structure.
30
31<output dir>/
Azim Khan4b543232017-06-30 09:35:21 +010032 |-- mbedtls/
33 | |-- <test suite #1>/
34 | | |-- main.c
35 | | |-- *.data files
36 | ...
37 | |-- <test suite #n>/
38 | | |-- main.c
39 | | |-- *.data files
40 | |
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010041"""
42
43
44BEGIN_HEADER_REGEX = '/\*\s*BEGIN_HEADER\s*\*/'
45END_HEADER_REGEX = '/\*\s*END_HEADER\s*\*/'
46
47BEGIN_DEP_REGEX = 'BEGIN_DEPENDENCIES'
48END_DEP_REGEX = 'END_DEPENDENCIES'
49
50BEGIN_CASE_REGEX = '/\*\s*BEGIN_CASE\s*(.*?)\s*\*/'
51END_CASE_REGEX = '/\*\s*END_CASE\s*\*/'
52
53
54class InvalidFileFormat(Exception):
55 """
56 Exception to indicate invalid file format.
57 """
58 pass
59
60
Azim Khan4b543232017-06-30 09:35:21 +010061class FileWrapper(file):
62 """
63 File wrapper class. Provides reading with line no. tracking.
64 """
65
66 def __init__(self, file_name):
67 """
68 Init file handle.
69
70 :param file_name:
71 """
72 super(FileWrapper, self).__init__(file_name, 'r')
73 self.line_no = 0
74
75 def next(self):
76 """
77 Iterator return impl.
78 :return:
79 """
80 line = super(FileWrapper, self).next()
81 if line:
82 self.line_no += 1
83 return line
84
85 def readline(self, limit=0):
86 """
87 Wrap the base class readline.
88
89 :param limit:
90 :return:
91 """
92 return self.next()
93
94
95def split_dep(dep):
96 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
97
98
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010099def gen_deps(deps):
100 """
101 Generates dependency i.e. if def and endif code
102
103 :param deps:
104 :return:
105 """
Azim Khan4b543232017-06-30 09:35:21 +0100106 dep_start = ''.join(['#if %sdefined(%s)\n' % split_dep(x) for x in deps])
107 dep_end = ''.join(['#endif /* %s */\n' % x for x in reversed(deps)])
108
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100109 return dep_start, dep_end
110
111
112def gen_deps_one_line(deps):
113 """
114 Generates dependency checks in one line. Useful for writing code in #else case.
115
116 :param deps:
117 :return:
118 """
Azim Khan4b543232017-06-30 09:35:21 +0100119 defines = ('#if ' if len(deps) else '') + ' && '.join(['%sdefined(%s)' % split_dep(x) for x in deps])
120 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100121
122
Azim Khan4b543232017-06-30 09:35:21 +0100123def gen_function_wrapper(name, locals, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100124 """
125 Creates test function code
126
127 :param name:
Azim Khan4b543232017-06-30 09:35:21 +0100128 :param locals:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100129 :param args_dispatch:
130 :return:
131 """
132 # Then create the wrapper
133 wrapper = '''
134void {name}_wrapper( void ** params )
135{{
136 {unused_params}
Azim Khan2397bba2017-06-09 04:35:03 +0100137{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100138 {name}( {args} );
139}}
Azim Khan4b543232017-06-30 09:35:21 +0100140'''.format(name=name, unused_params='(void)params;' if len(args_dispatch) == 0 else '',
141 args=', '.join(args_dispatch),
142 locals=locals)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100143 return wrapper
144
145
146def gen_dispatch(name, deps):
147 """
148 Generates dispatch condition for the functions.
149
150 :param name:
151 :param deps:
152 :return:
153 """
154 if len(deps):
155 ifdef = gen_deps_one_line(deps)
156 dispatch_code = '''
157{ifdef}
158 {name}_wrapper,
159#else
160 NULL,
161#endif
162'''.format(ifdef=ifdef, name=name)
163 else:
164 dispatch_code = '''
165 {name}_wrapper,
166'''.format(name=name)
167
168 return dispatch_code
169
170
Azim Khan4b543232017-06-30 09:35:21 +0100171def parse_suite_headers(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100172 """
173 Parses function headers.
174
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100175 :param funcs_f:
176 :return:
177 """
Azim Khan4b543232017-06-30 09:35:21 +0100178 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100179 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100180 if re.search(END_HEADER_REGEX, line):
181 break
182 headers += line
183 else:
184 raise InvalidFileFormat("file: %s - end header pattern [%s] not found!" % (funcs_f.name, END_HEADER_REGEX))
185
Azim Khan4b543232017-06-30 09:35:21 +0100186 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100187
188
Azim Khan4b543232017-06-30 09:35:21 +0100189def parse_suite_deps(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100190 """
191 Parses function dependencies.
192
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100193 :param funcs_f:
194 :return:
195 """
196 deps = []
197 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100198 m = re.search('depends_on\:(.*)', line.strip())
199 if m:
200 deps += [x.strip() for x in m.group(1).split(':')]
201 if re.search(END_DEP_REGEX, line):
202 break
203 else:
204 raise InvalidFileFormat("file: %s - end dependency pattern [%s] not found!" % (funcs_f.name, END_DEP_REGEX))
205
Azim Khan4b543232017-06-30 09:35:21 +0100206 return deps
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100207
208
209def parse_function_deps(line):
210 """
211
212 :param line:
213 :return:
214 """
215 deps = []
216 m = re.search(BEGIN_CASE_REGEX, line)
217 dep_str = m.group(1)
218 if len(dep_str):
219 m = re.search('depends_on:(.*)', dep_str)
220 if m:
Azim Khan4b543232017-06-30 09:35:21 +0100221 deps = [x.strip() for x in m.group(1).strip().split(':')]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100222 return deps
223
224
225def parse_function_signature(line):
226 """
227 Parsing function signature
228
229 :param line:
230 :return:
231 """
232 args = []
Azim Khan2397bba2017-06-09 04:35:03 +0100233 locals = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100234 args_dispatch = []
235 m = re.search('\s*void\s+(\w+)\s*\(', line, re.I)
236 if not m:
237 raise ValueError("Test function should return 'void'\n%s" % line)
238 name = m.group(1)
239 line = line[len(m.group(0)):]
240 arg_idx = 0
241 for arg in line[:line.find(')')].split(','):
242 arg = arg.strip()
243 if arg == '':
244 continue
245 if re.search('int\s+.*', arg.strip()):
246 args.append('int')
247 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
248 elif re.search('char\s*\*\s*.*', arg.strip()):
249 args.append('char*')
250 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100251 elif re.search('HexParam_t\s*\*\s*.*', arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100252 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100253 # create a structure
254 locals += """ HexParam_t hex%d = {%s, %s};
255""" % (arg_idx, '(uint8_t *) params[%d]' % arg_idx, '*( (uint32_t *) params[%d] )' % (arg_idx + 1))
256
257 args_dispatch.append('&hex%d' % arg_idx)
258 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100259 else:
Azim Khan4b543232017-06-30 09:35:21 +0100260 raise ValueError("Test function arguments can only be 'int', 'char *' or 'HexParam_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100261 arg_idx += 1
262
Azim Khan4b543232017-06-30 09:35:21 +0100263 return name, args, locals, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100264
265
Azim Khan4b543232017-06-30 09:35:21 +0100266def parse_function_code(funcs_f, deps, suite_deps):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100267 """
268
269 :param line_no:
270 :param funcs_f:
271 :param deps:
272 :param suite_deps:
273 :return:
274 """
Azim Khan4b543232017-06-30 09:35:21 +0100275 code = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100276 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100277 # Check function signature
278 m = re.match('.*?\s+(\w+)\s*\(', line, re.I)
279 if m:
280 # check if we have full signature i.e. split in more lines
281 if not re.match('.*\)', line):
282 for lin in funcs_f:
283 line += lin
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100284 if re.search('.*?\)', line):
285 break
Azim Khan4b543232017-06-30 09:35:21 +0100286 name, args, locals, args_dispatch = parse_function_signature(line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100287 code += line.replace(name, 'test_' + name)
288 name = 'test_' + name
289 break
290 else:
291 raise InvalidFileFormat("file: %s - Test functions not found!" % funcs_f.name)
292
293 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100294 if re.search(END_CASE_REGEX, line):
295 break
296 code += line
297 else:
298 raise InvalidFileFormat("file: %s - end case pattern [%s] not found!" % (funcs_f.name, END_CASE_REGEX))
299
300 # Add exit label if not present
301 if code.find('exit:') == -1:
302 s = code.rsplit('}', 1)
303 if len(s) == 2:
Azim Khan4b543232017-06-30 09:35:21 +0100304 code = """exit:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100305 ;;
Azim Khan4b543232017-06-30 09:35:21 +0100306}""".join(s)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100307
Azim Khan4b543232017-06-30 09:35:21 +0100308 code += gen_function_wrapper(name, locals, args_dispatch)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100309 ifdef, endif = gen_deps(deps)
310 dispatch_code = gen_dispatch(name, suite_deps + deps)
Azim Khan4b543232017-06-30 09:35:21 +0100311 return name, args, ifdef + code + endif, dispatch_code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100312
313
314def parse_functions(funcs_f):
315 """
316 Returns functions code pieces
317
318 :param funcs_f:
319 :return:
320 """
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100321 suite_headers = ''
322 suite_deps = []
323 suite_functions = ''
324 func_info = {}
325 function_idx = 0
326 dispatch_code = ''
327 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100328 if re.search(BEGIN_HEADER_REGEX, line):
Azim Khan4b543232017-06-30 09:35:21 +0100329 headers = parse_suite_headers(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100330 suite_headers += headers
331 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khan4b543232017-06-30 09:35:21 +0100332 deps = parse_suite_deps(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100333 suite_deps += deps
334 elif re.search(BEGIN_CASE_REGEX, line):
335 deps = parse_function_deps(line)
Azim Khan4b543232017-06-30 09:35:21 +0100336 func_name, args, func_code, func_dispatch = parse_function_code(funcs_f, deps, suite_deps)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100337 suite_functions += func_code
338 # Generate dispatch code and enumeration info
339 assert func_name not in func_info, "file: %s - function %s re-declared at line %d" % \
Azim Khan4b543232017-06-30 09:35:21 +0100340 (funcs_f.name, func_name, funcs_f.line_no)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100341 func_info[func_name] = (function_idx, args)
342 dispatch_code += '/* Function Id: %d */\n' % function_idx
343 dispatch_code += func_dispatch
344 function_idx += 1
345
346 ifdef, endif = gen_deps(suite_deps)
Azim Khan13c6bfb2017-06-15 14:45:56 +0100347 func_code = ifdef + suite_headers + suite_functions + endif
348 return suite_deps, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100349
350
351def escaped_split(str, ch):
352 """
353 Split str on character ch but ignore escaped \{ch}
Azim Khan599cd242017-07-06 17:34:27 +0100354 Since return value is used to write back to the intermediate data file.
355 Any escape characters in the input are retained in the output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100356
357 :param str:
358 :param ch:
359 :return:
360 """
361 if len(ch) > 1:
362 raise ValueError('Expected split character. Found string!')
363 out = []
364 part = ''
365 escape = False
366 for i in range(len(str)):
367 if not escape and str[i] == ch:
368 out.append(part)
369 part = ''
370 else:
Azim Khanacc54732017-07-03 14:06:45 +0100371 part += str[i]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100372 escape = not escape and str[i] == '\\'
373 if len(part):
374 out.append(part)
375 return out
376
377
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100378def parse_test_data(data_f, debug=False):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100379 """
380 Parses .data file
381
382 :param data_f:
383 :return:
384 """
385 STATE_READ_NAME = 0
386 STATE_READ_ARGS = 1
387 state = STATE_READ_NAME
388 deps = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100389 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100390 for line in data_f:
391 line = line.strip()
392 if len(line) and line[0] == '#': # Skip comments
393 continue
394
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100395 # Blank line indicates end of test
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100396 if len(line) == 0:
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100397 assert state != STATE_READ_ARGS, "Newline before arguments. " \
398 "Test function and arguments missing for %s" % name
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100399 continue
400
401 if state == STATE_READ_NAME:
402 # Read test name
403 name = line
404 state = STATE_READ_ARGS
405 elif state == STATE_READ_ARGS:
406 # Check dependencies
407 m = re.search('depends_on\:(.*)', line)
408 if m:
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100409 deps = [x.strip() for x in m.group(1).split(':') if len(x.strip())]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100410 else:
411 # Read test vectors
412 parts = escaped_split(line, ':')
413 function = parts[0]
414 args = parts[1:]
415 yield name, function, deps, args
416 deps = []
417 state = STATE_READ_NAME
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100418 assert state != STATE_READ_ARGS, "Newline before arguments. " \
419 "Test function and arguments missing for %s" % name
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100420
421
422def gen_dep_check(dep_id, dep):
423 """
424 Generate code for the dependency.
425
426 :param dep_id:
427 :param dep:
428 :return:
429 """
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100430 assert dep_id > -1, "Dependency Id should be a positive integer."
431 noT, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
432 assert len(dep) > 0, "Dependency should not be an empty string."
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100433 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100434 case {id}:
435 {{
Azim Khand61b8372017-07-10 11:54:01 +0100436#if {noT}defined({macro})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100437 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100438#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100439 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100440#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100441 }}
442 break;'''.format(noT=noT, macro=dep, id=dep_id)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100443 return dep_check
444
445
446def gen_expression_check(exp_id, exp):
447 """
448 Generates code for expression check
449
450 :param exp_id:
451 :param exp:
452 :return:
453 """
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100454 assert exp_id > -1, "Expression Id should be a positive integer."
455 assert len(exp) > 0, "Expression should not be an empty string."
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100456 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100457 case {exp_id}:
458 {{
459 *out_value = {expression};
460 }}
461 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100462 return exp_code
463
464
Azim Khan599cd242017-07-06 17:34:27 +0100465def write_deps(out_data_f, test_deps, unique_deps):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100466 """
Azim Khan599cd242017-07-06 17:34:27 +0100467 Write dependencies to intermediate test data file.
468 It also returns dependency check code.
469
470 :param out_data_f:
471 :param dep:
472 :param unique_deps:
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100473 :return:
474 """
Azim Khan599cd242017-07-06 17:34:27 +0100475 dep_check_code = ''
476 if len(test_deps):
477 out_data_f.write('depends_on')
478 for dep in test_deps:
479 if dep not in unique_deps:
480 unique_deps.append(dep)
481 dep_id = unique_deps.index(dep)
482 dep_check_code += gen_dep_check(dep_id, dep)
483 else:
484 dep_id = unique_deps.index(dep)
485 out_data_f.write(':' + str(dep_id))
486 out_data_f.write('\n')
487 return dep_check_code
488
489
490def write_parameters(out_data_f, test_args, func_args, unique_expressions):
491 """
492 Writes test parameters to the intermediate data file.
493 Also generates expression code.
494
495 :param out_data_f:
496 :param test_args:
497 :param func_args:
498 :param unique_expressions:
499 :return:
500 """
501 expression_code = ''
502 for i in xrange(len(test_args)):
503 typ = func_args[i]
504 val = test_args[i]
505
506 # check if val is a non literal int val
507 if typ == 'int' and not re.match('(\d+$)|((0x)?[0-9a-fA-F]+$)', val): # its an expression
508 typ = 'exp'
509 if val not in unique_expressions:
510 unique_expressions.append(val)
511 # exp_id can be derived from len(). But for readability and consistency with case of existing let's
512 # use index().
513 exp_id = unique_expressions.index(val)
514 expression_code += gen_expression_check(exp_id, val)
515 val = exp_id
516 else:
517 val = unique_expressions.index(val)
518 out_data_f.write(':' + typ + ':' + str(val))
519 out_data_f.write('\n')
520 return expression_code
521
522
523def gen_suite_deps_checks(suite_deps, dep_check_code, expression_code):
524 """
525 Adds preprocessor checks for test suite dependencies.
526
527 :param suite_deps:
528 :param dep_check_code:
529 :param expression_code:
530 :return:
531 """
Azim Khan599cd242017-07-06 17:34:27 +0100532 if len(suite_deps):
533 ifdef = gen_deps_one_line(suite_deps)
534 dep_check_code = '''
535{ifdef}
536{code}
Azim Khan599cd242017-07-06 17:34:27 +0100537#endif
538'''.format(ifdef=ifdef, code=dep_check_code)
539 expression_code = '''
540{ifdef}
541{code}
Azim Khan599cd242017-07-06 17:34:27 +0100542#endif
543'''.format(ifdef=ifdef, code=expression_code)
544 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100545
546
Azim Khan13c6bfb2017-06-15 14:45:56 +0100547def gen_from_test_data(data_f, out_data_f, func_info, suite_deps):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100548 """
549 Generates dependency checks, expression code and intermediate data file from test data file.
550
551 :param data_f:
552 :param out_data_f:
553 :param func_info:
Azim Khan13c6bfb2017-06-15 14:45:56 +0100554 :param suite_deps:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100555 :return:
556 """
557 unique_deps = []
558 unique_expressions = []
559 dep_check_code = ''
560 expression_code = ''
561 for test_name, function_name, test_deps, test_args in parse_test_data(data_f):
562 out_data_f.write(test_name + '\n')
563
Azim Khan599cd242017-07-06 17:34:27 +0100564 # Write deps
565 dep_check_code += write_deps(out_data_f, test_deps, unique_deps)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100566
Azim Khan599cd242017-07-06 17:34:27 +0100567 # Write test function name
568 test_function_name = 'test_' + function_name
569 assert test_function_name in func_info, "Function %s not found!" % test_function_name
570 func_id, func_args = func_info[test_function_name]
571 out_data_f.write(str(func_id))
572
573 # Write parameters
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100574 assert len(test_args) == len(func_args), \
575 "Invalid number of arguments in test %s. See function %s signature." % (test_name, function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100576 expression_code += write_parameters(out_data_f, test_args, func_args, unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100577
Azim Khan599cd242017-07-06 17:34:27 +0100578 # Write a newline as test case separator
579 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100580
Azim Khan599cd242017-07-06 17:34:27 +0100581 dep_check_code, expression_code = gen_suite_deps_checks(suite_deps, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100582 return dep_check_code, expression_code
583
584
Azim Khan1de892b2017-06-09 15:02:36 +0100585def generate_code(funcs_file, data_file, template_file, platform_file, help_file, suites_dir, c_file, out_data_file):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100586 """
587 Generate mbed-os test code.
588
589 :param funcs_file:
590 :param dat a_file:
591 :param template_file:
592 :param platform_file:
593 :param help_file:
594 :param suites_dir:
595 :param c_file:
596 :param out_data_file:
597 :return:
598 """
599 for name, path in [('Functions file', funcs_file),
600 ('Data file', data_file),
601 ('Template file', template_file),
602 ('Platform file', platform_file),
603 ('Help code file', help_file),
604 ('Suites dir', suites_dir)]:
605 if not os.path.exists(path):
606 raise IOError("ERROR: %s [%s] not found!" % (name, path))
607
608 snippets = {'generator_script' : os.path.basename(__file__)}
609
610 # Read helpers
611 with open(help_file, 'r') as help_f, open(platform_file, 'r') as platform_f:
612 snippets['test_common_helper_file'] = help_file
613 snippets['test_common_helpers'] = help_f.read()
614 snippets['test_platform_file'] = platform_file
615 snippets['platform_code'] = platform_f.read().replace('DATA_FILE',
616 out_data_file.replace('\\', '\\\\')) # escape '\'
617
618 # Function code
Azim Khanacc54732017-07-03 14:06:45 +0100619 with FileWrapper(funcs_file) as funcs_f, open(data_file, 'r') as data_f, open(out_data_file, 'w') as out_data_f:
Azim Khan13c6bfb2017-06-15 14:45:56 +0100620 suite_deps, dispatch_code, func_code, func_info = parse_functions(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100621 snippets['functions_code'] = func_code
622 snippets['dispatch_code'] = dispatch_code
Azim Khan13c6bfb2017-06-15 14:45:56 +0100623 dep_check_code, expression_code = gen_from_test_data(data_f, out_data_f, func_info, suite_deps)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100624 snippets['dep_check_code'] = dep_check_code
625 snippets['expression_code'] = expression_code
626
627 snippets['test_file'] = c_file
628 snippets['test_main_file'] = template_file
629 snippets['test_case_file'] = funcs_file
630 snippets['test_case_data_file'] = data_file
631 # Read Template
632 # Add functions
633 #
634 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
635 line_no = 1
636 for line in template_f.readlines():
637 snippets['line_no'] = line_no + 1 # Increment as it sets next line number
638 code = line.format(**snippets)
639 c_f.write(code)
640 line_no += 1
641
642
643def check_cmd():
644 """
645 Command line parser.
646
647 :return:
648 """
649 parser = argparse.ArgumentParser(description='Generate code for mbed-os tests.')
650
651 parser.add_argument("-f", "--functions-file",
652 dest="funcs_file",
653 help="Functions file",
654 metavar="FUNCTIONS",
655 required=True)
656
657 parser.add_argument("-d", "--data-file",
658 dest="data_file",
659 help="Data file",
660 metavar="DATA",
661 required=True)
662
663 parser.add_argument("-t", "--template-file",
664 dest="template_file",
665 help="Template file",
666 metavar="TEMPLATE",
667 required=True)
668
669 parser.add_argument("-s", "--suites-dir",
670 dest="suites_dir",
671 help="Suites dir",
672 metavar="SUITES",
673 required=True)
674
675 parser.add_argument("--help-file",
676 dest="help_file",
677 help="Help file",
678 metavar="HELPER",
679 required=True)
680
681 parser.add_argument("-p", "--platform-file",
682 dest="platform_file",
683 help="Platform code file",
684 metavar="PLATFORM_FILE",
685 required=True)
686
687 parser.add_argument("-o", "--out-dir",
688 dest="out_dir",
689 help="Dir where generated code and scripts are copied",
690 metavar="OUT_DIR",
691 required=True)
692
693 args = parser.parse_args()
694
695 data_file_name = os.path.basename(args.data_file)
696 data_name = os.path.splitext(data_file_name)[0]
697
698 out_c_file = os.path.join(args.out_dir, data_name + '.c')
699 out_data_file = os.path.join(args.out_dir, data_file_name)
700
701 out_c_file_dir = os.path.dirname(out_c_file)
702 out_data_file_dir = os.path.dirname(out_data_file)
703 for d in [out_c_file_dir, out_data_file_dir]:
704 if not os.path.exists(d):
705 os.makedirs(d)
706
Azim Khan1de892b2017-06-09 15:02:36 +0100707 generate_code(args.funcs_file, args.data_file, args.template_file, args.platform_file,
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100708 args.help_file, args.suites_dir, out_c_file, out_data_file)
709
710
711if __name__ == "__main__":
712 check_cmd()