120 lines
3.5 KiB
Python
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()
|