blob: 013266dbd73e63eb541c3de97a048209d24edce5 [file] [log] [blame]
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Intel Corporation
import argparse
import json
import re
import ijson
import xlsxwriter
import yaml
class ComponentStats:
def __init__(
self,
testsuites: int,
runnable: int,
build_only: int,
sim_only: int,
hw_only: int,
mixed: int,
):
self.testsuites = testsuites
self.runnable = runnable
self.build_only = build_only
self.sim_only = sim_only
self.hw_only = hw_only
self.mixed = mixed
class Json_report:
json_object = {"components": []}
simulators = [
'unit_testing',
'native',
'qemu',
'mps2/an385',
]
report_json = {}
def __init__(self):
args = parse_args()
self.parse_testplan(args.testplan)
self.maintainers_file = self.get_maintainers_file(args.maintainers)
self.report_json = self.generate_json_report(args.coverage)
if args.format == "json":
self.save_json_report(args.output, self.report_json)
elif args.format == "xlsx":
self.generate_xlsx_report(self.report_json, args.output)
elif args.format == "all":
self.save_json_report(args.output, self.report_json)
self.generate_xlsx_report(self.report_json, args.output)
else:
print("Format incorrect")
def get_maintainers_file(self, maintainers):
maintainers_file = ""
with open(maintainers) as file:
maintainers_file = yaml.safe_load(file)
file.close()
return maintainers_file
def _parse_testcase(self, testsuite, testcase):
if testcase['status'] == 'None':
testcase_name = testsuite['name']
component_name = testcase_name[: testcase_name.find('.')]
component = {"name": component_name, "sub_components": [], "files": []}
features = self.json_object['components']
known_component_flag = False
for item in features:
if component_name == item['name']:
component = item
known_component_flag = True
break
sub_component_name = testcase_name[testcase_name.find('.') :]
sub_component_name = sub_component_name[1:]
if idx := sub_component_name.find(".") > 0:
sub_component_name = sub_component_name[:idx]
if known_component_flag is False:
sub_component = {"name": sub_component_name, "test_suites": []}
test_suite = {
"name": testsuite['name'],
"path": testsuite['path'],
"platforms": [],
"runnable": testsuite['runnable'],
"status": "",
"test_cases": [],
}
test_case = {"name": testcase_name}
if any(platform in testsuite['platform'] for platform in self.simulators):
if test_suite['status'] == "":
test_suite['status'] = 'sim_only'
if test_suite['status'] == 'hw_only':
test_suite['status'] = 'mixed'
else:
if test_suite['status'] == "":
test_suite['status'] = 'hw_only'
if test_suite['status'] == 'sim_only':
test_suite['status'] = 'mixed'
test_suite['test_cases'].append(test_case)
test_suite['platforms'].append(testsuite['platform'])
sub_component["test_suites"].append(test_suite)
component['sub_components'].append(sub_component)
self.json_object['components'].append(component)
else:
sub_component = {}
sub_components = component['sub_components']
known_sub_component_flag = False
for i_sub_component in sub_components:
if sub_component_name == i_sub_component['name']:
sub_component = i_sub_component
known_sub_component_flag = True
break
if known_sub_component_flag is False:
sub_component = {"name": sub_component_name, "test_suites": []}
test_suite = {
"name": testsuite['name'],
"path": testsuite['path'],
"platforms": [],
"runnable": testsuite['runnable'],
"status": "",
"test_cases": [],
}
test_case = {"name": testcase_name}
if any(platform in testsuite['platform'] for platform in self.simulators):
if test_suite['status'] == "":
test_suite['status'] = 'sim_only'
if test_suite['status'] == 'hw_only':
test_suite['status'] = 'mixed'
else:
if test_suite['status'] == "":
test_suite['status'] = 'hw_only'
if test_suite['status'] == 'sim_only':
test_suite['status'] = 'mixed'
test_suite['test_cases'].append(test_case)
test_suite['platforms'].append(testsuite['platform'])
sub_component["test_suites"].append(test_suite)
component['sub_components'].append(sub_component)
else:
test_suite = {}
test_suites = sub_component['test_suites']
known_testsuite_flag = False
for i_testsuite in test_suites:
if testsuite['name'] == i_testsuite['name']:
test_suite = i_testsuite
known_testsuite_flag = True
break
if known_testsuite_flag is False:
test_suite = {
"name": testsuite['name'],
"path": testsuite['path'],
"platforms": [],
"runnable": testsuite['runnable'],
"status": "",
"test_cases": [],
}
test_case = {"name": testcase_name}
if any(platform in testsuite['platform'] for platform in self.simulators):
if test_suite['status'] == "":
test_suite['status'] = 'sim_only'
if test_suite['status'] == 'hw_only':
test_suite['status'] = 'mixed'
else:
if test_suite['status'] == "":
test_suite['status'] = 'hw_only'
if test_suite['status'] == 'sim_only':
test_suite['status'] = 'mixed'
test_suite['test_cases'].append(test_case)
test_suite['platforms'].append(testsuite['platform'])
sub_component["test_suites"].append(test_suite)
else:
if any(platform in testsuite['platform'] for platform in self.simulators):
if test_suite['status'] == "":
test_suite['status'] = 'sim_only'
if test_suite['status'] == 'hw_only':
test_suite['status'] = 'mixed'
else:
if test_suite['status'] == "":
test_suite['status'] = 'hw_only'
if test_suite['status'] == 'sim_only':
test_suite['status'] = 'mixed'
test_case = {}
test_cases = test_suite['test_cases']
known_testcase_flag = False
for i_testcase in test_cases:
if testcase_name == i_testcase['name']:
test_case = i_testcase
known_testcase_flag = True
break
if known_testcase_flag is False:
test_case = {"name": testcase_name}
test_suite['test_cases'].append(test_case)
def parse_testplan(self, testplan_path):
with open(testplan_path) as file:
parser = ijson.items(file, 'testsuites')
for element in parser:
for testsuite in element:
for testcase in testsuite['testcases']:
self._parse_testcase(testsuite, testcase)
def get_files_from_maintainers_file(self, component_name):
files_path = []
for item in self.maintainers_file:
_found_flag = False
try:
tests = self.maintainers_file[item].get('tests', [])
for i_test in tests:
if component_name in i_test:
_found_flag = True
if _found_flag is True:
for path in self.maintainers_file[item]['files']:
path = path.replace('*', '.*')
files_path.append(path)
except TypeError:
print("ERROR: Fail while parsing MAINTAINERS file at %s", component_name)
return files_path
def _generate_component_report(self, element, component) -> dict:
json_component = {}
json_component["name"] = component["name"]
json_component["sub_components"] = component["sub_components"]
json_component["Comment"] = ""
files_path = self.get_files_from_maintainers_file(component["name"])
if len(files_path) == 0:
json_component["files"] = []
json_component["Comment"] = "Missed in maintainers.yml file."
return json_component
json_files = []
for i_file in files_path:
for covered_file in element:
x = re.search(('.*' + i_file + '.*'), covered_file['file'])
if not x:
continue
file_name = covered_file['file'][covered_file['file'].rfind('/') + 1 :]
file_path = covered_file['file']
file_coverage, file_lines, file_hit = self._calculate_coverage_of_file(covered_file)
json_file = {
"Name": file_name,
"Path": file_path,
"Lines": file_lines,
"Hit": file_hit,
"Coverage": file_coverage,
"Covered_Functions": [],
"Uncovered_Functions": [],
}
for i_fun in covered_file['functions']:
if i_fun['execution_count'] != 0:
json_covered_funciton = {"Name": i_fun['name']}
json_file['Covered_Functions'].append(json_covered_funciton)
for i_fun in covered_file['functions']:
if i_fun['execution_count'] == 0:
json_uncovered_funciton = {"Name": i_fun['name']}
json_file['Uncovered_Functions'].append(json_uncovered_funciton)
comp_exists = [x for x in json_files if x['Path'] == json_file['Path']]
if not comp_exists:
json_files.append(json_file)
json_component['files'] = json_files
return json_component
def generate_json_report(self, coverage):
output_json = {"components": []}
with open(coverage) as file:
parser = ijson.items(file, 'files')
for element in parser:
for i_json_component in self.json_object['components']:
json_component = self._generate_component_report(element, i_json_component)
output_json['components'].append(json_component)
return output_json
def _calculate_coverage_of_file(self, file):
tracked_lines = len(file['lines'])
covered_lines = 0
for line in file['lines']:
if line['count'] != 0:
covered_lines += 1
return ((covered_lines / tracked_lines) * 100), tracked_lines, covered_lines
def save_json_report(self, output_path, json_object):
json_object = json.dumps(json_object, indent=4)
with open(output_path + '.json', "w") as outfile:
outfile.write(json_object)
def _find_char(self, path, str, n):
sep = path.split(str, n)
if len(sep) <= n:
return -1
return len(path) - len(sep[-1]) - len(str)
def _component_calculate_stats(self, json_component) -> ComponentStats:
testsuites_count = 0
runnable_count = 0
build_only_count = 0
sim_only_count = 0
hw_only_count = 0
mixed_count = 0
for i_sub_component in json_component['sub_components']:
for i_testsuit in i_sub_component['test_suites']:
testsuites_count += 1
if i_testsuit['runnable'] is True:
runnable_count += 1
else:
build_only_count += 1
if i_testsuit['status'] == "hw_only":
hw_only_count += 1
elif i_testsuit['status'] == "sim_only":
sim_only_count += 1
else:
mixed_count += 1
return ComponentStats(
testsuites_count,
runnable_count,
build_only_count,
sim_only_count,
hw_only_count,
mixed_count,
)
def _xlsx_generate_summary_page(self, workbook, json_report):
# formats
header_format = workbook.add_format(
{
"bold": True,
"fg_color": "#538DD5",
"font_color": "white",
}
)
cell_format = workbook.add_format(
{
'valign': 'vcenter',
}
)
# generate summary page
worksheet = workbook.add_worksheet('Summary')
row = 0
col = 0
worksheet.write(row, col, "Components", header_format)
worksheet.write(row, col + 1, "TestSuites", header_format)
worksheet.write(row, col + 2, "Runnable", header_format)
worksheet.write(row, col + 3, "Build only", header_format)
worksheet.write(row, col + 4, "Simulators only", header_format)
worksheet.write(row, col + 5, "Hardware only", header_format)
worksheet.write(row, col + 6, "Mixed", header_format)
worksheet.write(row, col + 7, "Coverage [%]", header_format)
worksheet.write(row, col + 8, "Total Functions", header_format)
worksheet.write(row, col + 9, "Uncovered Functions", header_format)
worksheet.write(row, col + 10, "Comment", header_format)
row = 1
col = 0
for item in json_report['components']:
worksheet.write(row, col, item['name'], cell_format)
stats = self._component_calculate_stats(item)
worksheet.write(row, col + 1, stats.testsuites, cell_format)
worksheet.write(row, col + 2, stats.runnable, cell_format)
worksheet.write(row, col + 3, stats.build_only, cell_format)
worksheet.write(row, col + 4, stats.sim_only, cell_format)
worksheet.write(row, col + 5, stats.hw_only, cell_format)
worksheet.write(row, col + 6, stats.mixed, cell_format)
lines = 0
hit = 0
coverage = 0.0
total_funs = 0
uncovered_funs = 0
for i_file in item['files']:
lines += i_file['Lines']
hit += i_file['Hit']
total_funs += len(i_file['Covered_Functions']) + len(i_file['Uncovered_Functions'])
uncovered_funs += len(i_file['Uncovered_Functions'])
if lines != 0:
coverage = (hit / lines) * 100
worksheet.write_number(
row, col + 7, coverage, workbook.add_format({'num_format': '#,##0.00'})
)
worksheet.write_number(row, col + 8, total_funs)
worksheet.write_number(row, col + 9, uncovered_funs)
worksheet.write(row, col + 10, item["Comment"], cell_format)
row += 1
col = 0
worksheet.conditional_format(
1,
col + 7,
row,
col + 7,
{
'type': 'data_bar',
'min_value': 0,
'max_value': 100,
'bar_color': '#3fd927',
'bar_solid': True,
},
)
worksheet.autofit()
worksheet.set_default_row(15)
def generate_xlsx_report(self, json_report, output):
self.report_book = xlsxwriter.Workbook(output + ".xlsx")
header_format = self.report_book.add_format(
{"bold": True, "fg_color": "#538DD5", "font_color": "white"}
)
# Create a format to use in the merged range.
merge_format = self.report_book.add_format(
{
"bold": 1,
"align": "center",
"valign": "vcenter",
"fg_color": "#538DD5",
"font_color": "white",
}
)
cell_format = self.report_book.add_format({'valign': 'vcenter'})
self._xlsx_generate_summary_page(self.report_book, self.report_json)
row = 0
col = 0
for item in json_report['components']:
worksheet = self.report_book.add_worksheet(item['name'])
row = 0
col = 0
worksheet.write(row, col, "File Name", header_format)
worksheet.write(row, col + 1, "File Path", header_format)
worksheet.write(row, col + 2, "Coverage [%]", header_format)
worksheet.write(row, col + 3, "Lines", header_format)
worksheet.write(row, col + 4, "Hits", header_format)
worksheet.write(row, col + 5, "Diff", header_format)
row += 1
col = 0
for i_file in item['files']:
worksheet.write(
row, col, i_file['Path'][i_file['Path'].rfind('/') + 1 :], cell_format
)
worksheet.write(
row,
col + 1,
i_file["Path"][(self._find_char(i_file["Path"], '/', 3) + 1) :],
cell_format,
)
worksheet.write_number(
row,
col + 2,
i_file["Coverage"],
self.report_book.add_format({'num_format': '#,##0.00'}),
)
worksheet.write(row, col + 3, i_file["Lines"], cell_format)
worksheet.write(row, col + 4, i_file["Hit"], cell_format)
worksheet.write(row, col + 5, i_file["Lines"] - i_file["Hit"], cell_format)
row += 1
col = 0
row += 1
col = 0
worksheet.conditional_format(
1,
col + 2,
row,
col + 2,
{
'type': 'data_bar',
'min_value': 0,
'max_value': 100,
'bar_color': '#3fd927',
'bar_solid': True,
},
)
worksheet.merge_range(row, col, row, col + 2, "Uncovered Functions", merge_format)
row += 1
worksheet.write(row, col, 'Function Name', header_format)
worksheet.write(row, col + 1, 'Implementation File', header_format)
worksheet.write(row, col + 2, 'Comment', header_format)
row += 1
col = 0
for i_file in item['files']:
for i_uncov_fun in i_file['Uncovered_Functions']:
worksheet.write(row, col, i_uncov_fun["Name"], cell_format)
worksheet.write(
row,
col + 1,
i_file["Path"][self._find_char(i_file["Path"], '/', 3) + 1 :],
cell_format,
)
row += 1
col = 0
row += 1
col = 0
worksheet.write(row, col, "Components", header_format)
worksheet.write(row, col + 1, "Sub-Components", header_format)
worksheet.write(row, col + 2, "TestSuites", header_format)
worksheet.write(row, col + 3, "Runnable", header_format)
worksheet.write(row, col + 4, "Build only", header_format)
worksheet.write(row, col + 5, "Simulation only", header_format)
worksheet.write(row, col + 6, "Hardware only", header_format)
worksheet.write(row, col + 7, "Mixed", header_format)
row += 1
col = 0
worksheet.write(row, col, item['name'], cell_format)
for i_sub_component in item['sub_components']:
testsuites_count = 0
runnable_count = 0
build_only_count = 0
sim_only_count = 0
hw_only_count = 0
mixed_count = 0
worksheet.write(row, col + 1, i_sub_component['name'], cell_format)
for i_testsuit in i_sub_component['test_suites']:
testsuites_count += 1
if i_testsuit['runnable'] is True:
runnable_count += 1
else:
build_only_count += 1
if i_testsuit['status'] == "hw_only":
hw_only_count += 1
elif i_testsuit['status'] == "sim_only":
sim_only_count += 1
else:
mixed_count += 1
worksheet.write(row, col + 2, testsuites_count, cell_format)
worksheet.write(row, col + 3, runnable_count, cell_format)
worksheet.write(row, col + 4, build_only_count, cell_format)
worksheet.write(row, col + 5, sim_only_count, cell_format)
worksheet.write(row, col + 6, hw_only_count, cell_format)
worksheet.write(row, col + 7, mixed_count, cell_format)
row += 1
col = 0
worksheet.autofit()
worksheet.set_default_row(15)
self.report_book.close()
def parse_args():
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument(
'-m', '--maintainers', help='Path to maintainers.yml [Required]', required=True
)
parser.add_argument('-t', '--testplan', help='Path to testplan [Required]', required=True)
parser.add_argument(
'-c', '--coverage', help='Path to components file [Required]', required=True
)
parser.add_argument('-o', '--output', help='Report name [Required]', required=True)
parser.add_argument(
'-f', '--format', help='Output format (json, xlsx, all) [Required]', required=True
)
args = parser.parse_args()
return args
if __name__ == '__main__':
Json_report()