You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qemu/scripts/coverage/compare_gcov_json.py

120 lines
3.5 KiB
Python

#!/usr/bin/env python3
#
# Compare output of two gcovr JSON reports and report differences. To
# generate the required output first:
# - create two build dirs with --enable-gcov
# - run set of tests in each
# - run make coverage-html in each
# - run gcovr --json --exclude-unreachable-branches \
# --print-summary -o coverage.json --root ../../ . *.p
#
# Author: Alex Bennée <alex.bennee@linaro.org>
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
import argparse
import json
import sys
from pathlib import Path
def create_parser():
parser = argparse.ArgumentParser(
prog='compare_gcov_json',
description='analyse the differences in coverage between two runs')
parser.add_argument('-a', type=Path, default=None,
help=('First file to check'))
parser.add_argument('-b', type=Path, default=None,
help=('Second file to check'))
parser.add_argument('--verbose', action='store_true', default=False,
help=('A minimal verbosity level that prints the '
'overall result of the check/wait'))
return parser
# See https://gcovr.com/en/stable/output/json.html#json-format-reference
def load_json(json_file_path: Path, verbose = False) -> dict[str, set[int]]:
with open(json_file_path) as f:
data = json.load(f)
root_dir = json_file_path.absolute().parent
covered_lines = dict()
for filecov in data["files"]:
file_path = Path(filecov["file"])
# account for generated files - map into src tree
resolved_path = Path(file_path).absolute()
if resolved_path.is_relative_to(root_dir):
file_path = resolved_path.relative_to(root_dir)
# print(f"remapped {resolved_path} to {file_path}")
lines = filecov["lines"]
executed_lines = set(
linecov["line_number"]
for linecov in filecov["lines"]
if linecov["count"] != 0 and not linecov["gcovr/noncode"]
)
# if this file has any coverage add it to the system
if len(executed_lines) > 0:
if verbose:
print(f"file {file_path} {len(executed_lines)}/{len(lines)}")
covered_lines[str(file_path)] = executed_lines
return covered_lines
def find_missing_files(first, second):
"""
Return a list of files not covered in the second set
"""
missing_files = []
for f in sorted(first):
file_a = first[f]
try:
file_b = second[f]
except KeyError:
missing_files.append(f)
return missing_files
def main():
"""
Script entry point
"""
parser = create_parser()
args = parser.parse_args()
if not args.a or not args.b:
print("We need two files to compare")
sys.exit(1)
first_coverage = load_json(args.a, args.verbose)
second_coverage = load_json(args.b, args.verbose)
first_missing = find_missing_files(first_coverage,
second_coverage)
second_missing = find_missing_files(second_coverage,
first_coverage)
a_name = args.a.parent.name
b_name = args.b.parent.name
print(f"{b_name} missing coverage in {len(first_missing)} files")
for f in first_missing:
print(f" {f}")
print(f"{a_name} missing coverage in {len(second_missing)} files")
for f in second_missing:
print(f" {f}")
if __name__ == '__main__':
main()