3 # Compare output of two gcovr JSON reports and report differences. To
4 # generate the required output first:
5 # - create two build dirs with --enable-gcov
6 # - run set of tests in each
7 # - run make coverage-html in each
8 # - run gcovr --json --exclude-unreachable-branches \
9 # --print-summary -o coverage.json --root ../../ . *.p
11 # Author: Alex Bennée <alex.bennee@linaro.org>
13 # SPDX-License-Identifier: GPL-2.0-or-later
19 from pathlib
import Path
22 parser
= argparse
.ArgumentParser(
23 prog
='compare_gcov_json',
24 description
='analyse the differences in coverage between two runs')
26 parser
.add_argument('-a', type=Path
, default
=None,
27 help=('First file to check'))
29 parser
.add_argument('-b', type=Path
, default
=None,
30 help=('Second file to check'))
32 parser
.add_argument('--verbose', action
='store_true', default
=False,
33 help=('A minimal verbosity level that prints the '
34 'overall result of the check/wait'))
38 # See https://gcovr.com/en/stable/output/json.html#json-format-reference
39 def load_json(json_file_path
: Path
, verbose
= False) -> dict[str, set[int]]:
41 with
open(json_file_path
) as f
:
44 root_dir
= json_file_path
.absolute().parent
45 covered_lines
= dict()
47 for filecov
in data
["files"]:
48 file_path
= Path(filecov
["file"])
50 # account for generated files - map into src tree
51 resolved_path
= Path(file_path
).absolute()
52 if resolved_path
.is_relative_to(root_dir
):
53 file_path
= resolved_path
.relative_to(root_dir
)
54 # print(f"remapped {resolved_path} to {file_path}")
56 lines
= filecov
["lines"]
59 linecov
["line_number"]
60 for linecov
in filecov
["lines"]
61 if linecov
["count"] != 0 and not linecov
["gcovr/noncode"]
64 # if this file has any coverage add it to the system
65 if len(executed_lines
) > 0:
67 print(f
"file {file_path} {len(executed_lines)}/{len(lines)}")
68 covered_lines
[str(file_path
)] = executed_lines
72 def find_missing_files(first
, second
):
74 Return a list of files not covered in the second set
77 for f
in sorted(first
):
82 missing_files
.append(f
)
90 parser
= create_parser()
91 args
= parser
.parse_args()
93 if not args
.a
or not args
.b
:
94 print("We need two files to compare")
97 first_coverage
= load_json(args
.a
, args
.verbose
)
98 second_coverage
= load_json(args
.b
, args
.verbose
)
100 first_missing
= find_missing_files(first_coverage
,
103 second_missing
= find_missing_files(second_coverage
,
106 a_name
= args
.a
.parent
.name
107 b_name
= args
.b
.parent
.name
109 print(f
"{b_name} missing coverage in {len(first_missing)} files")
110 for f
in first_missing
:
113 print(f
"{a_name} missing coverage in {len(second_missing)} files")
114 for f
in second_missing
:
118 if __name__
== '__main__':