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