1# SPDX-License-Identifier: Apache-2.0 2# Copyright (c) 2024 Intel Corporation 3 4import yaml 5import ijson 6import json 7import re 8import argparse 9import xlsxwriter 10class Json_report: 11 12 json_object = { 13 "components":[] 14 } 15 16 simulators = [ 17 'unit_testing', 18 'native', 19 'qemu', 20 'mps2/an385' 21 ] 22 23 report_json = {} 24 25 def __init__(self): 26 args = parse_args() 27 self.parse_testplan(args.testplan) 28 self.maintainers_file = self.get_maintainers_file( args.maintainers) 29 self.report_json = self.generate_json_report( args.coverage) 30 31 if args.format == "json": 32 self.save_json_report( args.output, self.report_json) 33 elif args.format == "xlsx": 34 self.generate_xlsx_report(self.report_json, args.output) 35 elif args.format == "all": 36 self.save_json_report( args.output, self.report_json) 37 self.generate_xlsx_report(self.report_json, args.output) 38 else: 39 print("Format incorrect") 40 41 def get_maintainers_file(self, maintainers): 42 maintainers_file = "" 43 with open(maintainers, 'r') as file: 44 maintainers_file = yaml.safe_load(file) 45 file.close() 46 return maintainers_file 47 48 49 def parse_testplan(self, testplan_path): 50 with open(testplan_path, 'r') as file: 51 parser = ijson.items(file, 'testsuites') 52 for element in parser: 53 for testsuite in element: 54 for testcase in testsuite['testcases']: 55 if testcase['status'] == 'None': 56 testcase_name = testcase['identifier'] 57 component_name = testcase_name[:testcase_name.find('.')] 58 component = { 59 "name": component_name, 60 "sub_components":[], 61 "files":[] 62 } 63 features = self.json_object['components'] 64 known_component_flag = False 65 for item in features: 66 if component_name == item['name']: 67 component = item 68 known_component_flag = True 69 break 70 sub_component_name = testcase_name[testcase_name.find('.'):] 71 sub_component_name = sub_component_name[1:] 72 if sub_component_name.find(".") > 0: 73 sub_component_name = sub_component_name[:sub_component_name.find(".")] 74 if known_component_flag is False: 75 76 sub_component = { 77 "name":sub_component_name, 78 "test_suites":[] 79 } 80 test_suite = { 81 "name":testsuite['name'], 82 "path":testsuite['path'], 83 "platforms":[], 84 "runnable": testsuite['runnable'], 85 "status":"", 86 "test_cases":[] 87 } 88 test_case = { 89 "name":testcase_name 90 } 91 if any(platform in testsuite['platform'] for platform in self.simulators): 92 if test_suite['status'] == "": 93 test_suite['status'] = 'sim_only' 94 95 if test_suite['status'] == 'hw_only': 96 test_suite['status'] = 'mixed' 97 else: 98 if test_suite['status'] == "": 99 test_suite['status'] = 'hw_only' 100 101 if test_suite['status'] == 'sim_only': 102 test_suite['status'] = 'mixed' 103 test_suite['test_cases'].append(test_case) 104 test_suite['platforms'].append(testsuite['platform']) 105 sub_component["test_suites"].append(test_suite) 106 component['sub_components'].append(sub_component) 107 self.json_object['components'].append(component) 108 else: 109 sub_component = {} 110 sub_components = component['sub_components'] 111 known_sub_component_flag = False 112 for i_sub_component in sub_components: 113 if sub_component_name == i_sub_component['name']: 114 sub_component = i_sub_component 115 known_sub_component_flag = True 116 break 117 if known_sub_component_flag is False: 118 sub_component = { 119 "name":sub_component_name, 120 "test_suites":[] 121 } 122 test_suite = { 123 "name":testsuite['name'], 124 "path":testsuite['path'], 125 "platforms":[], 126 "runnable": testsuite['runnable'], 127 "status":"", 128 "test_cases":[] 129 } 130 test_case = { 131 "name": testcase_name 132 } 133 if any(platform in testsuite['platform'] for platform in self.simulators): 134 if test_suite['status'] == "": 135 test_suite['status'] = 'sim_only' 136 137 if test_suite['status'] == 'hw_only': 138 test_suite['status'] = 'mixed' 139 else: 140 if test_suite['status'] == "": 141 test_suite['status'] = 'hw_only' 142 143 if test_suite['status'] == 'sim_only': 144 test_suite['status'] = 'mixed' 145 test_suite['test_cases'].append(test_case) 146 test_suite['platforms'].append(testsuite['platform']) 147 sub_component["test_suites"].append(test_suite) 148 component['sub_components'].append(sub_component) 149 else: 150 test_suite = {} 151 test_suites = sub_component['test_suites'] 152 known_testsuite_flag = False 153 for i_testsuite in test_suites: 154 if testsuite['name'] == i_testsuite['name']: 155 test_suite = i_testsuite 156 known_testsuite_flag = True 157 break 158 if known_testsuite_flag is False: 159 test_suite = { 160 "name":testsuite['name'], 161 "path":testsuite['path'], 162 "platforms":[], 163 "runnable": testsuite['runnable'], 164 "status":"", 165 "test_cases":[] 166 } 167 test_case = { 168 "name": testcase_name 169 } 170 if any(platform in testsuite['platform'] for platform in self.simulators): 171 if test_suite['status'] == "": 172 test_suite['status'] = 'sim_only' 173 174 if test_suite['status'] == 'hw_only': 175 test_suite['status'] = 'mixed' 176 else: 177 if test_suite['status'] == "": 178 test_suite['status'] = 'hw_only' 179 180 if test_suite['status'] == 'sim_only': 181 test_suite['status'] = 'mixed' 182 test_suite['test_cases'].append(test_case) 183 test_suite['platforms'].append(testsuite['platform']) 184 sub_component["test_suites"].append(test_suite) 185 else: 186 if any(platform in testsuite['platform'] for platform in self.simulators): 187 if test_suite['status'] == "": 188 test_suite['status'] = 'sim_only' 189 190 if test_suite['status'] == 'hw_only': 191 test_suite['status'] = 'mixed' 192 else: 193 if test_suite['status'] == "": 194 test_suite['status'] = 'hw_only' 195 196 if test_suite['status'] == 'sim_only': 197 test_suite['status'] = 'mixed' 198 test_case = {} 199 test_cases = test_suite['test_cases'] 200 known_testcase_flag = False 201 for i_testcase in test_cases: 202 if testcase_name == i_testcase['name']: 203 test_case = i_testcase 204 known_testcase_flag = True 205 break 206 if known_testcase_flag is False: 207 test_case = { 208 "name":testcase_name 209 } 210 test_suite['test_cases'].append(test_case) 211 file.close() 212 213 def get_files_from_maintainers_file(self, component_name): 214 files_path = [] 215 for item in self.maintainers_file: 216 _found_flag = False 217 try: 218 tests = self.maintainers_file[item].get('tests', []) 219 for i_test in tests: 220 if component_name in i_test: 221 _found_flag = True 222 223 if _found_flag is True: 224 for path in self.maintainers_file[item]['files']: 225 path = path.replace('*','.*') 226 files_path.append(path) 227 except TypeError: 228 print("ERROR: Fail while parsing MAINTAINERS file at %s", component_name) 229 return files_path 230 231 def generate_json_report(self, coverage): 232 output_json = { 233 "components":[] 234 } 235 236 with open(coverage, 'r') as file: 237 parser = ijson.items(file, 'files') 238 for element in parser: 239 240 for i_json_component in self.json_object['components']: 241 json_component = {} 242 json_component["name"]=i_json_component["name"] 243 json_component["sub_components"] = i_json_component["sub_components"] 244 json_component["Comment"] = "" 245 files_path = [] 246 files_path = self.get_files_from_maintainers_file(i_json_component["name"]) 247 json_files = [] 248 if len(files_path) != 0: 249 for i_file in files_path: 250 for i_covered_file in element: 251 x = re.search(('.*'+i_file+'.*'), i_covered_file['file']) 252 if x: 253 file_name = i_covered_file['file'][i_covered_file['file'].rfind('/')+1:] 254 file_path = i_covered_file['file'] 255 file_coverage, file_lines, file_hit = self._calculate_coverage_of_file(i_covered_file) 256 json_file = { 257 "Name":file_name, 258 "Path":file_path, 259 "Lines": file_lines, 260 "Hit":file_hit, 261 "Coverage": file_coverage, 262 "Covered_Functions": [], 263 "Uncovered_Functions": [] 264 } 265 for i_fun in i_covered_file['functions']: 266 if i_fun['execution_count'] != 0: 267 json_covered_funciton ={ 268 "Name":i_fun['name'] 269 } 270 json_file['Covered_Functions'].append(json_covered_funciton) 271 for i_fun in i_covered_file['functions']: 272 if i_fun['execution_count'] == 0: 273 json_uncovered_funciton ={ 274 "Name":i_fun['name'] 275 } 276 json_file['Uncovered_Functions'].append(json_uncovered_funciton) 277 comp_exists = [x for x in json_files if x['Path'] == json_file['Path']] 278 if not comp_exists: 279 json_files.append(json_file) 280 json_component['files']=json_files 281 output_json['components'].append(json_component) 282 else: 283 json_component["files"] = [] 284 json_component["Comment"] = "Missed in maintainers.yml file." 285 output_json['components'].append(json_component) 286 287 return output_json 288 289 def _calculate_coverage_of_file(self, file): 290 tracked_lines = len(file['lines']) 291 covered_lines = 0 292 for line in file['lines']: 293 if line['count'] != 0: 294 covered_lines += 1 295 return ((covered_lines/tracked_lines)*100), tracked_lines, covered_lines 296 297 def save_json_report(self, output_path, json_object): 298 json_object = json.dumps(json_object, indent=4) 299 with open(output_path+'.json', "w") as outfile: 300 outfile.write(json_object) 301 302 def _find_char(self, path, str, n): 303 sep = path.split(str, n) 304 if len(sep) <= n: 305 return -1 306 return len(path) - len(sep[-1]) - len(str) 307 308 def _component_calculate_stats(self, json_component): 309 testsuites_count = 0 310 runnable_count = 0 311 build_only_count = 0 312 sim_only_count = 0 313 hw_only_count = 0 314 mixed_count = 0 315 for i_sub_component in json_component['sub_components']: 316 for i_testsuit in i_sub_component['test_suites']: 317 testsuites_count += 1 318 if i_testsuit['runnable'] is True: 319 runnable_count += 1 320 else: 321 build_only_count += 1 322 323 if i_testsuit['status'] == "hw_only": 324 hw_only_count += 1 325 elif i_testsuit['status'] == "sim_only": 326 sim_only_count += 1 327 else: 328 mixed_count += 1 329 return testsuites_count, runnable_count, build_only_count, sim_only_count, hw_only_count, mixed_count 330 331 def _xlsx_generate_summary_page(self, workbook, json_report): 332 # formats 333 header_format = workbook.add_format( 334 { 335 "bold": True, 336 "fg_color": "#538DD5", 337 "color":"white" 338 } 339 ) 340 cell_format = workbook.add_format( 341 { 342 'valign': 'vcenter' 343 } 344 ) 345 346 #generate summary page 347 worksheet = workbook.add_worksheet('Summary') 348 row = 0 349 col = 0 350 worksheet.write(row,col,"Components",header_format) 351 worksheet.write(row,col+1,"TestSuites",header_format) 352 worksheet.write(row,col+2,"Runnable",header_format) 353 worksheet.write(row,col+3,"Build only",header_format) 354 worksheet.write(row,col+4,"Simulators only",header_format) 355 worksheet.write(row,col+5,"Hardware only",header_format) 356 worksheet.write(row,col+6,"Mixed",header_format) 357 worksheet.write(row,col+7,"Coverage [%]",header_format) 358 worksheet.write(row,col+8,"Total Functions",header_format) 359 worksheet.write(row,col+9,"Uncovered Functions",header_format) 360 worksheet.write(row,col+10,"Comment",header_format) 361 row = 1 362 col = 0 363 for item in json_report['components']: 364 worksheet.write(row, col, item['name'],cell_format) 365 testsuites,runnable,build_only,sim_only,hw_only, mixed= self._component_calculate_stats(item) 366 worksheet.write(row,col+1,testsuites,cell_format) 367 worksheet.write(row,col+2,runnable,cell_format) 368 worksheet.write(row,col+3,build_only,cell_format) 369 worksheet.write(row,col+4,sim_only,cell_format) 370 worksheet.write(row,col+5,hw_only,cell_format) 371 worksheet.write(row,col+6,mixed,cell_format) 372 lines = 0 373 hit = 0 374 coverage = 0.0 375 total_funs = 0 376 uncovered_funs = 0 377 for i_file in item['files']: 378 lines += i_file['Lines'] 379 hit += i_file['Hit'] 380 total_funs += (len(i_file['Covered_Functions'])+len(i_file['Uncovered_Functions'])) 381 uncovered_funs += len(i_file['Uncovered_Functions']) 382 383 if lines != 0: 384 coverage = (hit/lines)*100 385 386 worksheet.write_number(row,col+7,coverage,workbook.add_format({'num_format':'#,##0.00'})) 387 worksheet.write_number(row,col+8,total_funs) 388 worksheet.write_number(row,col+9,uncovered_funs) 389 worksheet.write(row,col+10,item["Comment"],cell_format) 390 row += 1 391 col = 0 392 worksheet.conditional_format(1,col+7,row,col+7, {'type': 'data_bar', 393 'min_value': 0, 394 'max_value': 100, 395 'bar_color': '#3fd927', 396 'bar_solid': True, 397 }) 398 worksheet.autofit() 399 worksheet.set_default_row(15) 400 401 def generate_xlsx_report(self, json_report, output): 402 self.report_book = xlsxwriter.Workbook(output+".xlsx") 403 header_format = self.report_book.add_format( 404 { 405 "bold": True, 406 "fg_color": "#538DD5", 407 "color":"white" 408 } 409 ) 410 411 # Create a format to use in the merged range. 412 merge_format = self.report_book.add_format( 413 { 414 "bold": 1, 415 "align": "center", 416 "valign": "vcenter", 417 "fg_color": "#538DD5", 418 "color":"white" 419 } 420 ) 421 cell_format = self.report_book.add_format( 422 { 423 'valign': 'vcenter' 424 } 425 ) 426 427 self._xlsx_generate_summary_page(self.report_book, self.report_json) 428 row = 0 429 col = 0 430 for item in json_report['components']: 431 worksheet = self.report_book.add_worksheet(item['name']) 432 row = 0 433 col = 0 434 worksheet.write(row,col,"File Name",header_format) 435 worksheet.write(row,col+1,"File Path",header_format) 436 worksheet.write(row,col+2,"Coverage [%]",header_format) 437 worksheet.write(row,col+3,"Lines",header_format) 438 worksheet.write(row,col+4,"Hits",header_format) 439 worksheet.write(row,col+5,"Diff",header_format) 440 row += 1 441 col = 0 442 for i_file in item['files']: 443 worksheet.write(row,col,i_file['Path'][i_file['Path'].rfind('/')+1:],cell_format) 444 worksheet.write(row,col+1,i_file["Path"][(self._find_char(i_file["Path"],'/',3)+1):],cell_format) 445 worksheet.write_number(row,col+2,i_file["Coverage"],self.report_book.add_format({'num_format':'#,##0.00'})) 446 worksheet.write(row,col+3,i_file["Lines"],cell_format) 447 worksheet.write(row,col+4,i_file["Hit"],cell_format) 448 worksheet.write(row,col+5,i_file["Lines"]-i_file["Hit"],cell_format) 449 row += 1 450 col = 0 451 row += 1 452 col = 0 453 worksheet.conditional_format(1,col+2,row,col+2, {'type': 'data_bar', 454 'min_value': 0, 455 'max_value': 100, 456 'bar_color': '#3fd927', 457 'bar_solid': True, 458 }) 459 worksheet.merge_range(row,col,row,col+2, "Uncovered Functions", merge_format) 460 row += 1 461 worksheet.write(row,col,'Function Name',header_format) 462 worksheet.write(row,col+1,'Implementation File',header_format) 463 worksheet.write(row,col+2,'Comment',header_format) 464 row += 1 465 col = 0 466 for i_file in item['files']: 467 for i_uncov_fun in i_file['Uncovered_Functions']: 468 worksheet.write(row,col,i_uncov_fun["Name"],cell_format) 469 worksheet.write(row,col+1,i_file["Path"][self._find_char(i_file["Path"],'/',3)+1:],cell_format) 470 row += 1 471 col = 0 472 row += 1 473 col = 0 474 worksheet.write(row,col,"Components",header_format) 475 worksheet.write(row,col+1,"Sub-Components",header_format) 476 worksheet.write(row,col+2,"TestSuites",header_format) 477 worksheet.write(row,col+3,"Runnable",header_format) 478 worksheet.write(row,col+4,"Build only",header_format) 479 worksheet.write(row,col+5,"Simulation only",header_format) 480 worksheet.write(row,col+6,"Hardware only",header_format) 481 worksheet.write(row,col+7,"Mixed",header_format) 482 row += 1 483 col = 0 484 worksheet.write(row,col,item['name'],cell_format) 485 for i_sub_component in item['sub_components']: 486 testsuites_count = 0 487 runnable_count = 0 488 build_only_count = 0 489 sim_only_count = 0 490 hw_only_count = 0 491 mixed_count = 0 492 worksheet.write(row,col+1,i_sub_component['name'],cell_format) 493 for i_testsuit in i_sub_component['test_suites']: 494 testsuites_count += 1 495 if i_testsuit['runnable'] is True: 496 runnable_count += 1 497 else: 498 build_only_count += 1 499 500 if i_testsuit['status'] == "hw_only": 501 hw_only_count += 1 502 elif i_testsuit['status'] == "sim_only": 503 sim_only_count += 1 504 else: 505 mixed_count += 1 506 worksheet.write(row,col+2,testsuites_count,cell_format) 507 worksheet.write(row,col+3,runnable_count,cell_format) 508 worksheet.write(row,col+4,build_only_count,cell_format) 509 worksheet.write(row,col+5,sim_only_count,cell_format) 510 worksheet.write(row,col+6,hw_only_count,cell_format) 511 worksheet.write(row,col+7,mixed_count,cell_format) 512 row += 1 513 col = 0 514 515 worksheet.autofit() 516 worksheet.set_default_row(15) 517 self.report_book.close() 518 519def parse_args(): 520 parser = argparse.ArgumentParser(allow_abbrev=False) 521 parser.add_argument('-m','--maintainers', help='Path to maintainers.yml [Required]', required=True) 522 parser.add_argument('-t','--testplan', help='Path to testplan [Required]', required=True) 523 parser.add_argument('-c','--coverage', help='Path to components file [Required]', required=True) 524 parser.add_argument('-o','--output', help='Report name [Required]', required=True) 525 parser.add_argument('-f','--format', help='Output format (json, xlsx, all) [Required]', required=True) 526 527 args = parser.parse_args() 528 return args 529 530if __name__ == '__main__': 531 Json_report() 532