#!/usr/bin/env python2 # # Copyright(c) 2012-2018 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause-Clear # import shutil import sys import re import commands import os.path from collections import defaultdict import tests_config # # This script purpose is to remove unused functions definitions # It is giving the opportunity to unit test all functions from OCF. # As a parameter should be given path to file containing function, # which is target of testing. However that file has to be after # preprocessing. # # Output file of this script is not ready to make it. Before that, # has to be given definitions of functions, which are used by # tested function. # # In brief: this script allow wrapping all function calls in UT # class UnitTestsSourcesGenerator(object): script_file_abs_path = "" script_dir_abs_path = "" main_UT_dir = "" main_tested_dir = "" ctags_path = "" test_catalouges_list = [] dirs_to_include_list = [] tests_internal_includes_list = [] framework_includes = [] dirs_with_tests_list = [] test_files_paths_list = [] tested_files_paths_list = [] includes_to_copy_dict = {} preprocessing_repo = "" sources_to_test_repo = "" def __init__(self): self.script_file_abs_path = os.path.realpath(__file__) self.script_dir_abs_path = os.path.normpath(os.path.dirname(self.script_file_abs_path) + os.sep) self.set_ctags_path() self.set_main_UT_dir() self.set_main_tested_dir() self.test_catalouges_list = tests_config.DIRECTORIES_WITH_TESTS_LIST self.set_includes_to_copy_dict(tests_config.INCLUDES_TO_COPY_DICT) self.set_dirs_to_include() self.set_tests_internal_includes_list() self.set_framework_includes() self.set_files_with_tests_list() self.set_tested_files_paths_list() self.set_preprocessing_repo() self.set_sources_to_test_repo() def preprocessing(self): tested_files_list = self.get_tested_files_paths_list() project_includes = self.get_dirs_to_include_list() framework_includes = self.get_tests_internal_includes_list() gcc_flags = " -fno-inline -Dstatic= -Dinline= -E " gcc_command_template = "gcc " for path in project_includes: gcc_command_template += " -I " + path + " " for path in framework_includes: gcc_command_template += " -I " + path gcc_command_template += gcc_flags for path in tested_files_list: preprocessing_dst = self.get_preprocessing_repo() +\ self.get_relative_path(path, self.get_main_tested_dir()) preprocessing_dst_dir = os.path.dirname(preprocessing_dst) self.create_dir_if_not_exist(preprocessing_dst_dir) gcc_command = gcc_command_template +\ path + " > " + preprocessing_dst status, output = commands.getstatusoutput(gcc_command) if status != 0: print "Generating preprocessing for " + self.get_main_tested_dir() + path \ + " failed!" print output commands.getoutput("rm -f " + preprocessing_dst) continue self.remove_hashes(preprocessing_dst) print "Preprocessed file " + path + " saved to " + preprocessing_dst def copy_includes(self): includes_dict = self.get_includes_to_copy_dict() for dst, src in includes_dict.iteritems(): src_path = os.path.normpath(self.get_main_tested_dir() + src) if not os.path.isdir(src_path): print "Directory " + src_path + " given to include does not exists!" continue dst_path = os.path.normpath(self.get_main_UT_dir() + dst) shutil.rmtree(dst_path) shutil.copytree(src_path, dst_path) def prepare_sources_for_testing(self): test_files_paths = self.get_files_with_tests_list() for test_path in test_files_paths: path = self.get_tested_file_path(self.get_main_UT_dir() + test_path) preprocessed_tested_path = self.get_preprocessing_repo() + path if not os.path.isfile(preprocessed_tested_path): print "No preprocessed path for " + test_path + " test file." continue tested_src = self.get_src_to_test(test_path, preprocessed_tested_path) self.create_dir_if_not_exist(self.get_sources_to_test_repo() + os.path.dirname(test_path)) with open(self.get_sources_to_test_repo() + test_path, "w") as f: f.writelines(tested_src) print "Sources for " + test_path + " saved in " +\ self.get_sources_to_test_repo() + test_path def create_main_cmake_lists(self): buf = "cmake_minimum_required(VERSION 2.6.0)\n\n" buf += "project(OCF_unit_tests C)\n\n" buf += "enable_testing()\n\n" buf += "include_directories(\n" dirs_to_inc = self.get_dirs_to_include_list() + self.get_framework_includes()\ + self.get_tests_internal_includes_list() for path in dirs_to_inc: buf += "\t" + path + "\n" buf += ")\n\n" includes = self.get_tests_internal_includes_list() for path in includes: buf += "\nadd_subdirectory(" + path + ")" buf += "\n\n" test_files = self.get_files_with_tests_list() test_dirs_to_include = [os.path.dirname(path) for path in test_files] test_dirs_to_include = self.remove_duplicates_from_list(test_dirs_to_include) for path in test_dirs_to_include: buf += "\nadd_subdirectory(" + self.get_sources_to_test_repo() + path + ")" with open(self.get_main_UT_dir() + "CMakeLists.txt", "w") as f: f.writelines(buf) print "Main CMakeLists.txt generated written to " + self.get_main_UT_dir() + "CMakeLists.txt" def generate_cmakes_for_tests(self): test_files_paths = self.get_files_with_tests_list() for test_path in test_files_paths: tested_file_path = self.get_sources_to_test_repo() + test_path if not os.path.isfile(tested_file_path): print "No source to test for " + test_path + " test" continue test_file_path = self.get_main_UT_dir() + test_path cmake_buf = self.generate_test_cmake_buf(test_file_path, tested_file_path) cmake_path = self.get_sources_to_test_repo() + test_path cmake_path = os.path.splitext(cmake_path)[0] + ".cmake" with open(cmake_path, "w") as f: f.writelines(cmake_buf) print "cmake file for " + test_path + " written to " + cmake_path cmake_lists_path = os.path.dirname(cmake_path) + os.sep self.update_cmakelists(cmake_lists_path, cmake_path) def generate_test_cmake_buf(self, test_file_path, tested_file_path): test_file_name = os.path.basename(test_file_path) target_name = os.path.splitext(test_file_name)[0] add_executable = "add_executable(" + target_name + " " + test_file_path + " " + tested_file_path + ")\n" libraries = "target_link_libraries(" + target_name + " libcmocka.so ocf_env)\n" add_test = "add_test(" + target_name + " ${CMAKE_CURRENT_BINARY_DIR}/" + target_name + ")\n" tgt_properties = "set_target_properties(" + target_name + "\n" + \ "PROPERTIES\n" + \ "COMPILE_FLAGS \"-fno-inline -Dstatic= -Dinline= -w \"\n" link_flags = self.generate_cmake_link_flags(test_file_path) tgt_properties += link_flags + ")" buf = add_executable + libraries + add_test + tgt_properties return buf def generate_cmake_link_flags(self, path): ret = "" functions_to_wrap = self.get_functions_to_wrap(path) for function_name in functions_to_wrap: ret += ",--wrap=" + function_name if len(ret) > 0: ret = "LINK_FLAGS \"-Wl" + ret + "\"\n" return ret def update_cmakelists(self, cmake_lists_path, cmake_name): with open(cmake_lists_path + "CMakeLists.txt", "a+") as f: f.seek(0, os.SEEK_SET) new_line = "include(" + os.path.basename(cmake_name) + ")\n" if not new_line in f.read(): f.write(new_line) def get_functions_to_wrap(self, path): functions_list = self.get_functions_list(path) functions_list = [re.sub(r'__wrap_([\S]+)\s*[\d]+', r'\1', line) for line in functions_list if re.search("__wrap_", line)] return functions_list def get_functions_to_leave(self, path): with open(path) as f: l = f.readlines() buf = ''.join(l) tags_pattern = re.compile("[\s\S]*") buf = re.findall(tags_pattern, buf) if not len(buf) > 0: return [] buf = buf[0] buf = re.sub(r'<.*>', '', buf) buf = re.sub(r'[^a-zA-Z0-9_\n]+', '', buf) ret = buf.split("\n") ret = [name for name in ret if name] return ret def get_functions_list(self, file_path): ctags_path = self.get_ctags_path() # find all functions' definitions | put tabs instead of spaces | # take only columns with function name and line number | sort in descending order status, output = commands.getstatusoutput(ctags_path + "-x --c-types=f " + file_path + " --language-force=c | \ sed \"s/ \\+/\t/g\" | cut -f 1,3 | sort -nsr -k 2") # 'output' is string, but it has to be changed to list output = output.split("\n") return output def remove_functions_from_list(self, functions_list, to_remove_list): ret = functions_list[:] for function_name in to_remove_list: ret = [line for line in ret if not re.search(r'\b%s\b' % function_name, line)] return ret def get_src_to_test(self, test_path, preprocessed_tested_path): functions_to_leave = self.get_functions_to_leave(self.get_main_UT_dir() + test_path) functions_to_leave.append(self.get_tested_function_name(self.get_main_UT_dir() + test_path)) functions_list = self.get_functions_list(preprocessed_tested_path) functions_list = self.remove_functions_from_list(functions_list, functions_to_leave) with open(preprocessed_tested_path) as f: ret = f.readlines() for function in functions_list: line = function.split("\t")[1] line = int(line) self.remove_function_body(ret, line) return ret def set_tested_files_paths_list(self): test_files_list = self.get_files_with_tests_list() for f in test_files_list: self.tested_files_paths_list.append(self.get_main_tested_dir() +\ self.get_tested_file_path(self.get_main_UT_dir() + f)) self.tested_files_paths_list = self.remove_duplicates_from_list(self.tested_files_paths_list) def get_tested_files_paths_list(self): return self.tested_files_paths_list def get_files_with_tests_list(self): return self.test_files_paths_list def set_files_with_tests_list(self): test_catalogues_list = self.get_tests_catalouges_list() for catalogue in test_catalogues_list: dir_with_tests_path = self.get_main_UT_dir() + catalogue for path, dirs, files in os.walk(dir_with_tests_path): test_files = self.get_test_files_from_dir(path + os.sep) for test_file_name in test_files: test_rel_path = os.path.relpath(path + os.sep + test_file_name, self.get_main_UT_dir()) self.test_files_paths_list.append(test_rel_path) def are_markups_valid(self, path): file_path = self.get_tested_file_path(path) function_name = self.get_tested_function_name(path) if file_path is None: print path + " file has no tested_file tag!" return None elif not os.path.isfile(self.get_main_tested_dir() + file_path): print "Tested file given in " + path + " not exist!" return None if function_name is None: print path + " file has no tested_function_name tag!" return None return True def create_dir_if_not_exist(self, path): if not os.path.isdir(path): try: os.makedirs(path) except Exception: pass return True return None def get_tested_file_path(self, test_file_path): with open(test_file_path) as f: buf = f.readlines() buf = ''.join(buf) tags_pattern = re.compile("[\s\S]*") buf = re.findall(tags_pattern, buf) if not len(buf) > 0: return None buf = buf[0] buf = re.sub(r'<[^>]*>', '', buf) buf = re.sub(r'\s+', '', buf) if len(buf) > 0: return buf return None def get_tested_function_name(self, test_file_path): with open(test_file_path) as f: buf = f.readlines() buf = ''.join(buf) tags_pattern = re.compile("[\s\S]*") buf = re.findall(tags_pattern, buf) if not len(buf) > 0: return None buf = buf[0] buf = re.sub(r'<[^>]*>', '', buf) buf = re.sub('//', '', buf) buf = re.sub(r'\s+', '', buf) if len(buf) > 0: return buf return None def get_test_files_from_dir(self, path): ret = os.listdir(path) ret = [name for name in ret if os.path.isfile(path + os.sep + name) and (name.endswith(".c") or name.endswith(".h"))] ret = [name for name in ret if self.are_markups_valid(path + name)] return ret def get_list_of_directories(self, path): if not os.path.isdir(path): return [] ret = os.listdir(path) ret = [name for name in ret if not os.path.isfile(path + os.sep + name)] ret = [os.path.normpath(name) + os.sep for name in ret] return ret def remove_hashes(self, path): with open(path) as f: buf = f.readlines() buf = [l for l in buf if not re.search(r'.*#.*', l)] with open(path, "w") as f: f.writelines(buf) return for i in range(len(padding)): try: padding[i] = padding[i].split("#")[0] except ValueError: continue f = open(path, "w") f.writelines(padding) f.close() def find_function_end(self,code_lines_list, first_line_of_function_index): brackets_counter = 0 current_line_index = first_line_of_function_index while True: if "{" in code_lines_list[current_line_index]: brackets_counter += code_lines_list[current_line_index].count("{") brackets_counter -= code_lines_list[current_line_index].count("}") break else: current_line_index += 1 while brackets_counter > 0: current_line_index += 1 if "{" in code_lines_list[current_line_index]: brackets_counter += code_lines_list[current_line_index].count("{") brackets_counter -= code_lines_list[current_line_index].count("}") elif "}" in code_lines_list[current_line_index]: brackets_counter -= code_lines_list[current_line_index].count("}") return current_line_index def remove_function_body(self, code_lines_list, line_id): try: while "{" not in code_lines_list[line_id]: if ";" in code_lines_list[line_id]: return line_id += 1 except IndexError: return last_line_id = self.find_function_end(code_lines_list, line_id) code_lines_list[line_id] = code_lines_list[line_id].split("{")[0] code_lines_list[line_id] += ";" del code_lines_list[line_id + 1: last_line_id + 1] def set_ctags_path(self): status, output = commands.getstatusoutput("/usr/bin/ctags --version &> /dev/null") if status == 0: path = "/usr/bin/ctags " status, output = commands.getstatusoutput(path + "--c-types=f") if not re.search("unrecognized option", output, re.IGNORECASE): self.ctags_path = path return status, output = commands.getstatusoutput("/usr/local/bin/ctags --version &> /dev/null") if status == 0: path = "/usr/local/bin/ctags " status, output = commands.getstatusoutput(path + "--c-types=f") if not re.search("unrecognized option", output, re.IGNORECASE): self.ctags_path = path return print "ERROR: Current ctags version don't support \"--c-types=f\" parameter!" exit(1) def get_ctags_path(self): return self.ctags_path def get_tests_catalouges_list(self): return self.test_catalouges_list def get_relative_path(self, original_path, part_to_remove): return original_path.split(part_to_remove, 1)[1] def get_dirs_to_include_list(self): return self.dirs_to_include_list def set_dirs_to_include(self): self.dirs_to_include_list = [self.get_main_tested_dir() + name\ for name in tests_config.DIRECTORIES_TO_INCLUDE_FROM_PROJECT_LIST] def set_tests_internal_includes_list(self): self.tests_internal_includes_list = [self.get_main_UT_dir() + name\ for name in tests_config.DIRECTORIES_TO_INCLUDE_FROM_UT_LIST] def set_preprocessing_repo(self): self.preprocessing_repo = self.get_main_UT_dir() +\ tests_config.PREPROCESSED_SOURCES_REPOSITORY def set_sources_to_test_repo(self): self.sources_to_test_repo = self.get_main_UT_dir() +\ tests_config.SOURCES_TO_TEST_REPOSITORY def get_sources_to_test_repo(self): return self.sources_to_test_repo def get_preprocessing_repo(self): return self.preprocessing_repo def get_tests_internal_includes_list(self): return self.tests_internal_includes_list def get_script_dir_path(self): return os.path.normpath(self.script_dir_abs_path) + os.sep def get_main_UT_dir(self): return os.path.normpath(self.main_UT_dir) + os.sep def get_main_tested_dir(self): return os.path.normpath(self.main_tested_dir) + os.sep def remove_duplicates_from_list(self, l): return list(set(l)) def set_framework_includes(self): self.framework_includes = tests_config.FRAMEWORK_DIRECTORIES_TO_INCLUDE_LIST def get_framework_includes(self): return self.framework_includes def set_includes_to_copy_dict(self, files_to_copy_dict): self.includes_to_copy_dict = files_to_copy_dict def get_includes_to_copy_dict(self): return self.includes_to_copy_dict def set_main_UT_dir(self): main_UT_dir = os.path.normpath(os.path.normpath(self.get_script_dir_path()\ + os.sep + tests_config.MAIN_DIRECTORY_OF_UNIT_TESTS)) if not os.path.isdir(main_UT_dir): print "Given path to main UT directory is wrong!" sys.exit(1) self.main_UT_dir = main_UT_dir def set_main_tested_dir(self): main_tested_dir = os.path.normpath(os.path.normpath(self.get_script_dir_path()\ + os.sep + tests_config.MAIN_DIRECTORY_OF_TESTED_PROJECT)) if not os.path.isdir(main_tested_dir): print "Given path to main tested directory is wrong!" sys.exit(1) self.main_tested_dir = main_tested_dir def __main__(): generator = UnitTestsSourcesGenerator() generator.copy_includes() generator.preprocessing() generator.prepare_sources_for_testing() generator.create_main_cmake_lists() generator.generate_cmakes_for_tests() print "Files for testing generated!" if __name__ == "__main__": __main__()