1#!/usr/bin/env python3 2# coding=utf-8 3 4# 5# Copyright (c) 2023 Huawei Device Co., Ltd. 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19 20import os 21import sys 22import json 23import shutil 24import subprocess 25import CppHeaderParser 26import get_innerkits_json 27import make_report 28 29filter_file_name_list = [ 30 "appexecfwk/libjnikit/include/jni.h", 31] 32FILTER_CLASS_ATTRIBUTE_LIST = ["ACE_EXPORT", "OHOS_NWEB_EXPORT"] 33 34 35def _init_sys_config(): 36 sys.localcoverage_path = os.path.join(current_path, "..") 37 sys.path.insert(0, sys.localcoverage_path) 38 39 40def create_coverage_result_outpath(filepath): 41 if os.path.exists(filepath): 42 shutil.rmtree(filepath) 43 os.makedirs(filepath) 44 45 46def get_subsystem_part_list(project_rootpath): 47 subsystme_part_dict = {} 48 subsystem_part_config_filepath = os.path.join( 49 project_rootpath, "out", product_name, "build_configs/infos_for_testfwk.json") 50 if os.path.exists(subsystem_part_config_filepath): 51 try: 52 with open(subsystem_part_config_filepath, 'r') as f: 53 data = json.load(f) 54 except IOError: 55 print("Error for open subsystem config file. ") 56 if not data: 57 print("subsystem_part config file error.") 58 else: 59 subsystme_part_dict = data.get("phone", "").get("subsystem_infos", "") 60 return subsystme_part_dict 61 else: 62 print("subsystem_part_config_filepath not exists.") 63 return {} 64 65 66def load_json_data(): 67 json_file_path = os.path.join(CODEPATH, KIT_MODULES_INFO) 68 json_data_dic = {} 69 if os.path.isfile(json_file_path): 70 try: 71 with open(json_file_path, 'r') as f: 72 json_data_dic = json.load(f) 73 if not json_data_dic: 74 print("Loadind file \"%s\" error" % json_file_path) 75 return {} 76 except(IOError, ValueError): 77 print("Error for load_json_data: \"%s\"" % json_file_path) 78 else: 79 print("Info: \"%s\" not exist." % json_file_path) 80 return json_data_dic 81 82 83def get_file_list(find_path, postfix): 84 file_names = os.listdir(find_path) 85 file_list = [] 86 if len(file_names) > 0: 87 for fn in file_names: 88 if fn.find(postfix) != -1 and fn[-len(postfix):] == postfix: 89 file_list.append(fn) 90 return file_list 91 92 93def get_file_list_by_postfix(path, postfix, filter_jar=""): 94 file_list = [] 95 for dirs in os.walk(path): 96 files = get_file_list(find_path=dirs[0], postfix=postfix) 97 for file_path in files: 98 if "" != file_path and -1 == file_path.find(__file__): 99 pos = file_path.rfind(os.sep) 100 file_name = file_path[pos + 1:] 101 file_path = os.path.join(dirs[0], file_path) 102 if filter_jar != "" and file_name == filter_jar: 103 print("Skipped %s" % file_path) 104 continue 105 file_list.append(file_path) 106 return file_list 107 108 109def is_need_to_be_parsed(filepath): 110 for item in filter_file_name_list: 111 if -1 != filepath.find(item): 112 return False 113 return True 114 115 116def get_pubilc_func_list_from_headfile(cxx_header_filepath): 117 pubilc_func_list = [] 118 try: 119 cpp_header = CppHeaderParser.CppHeader(cxx_header_filepath) 120 for classname in cpp_header.classes: 121 class_name = classname 122 curr_class = cpp_header.classes[classname] 123 for func in curr_class["methods"]["public"]: 124 func_returntype = func["rtnType"] 125 func_name = func["name"] 126 if class_name in FILTER_CLASS_ATTRIBUTE_LIST: 127 class_name = func_name 128 if func_returntype.find("KVSTORE_API") != -1: 129 func_returntype = func_returntype.replace("KVSTORE_API", "").strip() 130 if func_name.isupper(): 131 continue 132 if class_name == func_name: 133 destructor = func["destructor"] 134 if destructor: 135 func_name = f"~{func_name}" 136 func_returntype = "" 137 debug = func["debug"].replace("KVSTORE_API", "") 138 debug = debug.replace(" ", "") 139 debug = debug.strip("{") 140 if debug.endswith("=delete;"): 141 continue 142 if debug.endswith("=default;"): 143 continue 144 if debug.startswith("inline"): 145 continue 146 if debug.startswith("constexpr"): 147 continue 148 if debug.startswith("virtual"): 149 continue 150 template = func["template"] 151 if template: 152 continue 153 param_type_list = [t["type"] for t in func["parameters"]] 154 pubilc_func_list.append((cxx_header_filepath, class_name, 155 func_name, param_type_list, func_returntype)) 156 for func in cpp_header.functions: 157 func_returntype = func["rtnType"] 158 func_name = func["name"] 159 if func_returntype.find("KVSTORE_API") != -1: 160 func_returntype = func_returntype.replace("KVSTORE_API", "").strip() 161 if func_name.isupper(): 162 continue 163 template = func["template"] 164 if template: 165 continue 166 debug = func["debug"].replace("KVSTORE_API", "") 167 debug = debug.replace(" ", "") 168 debug = debug.strip("{") 169 if debug.startswith("inline"): 170 continue 171 if debug.startswith("constexpr"): 172 continue 173 param_type_list = [t["type"] for t in func["parameters"]] 174 pubilc_func_list.append( 175 (cxx_header_filepath, "", func_name, param_type_list, 176 func_returntype) 177 ) 178 except CppHeaderParser.CppParseError: 179 print("") 180 return pubilc_func_list 181 182 183def get_sdk_interface_func_list(part_name): 184 interface_func_list = [] 185 sub_path = load_json_data().get(part_name, "") 186 if sub_path == "": 187 return interface_func_list 188 189 sdk_path = os.path.join(CODEPATH, "out", product_name, sub_path) 190 if os.path.exists(sdk_path): 191 file_list = get_file_list_by_postfix(sdk_path, ".h") 192 for file in file_list: 193 try: 194 if is_need_to_be_parsed(file): 195 interface_func_list += get_pubilc_func_list_from_headfile(file) 196 except Exception: 197 print("get interface error ", sdk_path) 198 else: 199 print("Error: %s is not exist." % sdk_path) 200 return interface_func_list 201 202 203def get_function_info_string(func_string): 204 function_info = "" 205 cxxfilt_filepath = "/usr/bin/c++filt" 206 if os.path.exists(cxxfilt_filepath): 207 command = ["c++filt", func_string] 208 function_info = subprocess.check_output(command, shell=False) 209 else: 210 print("/usr/bin/c++filt is not exist.") 211 return function_info 212 213 214def get_covered_function_list(part_name): 215 covered_function_list = [] 216 file_name = f"{part_name}_strip.info" 217 file_path = os.path.join(SUB_SYSTEM_INFO_PATH, file_name) 218 if os.path.exists(file_path): 219 with open(file_path, "r") as fd: 220 for line in fd: 221 if line.startswith("FNDA:"): 222 sub_line_string = line[len("FNDA:"):].replace("\n", "").strip() 223 temp_list = sub_line_string.split(",") 224 if len(temp_list) == 2 and int(temp_list[0]) != 0: 225 func_info = get_function_info_string(temp_list[1]) 226 after_func_info = func_info.decode("utf-8") 227 if "" == after_func_info: 228 continue 229 after_func_info = after_func_info.replace("\n", "") 230 if after_func_info == temp_list[1] and after_func_info.startswith("_"): 231 continue 232 covered_function_list.append(after_func_info) 233 else: 234 pass 235 return covered_function_list 236 237 238def get_para_sub_string(content): 239 start_index = -1 240 ended_index = -1 241 parentheses_list_left = [] 242 parentheses_list_right = [] 243 244 for index in range(len(content)): 245 char = content[index] 246 if "<" == char: 247 if 0 == len(parentheses_list_left): 248 start_index = index 249 parentheses_list_left.append(char) 250 continue 251 if ">" == char: 252 parentheses_list_right.append(char) 253 if len(parentheses_list_left) == len(parentheses_list_right): 254 ended_index = index 255 break 256 continue 257 258 if -1 == start_index: 259 substring = content 260 else: 261 if -1 != ended_index: 262 substring = content[start_index:ended_index + 1] 263 else: 264 substring = content[start_index:] 265 266 return substring 267 268 269def filter_para_sub_string(source): 270 content = source 271 if content != "": 272 while True: 273 pos = content.find("<") 274 if -1 != pos: 275 substring = get_para_sub_string(content[pos:]) 276 content = content.replace(substring, "") 277 else: 278 break 279 return content 280 281 282def get_function_para_count(func_info): 283 pos_start = func_info.find("(") 284 pos_end = func_info.rfind(")") 285 content = func_info[pos_start + 1: pos_end] 286 if "" == content: 287 return 0 288 content = filter_para_sub_string(content) 289 para_list = content.split(",") 290 return len(para_list) 291 292 293def get_covered_result_data(public_interface_func_list, covered_func_list): 294 coverage_result_list = [] 295 for item in public_interface_func_list: 296 data_list = list(item) 297 class_name = data_list[1] 298 func_name = data_list[2] 299 para_list = data_list[3] 300 return_val = data_list[4] 301 para_string = "" 302 for index in range(len(para_list)): 303 if para_list[index].strip() == "": 304 continue 305 curr_para = para_list[index] 306 para_string += curr_para 307 if index < len(para_list) - 1: 308 para_string += ", " 309 fun_string = f"{return_val}' '{func_name}({para_string.strip().strip(',')})" 310 fun_string = fun_string.strip() 311 fun_string = filter_para_sub_string(fun_string) 312 313 if class_name != "": 314 find_string = f"::{class_name}::{func_name}(" 315 else: 316 find_string = func_name 317 func_info_list = [] 318 for line in covered_func_list: 319 if -1 != line.find(find_string): 320 func_info_list.append(line) 321 curr_list = [class_name, fun_string] 322 if len(func_info_list) == 0: 323 curr_list.append("N") 324 elif len(func_info_list) == 1: 325 curr_list.append("Y") 326 else: 327 interface_para_count = len(para_list) 328 find_flag = False 329 for funcinfo in func_info_list: 330 if find_string == funcinfo: 331 curr_list.append("Y") 332 break 333 para_count = get_function_para_count(funcinfo) 334 if interface_para_count == para_count: 335 curr_list.append("Y") 336 find_flag = True 337 break 338 if not find_flag: 339 curr_list.append("N") 340 coverage_result_list.append(curr_list) 341 return coverage_result_list 342 343 344def get_interface_coverage_result_list(part_name): 345 public_interface_func_list = [] 346 try: 347 interface_func_list = get_sdk_interface_func_list(part_name) 348 public_interface_func_list.extend(interface_func_list) 349 except Exception: 350 print("####") 351 covered_func_list = get_covered_function_list(part_name) 352 interface_coverage_result_list = get_covered_result_data( 353 public_interface_func_list, covered_func_list) 354 return interface_coverage_result_list 355 356 357def get_coverage_data(data_list): 358 covered_count = 0 359 total_count = len(data_list) 360 if 0 != total_count: 361 for item in data_list: 362 if "Y" == item[2] or "Recorded" == item[2]: 363 covered_count += 1 364 coverage = str("%.2f" % (covered_count * 100 / total_count)) + "%" 365 else: 366 coverage = "0%" 367 return covered_count, coverage 368 369 370def get_summary_data(interface_data_list): 371 summary_list = [] 372 total_count = 0 373 covered_count = 0 374 375 for item in interface_data_list: 376 subsystem_name = item[0] 377 data_list = item[1] 378 if 0 != len(data_list): 379 count, coverage = get_coverage_data(data_list) 380 summary_list.append([subsystem_name, len(data_list), count, coverage]) 381 total_count += len(data_list) 382 covered_count += count 383 if 0 != total_count: 384 total_coverage = str("%.2f" % (covered_count * 100 / total_count)) + "%" 385 summary_list.append(["Summary", total_count, covered_count, total_coverage]) 386 return summary_list 387 388 389def make_summary_file(summary_list, output_path): 390 report_path = os.path.join(output_path, "coverage_summary_file.xml") 391 try: 392 with open(report_path, "w") as fd: 393 fd.write('<?xml version="1.0" encoding="UTF-8"?>\n') 394 fd.write('<coverage>\n') 395 for item in summary_list: 396 fd.write(" <item subsystem_name=\"%s\" " 397 "function_count=\"%s\" coverage_value=\"%s\" />\n" % ( 398 item[0], str(item[1]), item[3])) 399 fd.write('</coverage>\n') 400 except(IOError, ValueError): 401 print("Error for make coverage result:",) 402 403 404def make_result_file(interface_data_list, summary_list, output_path, title_name): 405 report_path = os.path.join(output_path, "ohos_interfaceCoverage.html") 406 make_report.create_html_start(report_path) 407 make_report.create_title(report_path, title_name, summary_list) 408 make_report.create_summary(report_path, summary_list) 409 for item in interface_data_list: 410 subsystem_name = item[0] 411 data_list = item[1] 412 if 0 == len(data_list): 413 continue 414 count, coverage = get_coverage_data(data_list) 415 make_report.create_table_test( 416 report_path, subsystem_name, data_list, len(data_list), count) 417 make_report.create_html_ended(report_path) 418 419 420def make_coverage_result_file(interface_data_list, output_path, title_name): 421 summary_list = get_summary_data(interface_data_list) 422 make_summary_file(summary_list, output_path) 423 make_result_file(interface_data_list, summary_list, output_path, title_name) 424 425 426def make_interface_coverage_result(part_list): 427 interface_data_list = [] 428 for part_name in part_list: 429 coverage_result_list = get_interface_coverage_result_list( 430 part_name) 431 interface_data_list.append([part_name, coverage_result_list]) 432 make_coverage_result_file(interface_data_list, OUTPUT_REPORT_PATH, 433 "Inner Interface") 434 435 436if __name__ == "__main__": 437 current_path = os.getcwd() 438 CODEPATH = current_path.split("/test/testfwk/developer_test")[0] 439 SUB_SYSTEM_INFO_PATH = os.path.join( 440 CODEPATH, "test/testfwk/developer_test/localCoverage/codeCoverage/results/coverage/reports/cxx") 441 OUTPUT_REPORT_PATH = os.path.join( 442 CODEPATH, "test/testfwk/developer_test/localCoverage/interfaceCoverage/results/coverage/interface_kits" 443 ) 444 _init_sys_config() 445 from localCoverage.utils import get_product_name, get_target_cpu 446 product_name = get_product_name(CODEPATH) 447 cpu_type = get_target_cpu(CODEPATH) 448 PATH_INFO_PATH = "out/{}/innerkits/ohos-{}".format(product_name, cpu_type) 449 OUTPUT_JSON_PATH = "out/{}/packages/phone/innerkits/ohos-{}".format( 450 product_name, cpu_type) 451 KIT_MODULES_INFO = "out/{}/packages/phone/innerkits/ohos-{}/kits_modules_info.json".format( 452 product_name, cpu_type) 453 454 part_args = sys.argv[1] 455 part_name_list = part_args.split("testpart=")[1].split(",") 456 get_innerkits_json.gen_parts_info_json( 457 get_innerkits_json.get_parts_list(os.path.join(CODEPATH, PATH_INFO_PATH)), 458 os.path.join(CODEPATH, OUTPUT_JSON_PATH), cpu_type 459 ) 460 if len(part_name_list) > 0: 461 make_interface_coverage_result(part_name_list) 462 else: 463 print("subsystem_name not exists!") 464