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