diff --git a/tests/Makefile b/tests/Makefile index a21a0b9a9..f0da1cf24 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -2,7 +2,7 @@ # To compile on SunOS: add "-lsocket -lnsl" to LDFLAGS # To compile with PKCS11: add "-lpkcs11-helper" to LDFLAGS -CFLAGS ?= -O2 +CFLAGS ?= -g3 #-O2 WARNING_CFLAGS ?= -Wall -W -Wdeclaration-after-statement -Wno-unused-function -Wno-unused-value LDFLAGS ?= @@ -184,9 +184,16 @@ $(DEP): C_FILES := $(addsuffix .c,$(APPS)) .SECONDEXPANSION: -$(C_FILES): %.c: suites/$$(func.$$*).function suites/%.data scripts/generate_code.pl suites/helpers.function suites/main_test.function +$(C_FILES): %.c: suites/$$(func.$$*).function suites/%.data scripts/gen_mbed_code.py suites/helpers.function suites/mbed_test.function suites/desktop_test.function echo " Gen $@" - perl scripts/generate_code.pl suites $(func.$*) $* +# perl scripts/generate_code.pl suites $(func.$*) $* + python scripts/gen_mbed_code.py -f suites/$(func.$*).function \ + -d suites/$*.data \ + -t suites/mbed_test.function \ + -p suites/desktop_test.function \ + -s suites \ + --help-file suites/helpers.function \ + -o . $(BINARIES): %$(EXEXT): %.c $(DEP) @@ -199,9 +206,9 @@ all: $(BINARIES) clean: ifndef WINDOWS - rm -rf $(APPS) *.c TESTS + rm -rf $(APPS) *.c *.data TESTS else - del /Q /F *.c *.exe + del /Q /F *.c *.exe *.data rmdir /Q /S TESTS endif @@ -221,14 +228,15 @@ MBED_APPS := $(addprefix mbed_,$(APPS)) # name is used as the test group dir. .SECONDEXPANSION: -$(MBED_APPS): mbed_%: suites/$$(func.$$*).function suites/%.data scripts/gen_mbed_code.py suites/helpers.function suites/main_test.function - echo " Gen ./TESTS/mbedtls/$*/main.c" +$(MBED_APPS): mbed_%: suites/$$(func.$$*).function suites/%.data scripts/gen_mbed_code.py suites/helpers.function suites/mbed_test.function suites/embedded_test.function + echo " Gen ./TESTS/mbedtls/$*/$*.c" python scripts/gen_mbed_code.py -f suites/$(func.$*).function \ -d suites/$*.data \ -t suites/mbed_test.function \ + -p suites/embedded_test.function \ -s suites \ --help-file suites/helpers.function \ - -o ./TESTS + -o ./TESTS/mbedtls/$* gen-mbed-test: $(MBED_APPS) diff --git a/tests/scripts/gen_mbed_code.py b/tests/scripts/gen_mbed_code.py new file mode 100644 index 000000000..9fd9a0045 --- /dev/null +++ b/tests/scripts/gen_mbed_code.py @@ -0,0 +1,623 @@ +""" +mbed SDK +Copyright (c) 2017-2018 ARM Limited + +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. +""" + +import os +import re +import argparse +import shutil + + +""" +Generates code in following structure. + +/ +|-- host_tests/ +| |-- mbedtls_test.py +| |-- suites/ +| | |-- *.data files +| |-- mbedtls/ +| | |-- / +| | | |-- main.c +| | ... +| | |-- / +| | | |-- main.c +| | | +""" + + +BEGIN_HEADER_REGEX = '/\*\s*BEGIN_HEADER\s*\*/' +END_HEADER_REGEX = '/\*\s*END_HEADER\s*\*/' + +BEGIN_DEP_REGEX = 'BEGIN_DEPENDENCIES' +END_DEP_REGEX = 'END_DEPENDENCIES' + +BEGIN_CASE_REGEX = '/\*\s*BEGIN_CASE\s*(.*?)\s*\*/' +END_CASE_REGEX = '/\*\s*END_CASE\s*\*/' + + +class InvalidFileFormat(Exception): + """ + Exception to indicate invalid file format. + """ + pass + + +def gen_deps(deps): + """ + Generates dependency i.e. if def and endif code + + :param deps: + :return: + """ + dep_start = '' + dep_end = '' + for dep in deps: + if dep[0] == '!': + noT = '!' + dep = dep[1:] + else: + noT = '' + dep_start += '#if %sdefined(%s)\n' % (noT, dep) + dep_end = '#endif /* %s%s */\n' % (noT, dep) + dep_end + return dep_start, dep_end + + +def gen_deps_one_line(deps): + """ + Generates dependency checks in one line. Useful for writing code in #else case. + + :param deps: + :return: + """ + defines = [] + for dep in deps: + if dep[0] == '!': + noT = '!' + dep = dep[1:] + else: + noT = '' + defines.append('%sdefined(%s)' % (noT, dep)) + return '#if ' + ' && '.join(defines) + + +def gen_function_wrapper(name, args_dispatch): + """ + Creates test function code + + :param name: + :param args_dispatch: + :return: + """ + # Then create the wrapper + wrapper = ''' +void {name}_wrapper( void ** params ) +{{ + {unused_params} + {name}( {args} ); +}} +'''.format(name=name, unused_params='(void)params;' if len(args_dispatch) == 0 else '', args=', '.join(args_dispatch)) + return wrapper + + +def gen_dispatch(name, deps): + """ + Generates dispatch condition for the functions. + + :param name: + :param deps: + :return: + """ + if len(deps): + ifdef = gen_deps_one_line(deps) + dispatch_code = ''' +{ifdef} + {name}_wrapper, +#else + NULL, +#endif +'''.format(ifdef=ifdef, name=name) + else: + dispatch_code = ''' + {name}_wrapper, +'''.format(name=name) + + return dispatch_code + + +def parse_suite_headers(line_no, funcs_f): + """ + Parses function headers. + + :param line_no: + :param funcs_f: + :return: + """ + headers = '#line %d "%s"\n' % (line_no + 1, funcs_f.name) + for line in funcs_f: + line_no += 1 + if re.search(END_HEADER_REGEX, line): + break + headers += line + else: + raise InvalidFileFormat("file: %s - end header pattern [%s] not found!" % (funcs_f.name, END_HEADER_REGEX)) + + return line_no, headers + + +def parse_suite_deps(line_no, funcs_f): + """ + Parses function dependencies. + + :param line_no: + :param funcs_f: + :return: + """ + deps = [] + for line in funcs_f: + line_no += 1 + m = re.search('depends_on\:(.*)', line.strip()) + if m: + deps += [x.strip() for x in m.group(1).split(':')] + if re.search(END_DEP_REGEX, line): + break + else: + raise InvalidFileFormat("file: %s - end dependency pattern [%s] not found!" % (funcs_f.name, END_DEP_REGEX)) + + return line_no, deps + + +def parse_function_deps(line): + """ + + :param line: + :return: + """ + deps = [] + m = re.search(BEGIN_CASE_REGEX, line) + dep_str = m.group(1) + if len(dep_str): + m = re.search('depends_on:(.*)', dep_str) + if m: + deps = m.group(1).strip().split(':') + return deps + + +def parse_function_signature(line): + """ + Parsing function signature + + :param line: + :return: + """ + args = [] + args_dispatch = [] + m = re.search('\s*void\s+(\w+)\s*\(', line, re.I) + if not m: + raise ValueError("Test function should return 'void'\n%s" % line) + name = m.group(1) + line = line[len(m.group(0)):] + arg_idx = 0 + for arg in line[:line.find(')')].split(','): + arg = arg.strip() + if arg == '': + continue + if re.search('int\s+.*', arg.strip()): + args.append('int') + args_dispatch.append('*( (int *) params[%d] )' % arg_idx) + elif re.search('char\s*\*\s*.*', arg.strip()): + args.append('char*') + args_dispatch.append('(char *) params[%d]' % arg_idx) + else: + raise ValueError("Test function arguments can only be 'int' or 'char *'\n%s" % line) + arg_idx += 1 + + return name, args, args_dispatch + + +def parse_function_code(line_no, funcs_f, deps, suite_deps): + """ + + :param line_no: + :param funcs_f: + :param deps: + :param suite_deps: + :return: + """ + code = '#line %d "%s"\n' % (line_no + 1, funcs_f.name) + for line in funcs_f: + line_no += 1 + # Check function signature + m = re.match('.*?\s+(\w+)\s*\(', line, re.I) + if m: + # check if we have full signature i.e. split in more lines + if not re.match('.*\)', line): + for lin in funcs_f: + line += lin + line_no += 1 + if re.search('.*?\)', line): + break + name, args, args_dispatch = parse_function_signature(line) + code += line.replace(name, 'test_' + name) + name = 'test_' + name + break + else: + raise InvalidFileFormat("file: %s - Test functions not found!" % funcs_f.name) + + for line in funcs_f: + line_no += 1 + if re.search(END_CASE_REGEX, line): + break + code += line + else: + raise InvalidFileFormat("file: %s - end case pattern [%s] not found!" % (funcs_f.name, END_CASE_REGEX)) + + # Add exit label if not present + if code.find('exit:') == -1: + s = code.rsplit('}', 1) + if len(s) == 2: + code = """ +exit: + ;; +} +""".join(s) + + code += gen_function_wrapper(name, args_dispatch) + ifdef, endif = gen_deps(deps) + dispatch_code = gen_dispatch(name, suite_deps + deps) + return line_no, name, args, ifdef + code + endif, dispatch_code + + +def parse_functions(funcs_f): + """ + Returns functions code pieces + + :param funcs_f: + :return: + """ + line_no = 0 + suite_headers = '' + suite_deps = [] + suite_functions = '' + func_info = {} + function_idx = 0 + dispatch_code = '' + for line in funcs_f: + line_no += 1 + if re.search(BEGIN_HEADER_REGEX, line): + line_no, headers = parse_suite_headers(line_no, funcs_f) + suite_headers += headers + elif re.search(BEGIN_DEP_REGEX, line): + line_no, deps = parse_suite_deps(line_no, funcs_f) + suite_deps += deps + elif re.search(BEGIN_CASE_REGEX, line): + deps = parse_function_deps(line) + line_no, func_name, args, func_code, func_dispatch = parse_function_code(line_no, funcs_f, deps, suite_deps) + suite_functions += func_code + # Generate dispatch code and enumeration info + assert func_name not in func_info, "file: %s - function %s re-declared at line %d" % \ + (funcs_f.name, func_name, line_no) + func_info[func_name] = (function_idx, args) + dispatch_code += '/* Function Id: %d */\n' % function_idx + dispatch_code += func_dispatch + function_idx += 1 + + ifdef, endif = gen_deps(suite_deps) + func_code = ifdef + suite_functions + endif + return dispatch_code, suite_headers, func_code, func_info + + +def escaped_split(str, ch): + """ + Split str on character ch but ignore escaped \{ch} + + :param str: + :param ch: + :return: + """ + if len(ch) > 1: + raise ValueError('Expected split character. Found string!') + out = [] + part = '' + escape = False + for i in range(len(str)): + if not escape and str[i] == ch: + out.append(part) + part = '' + else: + part += str[i] + escape = not escape and str[i] == '\\' + if len(part): + out.append(part) + return out + + +def parse_test_data(data_f): + """ + Parses .data file + + :param data_f: + :return: + """ + STATE_READ_NAME = 0 + STATE_READ_ARGS = 1 + state = STATE_READ_NAME + deps = [] + + for line in data_f: + line = line.strip() + if len(line) and line[0] == '#': # Skip comments + continue + + # skip blank lines + if len(line) == 0: + continue + + if state == STATE_READ_NAME: + # Read test name + name = line + state = STATE_READ_ARGS + elif state == STATE_READ_ARGS: + # Check dependencies + m = re.search('depends_on\:(.*)', line) + if m: + deps = m.group(1).split(':') + else: + # Read test vectors + parts = escaped_split(line, ':') + function = parts[0] + args = parts[1:] + yield name, function, deps, args + deps = [] + state = STATE_READ_NAME + + +def gen_dep_check(dep_id, dep): + """ + Generate code for the dependency. + + :param dep_id: + :param dep: + :return: + """ + if dep[0] == '!': + noT = '!' + dep = dep[1:] + else: + noT = '' + dep_check = ''' +if ( dep_id == {id} ) +{{ +#if {noT}defined({macro}) + return( DEPENDENCY_SUPPORTED ); +#else + return( DEPENDENCY_NOT_SUPPORTED ); +#endif +}} +else +'''.format(noT=noT, macro=dep, id=dep_id) + + return dep_check + + +def gen_expression_check(exp_id, exp): + """ + Generates code for expression check + + :param exp_id: + :param exp: + :return: + """ + exp_code = ''' +if ( exp_id == {exp_id} ) +{{ + *out_value = {expression}; +}} +else +'''.format(exp_id=exp_id, expression=exp) + return exp_code + + +def gen_from_test_data(data_f, out_data_f, func_info): + """ + Generates dependency checks, expression code and intermediate data file from test data file. + + :param data_f: + :param out_data_f: + :param func_info: + :return: + """ + unique_deps = [] + unique_expressions = [] + dep_check_code = '' + expression_code = '' + for test_name, function_name, test_deps, test_args in parse_test_data(data_f): + out_data_f.write(test_name + '\n') + + func_id, func_args = func_info['test_' + function_name] + if len(test_deps): + out_data_f.write('depends_on') + for dep in test_deps: + if dep not in unique_deps: + unique_deps.append(dep) + dep_id = unique_deps.index(dep) + dep_check_code += gen_dep_check(dep_id, dep) + else: + dep_id = unique_deps.index(dep) + out_data_f.write(':' + str(dep_id)) + out_data_f.write('\n') + + assert len(test_args) == len(func_args), \ + "Invalid number of arguments in test %s. See function %s signature." % (test_name, function_name) + out_data_f.write(str(func_id)) + for i in xrange(len(test_args)): + typ = func_args[i] + val = test_args[i] + + # check if val is a non literal int val + 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) + typ = 'exp' + if val not in unique_expressions: + unique_expressions.append(val) + # exp_id can be derived from len(). But for readability and consistency with case of existing let's + # use index(). + exp_id = unique_expressions.index(val) + expression_code += gen_expression_check(exp_id, val) + val = exp_id + else: + val = unique_expressions.index(val) + out_data_f.write(':' + typ + ':' + str(val)) + out_data_f.write('\n\n') + + # void unused params + if len(dep_check_code) == 0: + dep_check_code = '(void) dep_id;\n' + if len(expression_code) == 0: + expression_code = '(void) exp_id;\n' + expression_code += '(void) out_value;\n' + + return dep_check_code, expression_code + + +def gen_mbed_code(funcs_file, data_file, template_file, platform_file, help_file, suites_dir, c_file, out_data_file): + """ + Generate mbed-os test code. + + :param funcs_file: + :param dat a_file: + :param template_file: + :param platform_file: + :param help_file: + :param suites_dir: + :param c_file: + :param out_data_file: + :return: + """ + for name, path in [('Functions file', funcs_file), + ('Data file', data_file), + ('Template file', template_file), + ('Platform file', platform_file), + ('Help code file', help_file), + ('Suites dir', suites_dir)]: + if not os.path.exists(path): + raise IOError("ERROR: %s [%s] not found!" % (name, path)) + + snippets = {'generator_script' : os.path.basename(__file__)} + + # Read helpers + with open(help_file, 'r') as help_f, open(platform_file, 'r') as platform_f: + snippets['test_common_helper_file'] = help_file + snippets['test_common_helpers'] = help_f.read() + snippets['test_platform_file'] = platform_file + snippets['platform_code'] = platform_f.read().replace('DATA_FILE', + out_data_file.replace('\\', '\\\\')) # escape '\' + + # Function code + with open(funcs_file, 'r') as funcs_f, open(data_file, 'r') as data_f, open(out_data_file, 'w') as out_data_f: + dispatch_code, func_headers, func_code, func_info = parse_functions(funcs_f) + snippets['function_headers'] = func_headers + snippets['functions_code'] = func_code + snippets['dispatch_code'] = dispatch_code + dep_check_code, expression_code = gen_from_test_data(data_f, out_data_f, func_info) + snippets['dep_check_code'] = dep_check_code + snippets['expression_code'] = expression_code + + snippets['test_file'] = c_file + snippets['test_main_file'] = template_file + snippets['test_case_file'] = funcs_file + snippets['test_case_data_file'] = data_file + # Read Template + # Add functions + # + with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f: + line_no = 1 + for line in template_f.readlines(): + snippets['line_no'] = line_no + 1 # Increment as it sets next line number + code = line.format(**snippets) + c_f.write(code) + line_no += 1 + + +def check_cmd(): + """ + Command line parser. + + :return: + """ + parser = argparse.ArgumentParser(description='Generate code for mbed-os tests.') + + parser.add_argument("-f", "--functions-file", + dest="funcs_file", + help="Functions file", + metavar="FUNCTIONS", + required=True) + + parser.add_argument("-d", "--data-file", + dest="data_file", + help="Data file", + metavar="DATA", + required=True) + + parser.add_argument("-t", "--template-file", + dest="template_file", + help="Template file", + metavar="TEMPLATE", + required=True) + + parser.add_argument("-s", "--suites-dir", + dest="suites_dir", + help="Suites dir", + metavar="SUITES", + required=True) + + parser.add_argument("--help-file", + dest="help_file", + help="Help file", + metavar="HELPER", + required=True) + + parser.add_argument("-p", "--platform-file", + dest="platform_file", + help="Platform code file", + metavar="PLATFORM_FILE", + required=True) + + parser.add_argument("-o", "--out-dir", + dest="out_dir", + help="Dir where generated code and scripts are copied", + metavar="OUT_DIR", + required=True) + + args = parser.parse_args() + + data_file_name = os.path.basename(args.data_file) + data_name = os.path.splitext(data_file_name)[0] + + out_c_file = os.path.join(args.out_dir, data_name + '.c') + out_data_file = os.path.join(args.out_dir, data_file_name) + + out_c_file_dir = os.path.dirname(out_c_file) + out_data_file_dir = os.path.dirname(out_data_file) + for d in [out_c_file_dir, out_data_file_dir]: + if not os.path.exists(d): + os.makedirs(d) + + gen_mbed_code(args.funcs_file, args.data_file, args.template_file, args.platform_file, + args.help_file, args.suites_dir, out_c_file, out_data_file) + + +if __name__ == "__main__": + check_cmd() diff --git a/tests/suites/desktop_test.function b/tests/suites/desktop_test.function new file mode 100644 index 000000000..6e7fd075c --- /dev/null +++ b/tests/suites/desktop_test.function @@ -0,0 +1,644 @@ +#line 2 "suites/desktop_test.function" + +/** + * \brief Varifies that string is in string parameter format i.e. "" + * It also strips enclosing '"' from the input string. + * + * \param str String parameter. + * + * \return 0 if success else 1 + */ +int verify_string( char **str ) +{ + if( (*str)[0] != '"' || + (*str)[strlen( *str ) - 1] != '"' ) + { + mbedtls_fprintf( stderr, + "Expected string (with \"\") for parameter and got: %s\n", *str ); + return( -1 ); + } + + (*str)++; + (*str)[strlen( *str ) - 1] = '\0'; + + return( 0 ); +} + +/** + * \brief Varifies that string is an integer. Also gives the converted + * integer value. + * + * \param str Input string. + * \param value Pointer to int for output value. + * + * \return 0 if success else 1 + */ +int verify_int( char *str, int *value ) +{ + size_t i; + int minus = 0; + int digits = 1; + int hex = 0; + + for( i = 0; i < strlen( str ); i++ ) + { + if( i == 0 && str[i] == '-' ) + { + minus = 1; + continue; + } + + if( ( ( minus && i == 2 ) || ( !minus && i == 1 ) ) && + str[i - 1] == '0' && str[i] == 'x' ) + { + hex = 1; + continue; + } + + if( ! ( ( str[i] >= '0' && str[i] <= '9' ) || + ( hex && ( ( str[i] >= 'a' && str[i] <= 'f' ) || + ( str[i] >= 'A' && str[i] <= 'F' ) ) ) ) ) + { + digits = 0; + break; + } + } + + if( digits ) + { + if( hex ) + *value = strtol( str, NULL, 16 ); + else + *value = strtol( str, NULL, 10 ); + + return( 0 ); + } + + mbedtls_fprintf( stderr, + "Expected integer for parameter and got: %s\n", str ); + return( KEY_VALUE_MAPPING_NOT_FOUND ); +} + + +/** + * \brief Usage string. + * + */ +#define USAGE \ + "Usage: %s [OPTIONS] files...\n\n" \ + " Command line arguments:\n" \ + " files... One or more test data file. If no file is specified\n" \ + " the followimg default test case is used:\n" \ + " %s\n\n" \ + " Options:\n" \ + " -v | --verbose Display full information about each test\n" \ + " -h | --help Display this information\n\n", \ + argv[0], \ + "TESTCASE_FILENAME" + + +/** + * \brief Read a line from the passed file pointer. + * + * \param f FILE pointer + * \param buf Pointer to memory to hold read line. + * \param len Length of the buf. + * + * \return 0 if success else -1 + */ +int get_line( FILE *f, char *buf, size_t len ) +{ + char *ret; + int i = 0, str_len = 0, has_string = 0; + + /* Read until we get a valid line */ + do + { + ret = fgets( buf, len, f ); + if( ret == NULL ) + return( -1 ); + + str_len = strlen( buf ); + + /* Skip empty line and comment */ + if ( str_len == 0 || buf[0] == '#' ) + continue; + has_string = 0; + for ( i = 0; i < str_len; i++ ) + { + char c = buf[i]; + if ( c != ' ' && c != '\t' && c != '\n' && + c != '\v' && c != '\f' && c != '\r' ) + { + has_string = 1; + break; + } + } + } while( !has_string ); + + /* Strip new line and carriage return */ + ret = buf + strlen( buf ); + if( ret-- > buf && *ret == '\n' ) + *ret = '\0'; + if( ret-- > buf && *ret == '\r' ) + *ret = '\0'; + + return( 0 ); +} + +/** + * \brief Splits string delimited by ':'. Ignores '\:'. + * + * \param buf Input string + * \param len Input string length + * \param params Out params found + * \param params_len Out params array len + * + * \return Count of strings found. + */ +static int parse_arguments( char *buf, size_t len, char **params, + size_t params_len ) +{ + size_t cnt = 0, i; + char *cur = buf; + char *p = buf, *q; + + params[cnt++] = cur; + + while( *p != '\0' && p < buf + len ) + { + if( *p == '\\' ) + { + p++; + p++; + continue; + } + if( *p == ':' ) + { + if( p + 1 < buf + len ) + { + cur = p + 1; + assert( cnt < params_len ); + params[cnt++] = cur; + } + *p = '\0'; + } + + p++; + } + + /* Replace newlines, question marks and colons in strings */ + for( i = 0; i < cnt; i++ ) + { + p = params[i]; + q = params[i]; + + while( *p != '\0' ) + { + if( *p == '\\' && *(p + 1) == 'n' ) + { + p += 2; + *(q++) = '\n'; + } + else if( *p == '\\' && *(p + 1) == ':' ) + { + p += 2; + *(q++) = ':'; + } + else if( *p == '\\' && *(p + 1) == '?' ) + { + p += 2; + *(q++) = '?'; + } + else + *(q++) = *(p++); + } + *q = '\0'; + } + + return( cnt ); +} + +/** + * \brief Converts parameters into test function consumable parameters. + * Example: Input: {"int", "0", "char*", "Hello", + * "hex", "abef", "exp", "1"} + * Output: { + * 0, // Verified int + * "Hello", // Verified string + * 2, { 0xab, 0xef },// Converted len,hex pair + * 9600 // Evaluated expression + * } + * + * + * \param cnt Input string. + * \param params Out array of found strings. + * \param int_params_store Memory for storing processed integer parameters. + * + * \return 0 for success else 1 + */ +static int convert_params( size_t cnt , char ** params , int * int_params_store ) +{ + char ** cur = params; + char ** out = params; + int ret = ( DISPATCH_TEST_SUCCESS ); + + while ( cur - params < (int) cnt ) + { + char * type = *cur++; + char * val = *cur++; + + if ( strcmp( type, "char*" ) == 0 ) + { + if ( verify_string( &val ) == 0 ) + { + *out++ = val; + } + else + { + ret = ( DISPATCH_INVALID_TEST_DATA ); + break; + } + } + else if ( strcmp( type, "int" ) == 0 ) + { + if ( verify_int ( val, int_params_store ) == 0 ) + { + *out++ = (char *) int_params_store++; + } + else + { + ret = ( DISPATCH_INVALID_TEST_DATA ); + break; + } + } + else if ( strcmp( type, "hex" ) == 0 ) + { + *int_params_store = unhexify( (unsigned char *) val, val ); + *out++ = (char *)int_params_store++; + *out++ = val; + } + else if ( strcmp( type, "exp" ) == 0 ) + { + int exp_id = strtol( val, NULL, 10 ); + if ( get_expression ( exp_id, int_params_store ) == 0 ) + { + *out++ = (char *)int_params_store++; + } + else + { + ret = ( DISPATCH_INVALID_TEST_DATA ); + break; + } + } + else + { + ret = ( DISPATCH_INVALID_TEST_DATA ); + break; + } + } + return( ret ); +} + +/** + * \brief Tests snprintf implementation with test input. + * + * \param n Buffer test length. + * \param ref_buf Expected buffer. + * \param ref_ret Expected snprintf return value. + * + * \return 0 for success else 1 + */ +static int test_snprintf( size_t n, const char ref_buf[10], int ref_ret ) +{ + int ret; + char buf[10] = "xxxxxxxxx"; + const char ref[10] = "xxxxxxxxx"; + + ret = mbedtls_snprintf( buf, n, "%s", "123" ); + if( ret < 0 || (size_t) ret >= n ) + ret = -1; + + if( strncmp( ref_buf, buf, sizeof( buf ) ) != 0 || + ref_ret != ret || + memcmp( buf + n, ref + n, sizeof( buf ) - n ) != 0 ) + { + return( 1 ); + } + + return( 0 ); +} + +/** + * \brief Tests snprintf implementation. + * + * \param none + * + * \return 0 for success else 1 + */ +static int run_test_snprintf( void ) +{ + return( test_snprintf( 0, "xxxxxxxxx", -1 ) != 0 || + test_snprintf( 1, "", -1 ) != 0 || + test_snprintf( 2, "1", -1 ) != 0 || + test_snprintf( 3, "12", -1 ) != 0 || + test_snprintf( 4, "123", 3 ) != 0 || + test_snprintf( 5, "123", 3 ) != 0 ); +} + + +/** + * \brief Desktop implementation of execute_tests(). + * Parses command line and executes tests from + * supplied or default data file. + * + * \param argc Command line argument count. + * \param argv Argument array. + * + * \return Program exit status. + */ +int execute_tests( int argc , const char ** argv ) +{ + /* Local Configurations and options */ + const char *default_filename = "DATA_FILE"; + const char *test_filename = NULL; + const char **test_files = NULL; + int testfile_count = 0; + int option_verbose = 0; + + /* Other Local variables */ + int arg_index = 1; + const char *next_arg; + int testfile_index, ret, i, cnt; + int total_errors = 0, total_tests = 0, total_skipped = 0; + FILE *file; + char buf[5000]; + char *params[50]; + int int_params[50]; // Store for proccessed integer params. + void *pointer; +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) + int stdout_fd = -1; +#endif /* __unix__ || __APPLE__ __MACH__ */ + +#if defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C) && \ + !defined(TEST_SUITE_MEMORY_BUFFER_ALLOC) + unsigned char alloc_buf[1000000]; + mbedtls_memory_buffer_alloc_init( alloc_buf, sizeof( alloc_buf ) ); +#endif + + /* + * The C standard doesn't guarantee that all-bits-0 is the representation + * of a NULL pointer. We do however use that in our code for initializing + * structures, which should work on every modern platform. Let's be sure. + */ + memset( &pointer, 0, sizeof( void * ) ); + if( pointer != NULL ) + { + mbedtls_fprintf( stderr, "all-bits-zero is not a NULL pointer\n" ); + return( 1 ); + } + + /* + * Make sure we have a snprintf that correctly zero-terminates + */ + if( run_test_snprintf() != 0 ) + { + mbedtls_fprintf( stderr, "the snprintf implementation is broken\n" ); + return( 0 ); + } + + while( arg_index < argc ) + { + next_arg = argv[ arg_index ]; + + if( strcmp(next_arg, "--verbose" ) == 0 || + strcmp(next_arg, "-v" ) == 0 ) + { + option_verbose = 1; + } + else if( strcmp(next_arg, "--help" ) == 0 || + strcmp(next_arg, "-h" ) == 0 ) + { + mbedtls_fprintf( stdout, USAGE ); + mbedtls_exit( EXIT_SUCCESS ); + } + else + { + /* Not an option, therefore treat all further arguments as the file + * list. + */ + test_files = &argv[ arg_index ]; + testfile_count = argc - arg_index; + } + + arg_index++; + } + + /* If no files were specified, assume a default */ + if ( test_files == NULL || testfile_count == 0 ) + { + test_files = &default_filename; + testfile_count = 1; + } + + /* Initialize the struct that holds information about the last test */ + memset( &test_info, 0, sizeof( test_info ) ); + + /* Now begin to execute the tests in the testfiles */ + for ( testfile_index = 0; + testfile_index < testfile_count; + testfile_index++ ) + { + int unmet_dep_count = 0; + char *unmet_dependencies[20]; + + test_filename = test_files[ testfile_index ]; + + file = fopen( test_filename, "r" ); + if( file == NULL ) + { + mbedtls_fprintf( stderr, "Failed to open test file: %s\n", + test_filename ); + return( 1 ); + } + + while( !feof( file ) ) + { + if( unmet_dep_count > 0 ) + { + mbedtls_fprintf( stderr, + "FATAL: Dep count larger than zero at start of loop\n" ); + mbedtls_exit( MBEDTLS_EXIT_FAILURE ); + } + unmet_dep_count = 0; + + if( ( ret = get_line( file, buf, sizeof(buf) ) ) != 0 ) + break; + mbedtls_fprintf( stdout, "%s%.66s", test_info.failed ? "\n" : "", buf ); + mbedtls_fprintf( stdout, " " ); + for( i = strlen( buf ) + 1; i < 67; i++ ) + mbedtls_fprintf( stdout, "." ); + mbedtls_fprintf( stdout, " " ); + fflush( stdout ); + + total_tests++; + + if( ( ret = get_line( file, buf, sizeof( buf ) ) ) != 0 ) + break; + cnt = parse_arguments( buf, strlen( buf ), params, + sizeof( params ) / sizeof( params[0] ) ); + + if( strcmp( params[0], "depends_on" ) == 0 ) + { + for( i = 1; i < cnt; i++ ) + { + int dep_id = strtol( params[i], NULL, 10 ); + if( dep_check( dep_id ) != DEPENDENCY_SUPPORTED ) + { + if( 0 == option_verbose ) + { + /* Only one count is needed if not verbose */ + unmet_dep_count++; + break; + } + + unmet_dependencies[ unmet_dep_count ] = strdup( params[i] ); + if( unmet_dependencies[ unmet_dep_count ] == NULL ) + { + mbedtls_fprintf( stderr, "FATAL: Out of memory\n" ); + mbedtls_exit( MBEDTLS_EXIT_FAILURE ); + } + unmet_dep_count++; + } + } + + if( ( ret = get_line( file, buf, sizeof( buf ) ) ) != 0 ) + break; + cnt = parse_arguments( buf, strlen( buf ), params, + sizeof( params ) / sizeof( params[0] ) ); + } + + // If there are no unmet dependencies execute the test + if( unmet_dep_count == 0 ) + { + test_info.failed = 0; + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) + /* Suppress all output from the library unless we're verbose + * mode + */ + if( !option_verbose ) + { + stdout_fd = redirect_output( &stdout, "/dev/null" ); + if( stdout_fd == -1 ) + { + /* Redirection has failed with no stdout so exit */ + exit( 1 ); + } + } +#endif /* __unix__ || __APPLE__ __MACH__ */ + + ret = convert_params( cnt - 1, params + 1, int_params ); + if ( DISPATCH_TEST_SUCCESS == ret ) + { + int function_id = strtol( params[0], NULL, 10 ); + ret = dispatch_test( function_id, (void **)( params + 1 ) ); + } + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) + if( !option_verbose && restore_output( &stdout, stdout_fd ) ) + { + /* Redirection has failed with no stdout so exit */ + exit( 1 ); + } +#endif /* __unix__ || __APPLE__ __MACH__ */ + + } + + if( unmet_dep_count > 0 || ret == DISPATCH_UNSUPPORTED_SUITE ) + { + total_skipped++; + mbedtls_fprintf( stdout, "----" ); + + if( 1 == option_verbose && ret == DISPATCH_UNSUPPORTED_SUITE ) + { + mbedtls_fprintf( stdout, "\n Test Suite not enabled" ); + } + + if( 1 == option_verbose && unmet_dep_count > 0 ) + { + mbedtls_fprintf( stdout, "\n Unmet dependencies: " ); + for( i = 0; i < unmet_dep_count; i++ ) + { + mbedtls_fprintf( stdout, "%s ", + unmet_dependencies[i] ); + free( unmet_dependencies[i] ); + } + } + mbedtls_fprintf( stdout, "\n" ); + fflush( stdout ); + + unmet_dep_count = 0; + } + else if( ret == DISPATCH_TEST_SUCCESS ) + { + if( test_info.failed == 0 ) + { + mbedtls_fprintf( stdout, "PASS\n" ); + } + else + { + total_errors++; + mbedtls_fprintf( stdout, "FAILED\n" ); + mbedtls_fprintf( stdout, " %s\n at line %d, %s\n", + test_info.test, test_info.line_no, + test_info.filename ); + } + fflush( stdout ); + } + else if( ret == DISPATCH_INVALID_TEST_DATA ) + { + mbedtls_fprintf( stderr, "FAILED: FATAL PARSE ERROR\n" ); + fclose( file ); + mbedtls_exit( 2 ); + } + else if( ret == DISPATCH_TEST_FN_NOT_FOUND ) + { + mbedtls_fprintf( stderr, "FAILED: FATAL TEST FUNCTION NOT FUND\n" ); + fclose( file ); + mbedtls_exit( 2 ); + } + else + total_errors++; + } + fclose( file ); + + /* In case we encounter early end of file */ + for( i = 0; i < unmet_dep_count; i++ ) + free( unmet_dependencies[i] ); + } + + mbedtls_fprintf( stdout, "\n----------------------------------------------------------------------------\n\n"); + if( total_errors == 0 ) + mbedtls_fprintf( stdout, "PASSED" ); + else + mbedtls_fprintf( stdout, "FAILED" ); + + mbedtls_fprintf( stdout, " (%d / %d tests (%d skipped))\n", + total_tests - total_errors, total_tests, total_skipped ); + +#if defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C) && \ + !defined(TEST_SUITE_MEMORY_BUFFER_ALLOC) +#if defined(MBEDTLS_MEMORY_DEBUG) + mbedtls_memory_buffer_alloc_status(); +#endif + mbedtls_memory_buffer_alloc_free(); +#endif + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) + if( stdout_fd != -1 ) + close_output( stdout ); +#endif /* __unix__ || __APPLE__ __MACH__ */ + + return( total_errors != 0 ); +} diff --git a/tests/suites/embedded_test.function b/tests/suites/embedded_test.function new file mode 100644 index 000000000..21a5caba7 --- /dev/null +++ b/tests/suites/embedded_test.function @@ -0,0 +1,364 @@ +#line 2 "embedded_test.function" + +#include "greentea-client/test_env_c.h" + +/** + * \brief Increments pointer and asserts that it does not overflow. + * + * \param p Pointer to byte array + * \param start Pointer to start of byte array + * \param len Length of byte array + * \param step Increment size + * + */ +#define INCR_ASSERT(p, start, len, step) do \ +{ \ + assert( p >= start ); \ + assert( sizeof( *p ) == sizeof( *start ) ); \ + /* <= is checked to support use inside a loop where \ + pointer is incremented after reading data. */ \ + assert( (uint32_t)( (p - start) + step ) <= len ); \ + p += step; \ +} \ +while( 0 ) + + +/** + * \brief 4 byte align unsigned char pointer + * + * \param p Pointer to byte array + * \param start Pointer to start of byte array + * \param len Length of byte array + * + */ +#define ALIGN_32BIT(p, start, len) do \ +{ \ + uint32_t align = ( - (uintptr_t)p ) % 4; \ + INCR_ASSERT(p, start, len, align); \ +} \ +while( 0 ) + + +/** + * \brief Verify dependencies. Dependency identifiers are + * encoded in the buffer as 8 bit unsigned integers. + * + * \param count Number of dependencies. + * \param dep_p Pointer to buffer. + * + * \return DEPENDENCY_SUPPORTED if success else DEPENDENCY_NOT_SUPPORTED. + */ +int verify_dependencies( uint8_t count, uint8_t * dep_p ) +{ + uint8_t i; + for ( i = 0; i < count; i++ ) + { + if ( dep_check( (int)(dep_p[i]) ) != DEPENDENCY_SUPPORTED ) + return( DEPENDENCY_NOT_SUPPORTED ); + } + return( DEPENDENCY_SUPPORTED ); +} + + +/** + * \brief Receives unsigned integer on serial interface. + * Integers are encoded in network order. + * + * \param none + * + * \return unsigned int + */ +uint32_t receive_uint32() +{ + uint32_t value; + value = (uint8_t)greentea_getc() << 24; + value |= (uint8_t)greentea_getc() << 16; + value |= (uint8_t)greentea_getc() << 8; + value |= (uint8_t)greentea_getc(); + return( (uint32_t)value ); +} + +/** + * \brief Parses out an unsigned 32 int value from the byte array. + * Integers are encoded in network order. + * + * \param p Pointer to byte array + * + * \return unsigned int + */ +uint32_t parse_uint32( uint8_t * p ) +{ + uint32_t value; + value = *p++ << 24; + value |= *p++ << 16; + value |= *p++ << 8; + value |= *p; + return( value ); +} + + +/** + * \brief Receives test data on serial as greentea key,value pair: + * {{;}} + * + * \param data_len Out pointer to hold received data length. + * + * \return Byte array. + */ +uint8_t * receive_data( uint32_t * data_len ) +{ + uint32_t i = 0, errors = 0; + char c; + uint8_t * data = NULL; + + /* Read opening braces */ + i = 0; + while ( i < 2 ) + { + c = greentea_getc(); + /* Ignore any prevous CR LF characters */ + if ( c == '\n' || c == '\r' ) + continue; + i++; + if ( c != '{' ) + return( NULL ); + } + + /* Read data length */ + *data_len = receive_uint32(); + data = (uint8_t *)malloc( *data_len ); + assert( data != NULL ); + + greentea_getc(); // read ';' received after key i.e. *data_len + + for( i = 0; i < *data_len; i++ ) + data[i] = greentea_getc(); + + /* Read closing braces */ + for( i = 0; i < 2; i++ ) + { + c = greentea_getc(); + if ( c != '}' ) + { + errors++; + break; + } + } + + if ( errors ) + { + free( data ); + data = NULL; + *data_len = 0; + } + + return( data ); +} + +/** + * \brief Parses received byte array for test parameters. + * + * \param count Parameter count + * \param data Received Byte array + * \param data_len Byte array length + * \param error Parsing error out variable. + * + * \return Array of parsed parameters allocated on heap. + * Note: Caller has the responsibility to delete + * the memory after use. + */ +void ** parse_parameters( uint8_t count, uint8_t * data, uint32_t data_len, + int * error ) +{ + uint32_t i = 0; + char c; + void ** params = NULL; + void ** cur = NULL; + uint8_t * p = NULL; + + params = (void **)malloc( sizeof( void *) * ( count + 1 ) ); + assert( params != NULL ); + params[count] = NULL; + cur = params; + + p = data; + + /* Parameters */ + for( i = 0; i < count; i++ ) + { + c = (char)*p; + INCR_ASSERT( p, data, data_len, 1 ); + + /* Align p to 4 bytes for int, expression, string len or hex length */ + ALIGN_32BIT( p, data, data_len ); + + /* Network to host conversion */ + *( (int32_t *)p ) = (int32_t)parse_uint32( p ); + + switch( c ) + { + case 'E': + { + if ( get_expression( *( (int32_t *)p ), (int32_t *)p ) ) + { + *error = KEY_VALUE_MAPPING_NOT_FOUND; + goto exit; + } + } /* Intentional fall through */ + case 'I': + { + *cur++ = (void *)p; + INCR_ASSERT( p, data, data_len, sizeof( int32_t ) ); + } + break; + case 'H': + { + *cur++ = (void *)p; + } /* Intentional fall through */ + case 'S': + { + uint32_t sz = *( (int32_t *)p ); + INCR_ASSERT( p, data, data_len, sizeof( int32_t ) ); + *cur++ = (void *)p; + INCR_ASSERT( p, data, data_len, sz ); + } + break; + default: + { + *error = DISPATCH_INVALID_TEST_DATA; + goto exit; + } + break; + } + } + +exit: + if ( *error ) + { + free( params ); + params = NULL; + } + + return( params ); +} + +/** + * \brief Sends greentea key and int value pair to host. + * + * \param key key string + * \param value integer value + * + * \return void + */ +void send_key_integer( char * key, int value ) +{ + char str[50]; + snprintf( str, sizeof( str ), "%d", value ); + greentea_send_kv_c( key, str ); +} + +/** + * \brief Sends test setup failure to the host. + * + * \param failure Test set failure + * + * \return void + */ +void send_failure( int failure ) +{ + send_key_integer( "F", failure ); +} + +/** + * \brief Sends test status to the host. + * + * \param status Test status (PASS=0/FAIL=!0) + * + * \return void + */ +void send_status( int status ) +{ + send_key_integer( "R", status ); +} + + +/** + * \brief Embedded implementation of execute_tests(). + * Ignores command line and received test data + * on serial. + * + * \param argc not used + * \param argv not used + * + * \return Program exit status. + */ +int execute_tests( int args, const char ** argv ) +{ + int ret = 0; + uint32_t data_len = 0; + uint8_t count = 0, function_id; + void ** params = NULL; + uint8_t * data = NULL, * p = NULL; + + GREENTEA_SETUP_C( 180, "mbedtls_test" ); + greentea_send_kv_c( "GO", " " ); + + while ( 1 ) + { + ret = 0; + test_info.failed = 0; + data_len = 0; + + data = receive_data( &data_len ); + if ( data == NULL ) + continue; + p = data; + + do + { + /* Read dependency count */ + count = *p; + assert( count < data_len ); + INCR_ASSERT( p, data, data_len, sizeof( uint8_t ) ); + ret = verify_dependencies( count, p ); + if ( ret != DEPENDENCY_SUPPORTED ) + break; + + INCR_ASSERT( p, data, data_len, count ); + + /* Read function id */ + function_id = *p; + INCR_ASSERT( p, data, data_len, sizeof( uint8_t ) ); + + /* Read number of parameters */ + count = *p; + INCR_ASSERT( p, data, data_len, sizeof( uint8_t ) ); + + params = parse_parameters( count, p, data_len - (p - data), &ret ); + if ( ret ) + break; + + ret = dispatch_test( function_id, params ); + } + while ( 0 ); + + if ( data ) + { + free(data); + data = NULL; + } + + if ( params ) + { + free( params ); + params = NULL; + } + + if ( ret ) + send_failure( ret ); + else + send_status( test_info.failed ); + } + return( 0 ); +} + diff --git a/tests/suites/mbed_test.function b/tests/suites/mbed_test.function new file mode 100644 index 000000000..e09ed705c --- /dev/null +++ b/tests/suites/mbed_test.function @@ -0,0 +1,173 @@ +#line 2 "suites/mbed_test.function" +/* + * *** THIS FILE HAS BEEN MACHINE GENERATED *** + * + * This file has been machine generated using the script: + * {generator_script} + * + * Test file : {test_file} + * + * The following files were used to create this file. + * + * Main code file : {test_main_file} + * Platform code file : {test_platform_file} + * Helper file : {test_common_helper_file} + * Test suite file : {test_case_file} + * Test suite data : {test_case_data_file} + * + * + * This file is part of mbed TLS (https://tls.mbed.org) + */ + +#if !defined(MBEDTLS_CONFIG_FILE) +#include +#else +#include MBEDTLS_CONFIG_FILE +#endif + + +/*----------------------------------------------------------------------------*/ +/* Common helper code */ + +{test_common_helpers} + +#line {line_no} "suites/mbed_test.function" + + +/*----------------------------------------------------------------------------*/ +/* Test Suite Code */ + + +#define TEST_SUITE_ACTIVE + +{function_headers} + +{functions_code} + +#line {line_no} "suites/mbed_test.function" + + +/*----------------------------------------------------------------------------*/ +/* Test dispatch code */ + + +/** + * \brief Evaluates an expression/macro into its literal integer value. + * For optimizing space for embedded targets each expression/macro + * is identified by a unique identifier instead of string literals. + * Identifiers and evaluation code is generated by script: + * {generator_script} + * + * \param exp_id Expression identifier. + * \param out_value Pointer to int to hold the integer. + * + * \return 0 if exp_id is found. 1 otherwise. + */ +int get_expression( int32_t exp_id, int32_t * out_value ) +{{ +{expression_code} +#line {line_no} "suites/mbed_test.function" + {{ + return( KEY_VALUE_MAPPING_NOT_FOUND ); + }} + return( KEY_VALUE_MAPPING_FOUND ); +}} + + +/** + * \brief Checks if the dependency i.e. the compile flag is set. + * For optimizing space for embedded targets each dependency + * is identified by a unique identifier instead of string literals. + * Identifiers and check code is generated by script: + * {generator_script} + * + * \param exp_id Dependency identifier. + * + * \return DEPENDENCY_SUPPORTED if set else DEPENDENCY_NOT_SUPPORTED + */ +int dep_check( int dep_id ) +{{ +{dep_check_code} +#line {line_no} "suites/mbed_test.function" + {{ + return( DEPENDENCY_NOT_SUPPORTED ); + }} +}} + + +/** + * \brief Function pointer type for test function wrappers. + * + * + * \param void ** Pointer to void pointers. Represents an array of test + * function parameters. + * + * \return void + */ +typedef void (*TestWrapper_t)( void ** ); + + +/** + * \brief Table of test function wrappers. Used by dispatch_test(). + * This table is populated by script: + * {generator_script} + * + */ +TestWrapper_t test_funcs[] = +{{ +{dispatch_code} +#line {line_no} "suites/mbed_test.function" +}}; + + +/** + * \brief Dispatches test functions based on function index. + * + * \param exp_id Test function index. + * + * \return DISPATCH_TEST_SUCCESS if found + * DISPATCH_TEST_FN_NOT_FOUND if not found + * DISPATCH_UNSUPPORTED_SUITE if not compile time enabled. + */ +int dispatch_test( int func_idx, void ** params ) +{{ + int ret = DISPATCH_TEST_SUCCESS; + TestWrapper_t fp = NULL; + + if ( func_idx < (int)( sizeof(test_funcs)/sizeof( TestWrapper_t ) ) ) + {{ + fp = test_funcs[func_idx]; + if ( fp ) + fp( params ); + else + ret = ( DISPATCH_UNSUPPORTED_SUITE ); + }} else + {{ + ret = ( DISPATCH_TEST_FN_NOT_FOUND ); + }} + + return( ret ); +}} + + +{platform_code} + +#line {line_no} "suites/mbed_test.function" + +/*----------------------------------------------------------------------------*/ +/* Main Test code */ + + +/** + * \brief Program main. Invokes platform specific execute_tests(). + * + * \param argc Command line arguments count. + * \param argv Array of command line arguments. + * + * \return Exit code. + */ +int main( int argc, const char *argv[] ) +{{ + return execute_tests( argc, argv ); +}} + diff --git a/tests/suites/test_suite_cipher.chachapoly.data b/tests/suites/test_suite_cipher.chachapoly.data index 1760dc09d..ed2455fd5 100644 --- a/tests/suites/test_suite_cipher.chachapoly.data +++ b/tests/suites/test_suite_cipher.chachapoly.data @@ -1,5 +1,5 @@ Decrypt empty buffer -depends_on:MBEDTLS_CHACHAPOLY_C: +depends_on:MBEDTLS_CHACHAPOLY_C dec_empty_buf: ChaCha20+Poly1305 Encrypt and decrypt 0 bytes