blob: c6fc03f53bf78a06f56b35db704ef48147d270ec [file] [log] [blame]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001"""
2mbed SDK
3Copyright (c) 2017-2018 ARM Limited
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16"""
17
18import os
19import re
20import argparse
21import shutil
22
23
24"""
25Generates code in following structure.
26
27<output dir>/
28|-- host_tests/
29| |-- mbedtls_test.py
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010030| |-- mbedtls/
31| | |-- <test suite #1>/
32| | | |-- main.c
Azim Khan1de892b2017-06-09 15:02:36 +010033| | | |-- *.data files
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010034| | ...
35| | |-- <test suite #n>/
36| | | |-- main.c
Azim Khan1de892b2017-06-09 15:02:36 +010037| | | |-- *.data files
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010038| | |
39"""
40
41
42BEGIN_HEADER_REGEX = '/\*\s*BEGIN_HEADER\s*\*/'
43END_HEADER_REGEX = '/\*\s*END_HEADER\s*\*/'
44
45BEGIN_DEP_REGEX = 'BEGIN_DEPENDENCIES'
46END_DEP_REGEX = 'END_DEPENDENCIES'
47
48BEGIN_CASE_REGEX = '/\*\s*BEGIN_CASE\s*(.*?)\s*\*/'
49END_CASE_REGEX = '/\*\s*END_CASE\s*\*/'
50
51
52class InvalidFileFormat(Exception):
53 """
54 Exception to indicate invalid file format.
55 """
56 pass
57
58
59def gen_deps(deps):
60 """
61 Generates dependency i.e. if def and endif code
62
63 :param deps:
64 :return:
65 """
66 dep_start = ''
67 dep_end = ''
68 for dep in deps:
69 if dep[0] == '!':
70 noT = '!'
71 dep = dep[1:]
72 else:
73 noT = ''
74 dep_start += '#if %sdefined(%s)\n' % (noT, dep)
75 dep_end = '#endif /* %s%s */\n' % (noT, dep) + dep_end
76 return dep_start, dep_end
77
78
79def gen_deps_one_line(deps):
80 """
81 Generates dependency checks in one line. Useful for writing code in #else case.
82
83 :param deps:
84 :return:
85 """
86 defines = []
87 for dep in deps:
88 if dep[0] == '!':
89 noT = '!'
90 dep = dep[1:]
91 else:
92 noT = ''
93 defines.append('%sdefined(%s)' % (noT, dep))
94 return '#if ' + ' && '.join(defines)
95
96
97def gen_function_wrapper(name, args_dispatch):
98 """
99 Creates test function code
100
101 :param name:
102 :param args_dispatch:
103 :return:
104 """
105 # Then create the wrapper
106 wrapper = '''
107void {name}_wrapper( void ** params )
108{{
109 {unused_params}
Azim Khan2397bba2017-06-09 04:35:03 +0100110{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100111 {name}( {args} );
112}}
Azim Khan2397bba2017-06-09 04:35:03 +0100113'''.format(name=name, unused_params='(void)params;' if len(args_dispatch[1]) == 0 else '',
114 args=', '.join(args_dispatch[1]),
115 locals=args_dispatch[0])
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100116 return wrapper
117
118
119def gen_dispatch(name, deps):
120 """
121 Generates dispatch condition for the functions.
122
123 :param name:
124 :param deps:
125 :return:
126 """
127 if len(deps):
128 ifdef = gen_deps_one_line(deps)
129 dispatch_code = '''
130{ifdef}
131 {name}_wrapper,
132#else
133 NULL,
134#endif
135'''.format(ifdef=ifdef, name=name)
136 else:
137 dispatch_code = '''
138 {name}_wrapper,
139'''.format(name=name)
140
141 return dispatch_code
142
143
144def parse_suite_headers(line_no, funcs_f):
145 """
146 Parses function headers.
147
148 :param line_no:
149 :param funcs_f:
150 :return:
151 """
152 headers = '#line %d "%s"\n' % (line_no + 1, funcs_f.name)
153 for line in funcs_f:
154 line_no += 1
155 if re.search(END_HEADER_REGEX, line):
156 break
157 headers += line
158 else:
159 raise InvalidFileFormat("file: %s - end header pattern [%s] not found!" % (funcs_f.name, END_HEADER_REGEX))
160
161 return line_no, headers
162
163
164def parse_suite_deps(line_no, funcs_f):
165 """
166 Parses function dependencies.
167
168 :param line_no:
169 :param funcs_f:
170 :return:
171 """
172 deps = []
173 for line in funcs_f:
174 line_no += 1
175 m = re.search('depends_on\:(.*)', line.strip())
176 if m:
177 deps += [x.strip() for x in m.group(1).split(':')]
178 if re.search(END_DEP_REGEX, line):
179 break
180 else:
181 raise InvalidFileFormat("file: %s - end dependency pattern [%s] not found!" % (funcs_f.name, END_DEP_REGEX))
182
183 return line_no, deps
184
185
186def parse_function_deps(line):
187 """
188
189 :param line:
190 :return:
191 """
192 deps = []
193 m = re.search(BEGIN_CASE_REGEX, line)
194 dep_str = m.group(1)
195 if len(dep_str):
196 m = re.search('depends_on:(.*)', dep_str)
197 if m:
198 deps = m.group(1).strip().split(':')
199 return deps
200
201
202def parse_function_signature(line):
203 """
204 Parsing function signature
205
206 :param line:
207 :return:
208 """
209 args = []
Azim Khan2397bba2017-06-09 04:35:03 +0100210 locals = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100211 args_dispatch = []
212 m = re.search('\s*void\s+(\w+)\s*\(', line, re.I)
213 if not m:
214 raise ValueError("Test function should return 'void'\n%s" % line)
215 name = m.group(1)
216 line = line[len(m.group(0)):]
217 arg_idx = 0
218 for arg in line[:line.find(')')].split(','):
219 arg = arg.strip()
220 if arg == '':
221 continue
222 if re.search('int\s+.*', arg.strip()):
223 args.append('int')
224 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
225 elif re.search('char\s*\*\s*.*', arg.strip()):
226 args.append('char*')
227 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100228 elif re.search('HexParam_t\s*\*\s*.*', arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100229 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100230 # create a structure
231 locals += """ HexParam_t hex%d = {%s, %s};
232""" % (arg_idx, '(uint8_t *) params[%d]' % arg_idx, '*( (uint32_t *) params[%d] )' % (arg_idx + 1))
233
234 args_dispatch.append('&hex%d' % arg_idx)
235 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100236 else:
237 raise ValueError("Test function arguments can only be 'int' or 'char *'\n%s" % line)
238 arg_idx += 1
239
Azim Khan2397bba2017-06-09 04:35:03 +0100240 return name, args, (locals, args_dispatch)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100241
242
243def parse_function_code(line_no, funcs_f, deps, suite_deps):
244 """
245
246 :param line_no:
247 :param funcs_f:
248 :param deps:
249 :param suite_deps:
250 :return:
251 """
252 code = '#line %d "%s"\n' % (line_no + 1, funcs_f.name)
253 for line in funcs_f:
254 line_no += 1
255 # Check function signature
256 m = re.match('.*?\s+(\w+)\s*\(', line, re.I)
257 if m:
258 # check if we have full signature i.e. split in more lines
259 if not re.match('.*\)', line):
260 for lin in funcs_f:
261 line += lin
262 line_no += 1
263 if re.search('.*?\)', line):
264 break
265 name, args, args_dispatch = parse_function_signature(line)
266 code += line.replace(name, 'test_' + name)
267 name = 'test_' + name
268 break
269 else:
270 raise InvalidFileFormat("file: %s - Test functions not found!" % funcs_f.name)
271
272 for line in funcs_f:
273 line_no += 1
274 if re.search(END_CASE_REGEX, line):
275 break
276 code += line
277 else:
278 raise InvalidFileFormat("file: %s - end case pattern [%s] not found!" % (funcs_f.name, END_CASE_REGEX))
279
280 # Add exit label if not present
281 if code.find('exit:') == -1:
282 s = code.rsplit('}', 1)
283 if len(s) == 2:
284 code = """
285exit:
286 ;;
287}
288""".join(s)
289
290 code += gen_function_wrapper(name, args_dispatch)
291 ifdef, endif = gen_deps(deps)
292 dispatch_code = gen_dispatch(name, suite_deps + deps)
293 return line_no, name, args, ifdef + code + endif, dispatch_code
294
295
296def parse_functions(funcs_f):
297 """
298 Returns functions code pieces
299
300 :param funcs_f:
301 :return:
302 """
303 line_no = 0
304 suite_headers = ''
305 suite_deps = []
306 suite_functions = ''
307 func_info = {}
308 function_idx = 0
309 dispatch_code = ''
310 for line in funcs_f:
311 line_no += 1
312 if re.search(BEGIN_HEADER_REGEX, line):
313 line_no, headers = parse_suite_headers(line_no, funcs_f)
314 suite_headers += headers
315 elif re.search(BEGIN_DEP_REGEX, line):
316 line_no, deps = parse_suite_deps(line_no, funcs_f)
317 suite_deps += deps
318 elif re.search(BEGIN_CASE_REGEX, line):
319 deps = parse_function_deps(line)
320 line_no, func_name, args, func_code, func_dispatch = parse_function_code(line_no, funcs_f, deps, suite_deps)
321 suite_functions += func_code
322 # Generate dispatch code and enumeration info
323 assert func_name not in func_info, "file: %s - function %s re-declared at line %d" % \
324 (funcs_f.name, func_name, line_no)
325 func_info[func_name] = (function_idx, args)
326 dispatch_code += '/* Function Id: %d */\n' % function_idx
327 dispatch_code += func_dispatch
328 function_idx += 1
329
330 ifdef, endif = gen_deps(suite_deps)
Azim Khan13c6bfb2017-06-15 14:45:56 +0100331 func_code = ifdef + suite_headers + suite_functions + endif
332 return suite_deps, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100333
334
335def escaped_split(str, ch):
336 """
337 Split str on character ch but ignore escaped \{ch}
338
339 :param str:
340 :param ch:
341 :return:
342 """
343 if len(ch) > 1:
344 raise ValueError('Expected split character. Found string!')
345 out = []
346 part = ''
347 escape = False
348 for i in range(len(str)):
349 if not escape and str[i] == ch:
350 out.append(part)
351 part = ''
352 else:
353 part += str[i]
354 escape = not escape and str[i] == '\\'
355 if len(part):
356 out.append(part)
357 return out
358
359
360def parse_test_data(data_f):
361 """
362 Parses .data file
363
364 :param data_f:
365 :return:
366 """
367 STATE_READ_NAME = 0
368 STATE_READ_ARGS = 1
369 state = STATE_READ_NAME
370 deps = []
371
372 for line in data_f:
373 line = line.strip()
374 if len(line) and line[0] == '#': # Skip comments
375 continue
376
377 # skip blank lines
378 if len(line) == 0:
379 continue
380
381 if state == STATE_READ_NAME:
382 # Read test name
383 name = line
384 state = STATE_READ_ARGS
385 elif state == STATE_READ_ARGS:
386 # Check dependencies
387 m = re.search('depends_on\:(.*)', line)
388 if m:
389 deps = m.group(1).split(':')
390 else:
391 # Read test vectors
392 parts = escaped_split(line, ':')
393 function = parts[0]
394 args = parts[1:]
395 yield name, function, deps, args
396 deps = []
397 state = STATE_READ_NAME
398
399
400def gen_dep_check(dep_id, dep):
401 """
402 Generate code for the dependency.
403
404 :param dep_id:
405 :param dep:
406 :return:
407 """
408 if dep[0] == '!':
409 noT = '!'
410 dep = dep[1:]
411 else:
412 noT = ''
413 dep_check = '''
414if ( dep_id == {id} )
415{{
416#if {noT}defined({macro})
417 return( DEPENDENCY_SUPPORTED );
418#else
419 return( DEPENDENCY_NOT_SUPPORTED );
420#endif
421}}
422else
423'''.format(noT=noT, macro=dep, id=dep_id)
424
425 return dep_check
426
427
428def gen_expression_check(exp_id, exp):
429 """
430 Generates code for expression check
431
432 :param exp_id:
433 :param exp:
434 :return:
435 """
436 exp_code = '''
437if ( exp_id == {exp_id} )
438{{
439 *out_value = {expression};
440}}
441else
442'''.format(exp_id=exp_id, expression=exp)
443 return exp_code
444
445
Azim Khan13c6bfb2017-06-15 14:45:56 +0100446def gen_from_test_data(data_f, out_data_f, func_info, suite_deps):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100447 """
448 Generates dependency checks, expression code and intermediate data file from test data file.
449
450 :param data_f:
451 :param out_data_f:
452 :param func_info:
Azim Khan13c6bfb2017-06-15 14:45:56 +0100453 :param suite_deps:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100454 :return:
455 """
456 unique_deps = []
457 unique_expressions = []
458 dep_check_code = ''
459 expression_code = ''
460 for test_name, function_name, test_deps, test_args in parse_test_data(data_f):
461 out_data_f.write(test_name + '\n')
462
463 func_id, func_args = func_info['test_' + function_name]
464 if len(test_deps):
465 out_data_f.write('depends_on')
466 for dep in test_deps:
467 if dep not in unique_deps:
468 unique_deps.append(dep)
469 dep_id = unique_deps.index(dep)
470 dep_check_code += gen_dep_check(dep_id, dep)
471 else:
472 dep_id = unique_deps.index(dep)
473 out_data_f.write(':' + str(dep_id))
474 out_data_f.write('\n')
475
476 assert len(test_args) == len(func_args), \
477 "Invalid number of arguments in test %s. See function %s signature." % (test_name, function_name)
478 out_data_f.write(str(func_id))
479 for i in xrange(len(test_args)):
480 typ = func_args[i]
481 val = test_args[i]
482
483 # check if val is a non literal int val
484 if typ == 'int' and not re.match('\d+', val): # its an expression # FIXME: Handle hex format. Tip: instead try converting int(str, 10) and int(str, 16)
485 typ = 'exp'
486 if val not in unique_expressions:
487 unique_expressions.append(val)
488 # exp_id can be derived from len(). But for readability and consistency with case of existing let's
489 # use index().
490 exp_id = unique_expressions.index(val)
491 expression_code += gen_expression_check(exp_id, val)
492 val = exp_id
493 else:
494 val = unique_expressions.index(val)
495 out_data_f.write(':' + typ + ':' + str(val))
496 out_data_f.write('\n\n')
497
498 # void unused params
499 if len(dep_check_code) == 0:
500 dep_check_code = '(void) dep_id;\n'
501 if len(expression_code) == 0:
502 expression_code = '(void) exp_id;\n'
503 expression_code += '(void) out_value;\n'
Azim Khan13c6bfb2017-06-15 14:45:56 +0100504 ifdef = gen_deps_one_line(suite_deps)
505 if len(suite_deps):
506 dep_check_code = '''
507{ifdef}
508{code}
509#else
510(void) dep_id;
511#endif
512'''.format(ifdef=ifdef, code=dep_check_code)
513 expression_code = '''
514{ifdef}
515{code}
516#else
517(void) exp_id;
518(void) out_value;
519#endif
520'''.format(ifdef=ifdef, code=expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100521 return dep_check_code, expression_code
522
523
Azim Khan1de892b2017-06-09 15:02:36 +0100524def 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 +0100525 """
526 Generate mbed-os test code.
527
528 :param funcs_file:
529 :param dat a_file:
530 :param template_file:
531 :param platform_file:
532 :param help_file:
533 :param suites_dir:
534 :param c_file:
535 :param out_data_file:
536 :return:
537 """
538 for name, path in [('Functions file', funcs_file),
539 ('Data file', data_file),
540 ('Template file', template_file),
541 ('Platform file', platform_file),
542 ('Help code file', help_file),
543 ('Suites dir', suites_dir)]:
544 if not os.path.exists(path):
545 raise IOError("ERROR: %s [%s] not found!" % (name, path))
546
547 snippets = {'generator_script' : os.path.basename(__file__)}
548
549 # Read helpers
550 with open(help_file, 'r') as help_f, open(platform_file, 'r') as platform_f:
551 snippets['test_common_helper_file'] = help_file
552 snippets['test_common_helpers'] = help_f.read()
553 snippets['test_platform_file'] = platform_file
554 snippets['platform_code'] = platform_f.read().replace('DATA_FILE',
555 out_data_file.replace('\\', '\\\\')) # escape '\'
556
557 # Function code
558 with open(funcs_file, 'r') 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 +0100559 suite_deps, dispatch_code, func_code, func_info = parse_functions(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100560 snippets['functions_code'] = func_code
561 snippets['dispatch_code'] = dispatch_code
Azim Khan13c6bfb2017-06-15 14:45:56 +0100562 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 +0100563 snippets['dep_check_code'] = dep_check_code
564 snippets['expression_code'] = expression_code
565
566 snippets['test_file'] = c_file
567 snippets['test_main_file'] = template_file
568 snippets['test_case_file'] = funcs_file
569 snippets['test_case_data_file'] = data_file
570 # Read Template
571 # Add functions
572 #
573 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
574 line_no = 1
575 for line in template_f.readlines():
576 snippets['line_no'] = line_no + 1 # Increment as it sets next line number
577 code = line.format(**snippets)
578 c_f.write(code)
579 line_no += 1
580
581
582def check_cmd():
583 """
584 Command line parser.
585
586 :return:
587 """
588 parser = argparse.ArgumentParser(description='Generate code for mbed-os tests.')
589
590 parser.add_argument("-f", "--functions-file",
591 dest="funcs_file",
592 help="Functions file",
593 metavar="FUNCTIONS",
594 required=True)
595
596 parser.add_argument("-d", "--data-file",
597 dest="data_file",
598 help="Data file",
599 metavar="DATA",
600 required=True)
601
602 parser.add_argument("-t", "--template-file",
603 dest="template_file",
604 help="Template file",
605 metavar="TEMPLATE",
606 required=True)
607
608 parser.add_argument("-s", "--suites-dir",
609 dest="suites_dir",
610 help="Suites dir",
611 metavar="SUITES",
612 required=True)
613
614 parser.add_argument("--help-file",
615 dest="help_file",
616 help="Help file",
617 metavar="HELPER",
618 required=True)
619
620 parser.add_argument("-p", "--platform-file",
621 dest="platform_file",
622 help="Platform code file",
623 metavar="PLATFORM_FILE",
624 required=True)
625
626 parser.add_argument("-o", "--out-dir",
627 dest="out_dir",
628 help="Dir where generated code and scripts are copied",
629 metavar="OUT_DIR",
630 required=True)
631
632 args = parser.parse_args()
633
634 data_file_name = os.path.basename(args.data_file)
635 data_name = os.path.splitext(data_file_name)[0]
636
637 out_c_file = os.path.join(args.out_dir, data_name + '.c')
638 out_data_file = os.path.join(args.out_dir, data_file_name)
639
640 out_c_file_dir = os.path.dirname(out_c_file)
641 out_data_file_dir = os.path.dirname(out_data_file)
642 for d in [out_c_file_dir, out_data_file_dir]:
643 if not os.path.exists(d):
644 os.makedirs(d)
645
Azim Khan1de892b2017-06-09 15:02:36 +0100646 generate_code(args.funcs_file, args.data_file, args.template_file, args.platform_file,
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100647 args.help_file, args.suites_dir, out_c_file, out_data_file)
648
649
650if __name__ == "__main__":
651 check_cmd()