2
0

compare_gcov_json.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #!/usr/bin/env python3
  2. #
  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
  10. #
  11. # Author: Alex Bennée <alex.bennee@linaro.org>
  12. #
  13. # SPDX-License-Identifier: GPL-2.0-or-later
  14. #
  15. import argparse
  16. import json
  17. import sys
  18. from pathlib import Path
  19. def create_parser():
  20. parser = argparse.ArgumentParser(
  21. prog='compare_gcov_json',
  22. description='analyse the differences in coverage between two runs')
  23. parser.add_argument('-a', type=Path, default=None,
  24. help=('First file to check'))
  25. parser.add_argument('-b', type=Path, default=None,
  26. help=('Second file to check'))
  27. parser.add_argument('--verbose', action='store_true', default=False,
  28. help=('A minimal verbosity level that prints the '
  29. 'overall result of the check/wait'))
  30. return parser
  31. # See https://gcovr.com/en/stable/output/json.html#json-format-reference
  32. def load_json(json_file_path: Path, verbose = False) -> dict[str, set[int]]:
  33. with open(json_file_path) as f:
  34. data = json.load(f)
  35. root_dir = json_file_path.absolute().parent
  36. covered_lines = dict()
  37. for filecov in data["files"]:
  38. file_path = Path(filecov["file"])
  39. # account for generated files - map into src tree
  40. resolved_path = Path(file_path).absolute()
  41. if resolved_path.is_relative_to(root_dir):
  42. file_path = resolved_path.relative_to(root_dir)
  43. # print(f"remapped {resolved_path} to {file_path}")
  44. lines = filecov["lines"]
  45. executed_lines = set(
  46. linecov["line_number"]
  47. for linecov in filecov["lines"]
  48. if linecov["count"] != 0 and not linecov["gcovr/noncode"]
  49. )
  50. # if this file has any coverage add it to the system
  51. if len(executed_lines) > 0:
  52. if verbose:
  53. print(f"file {file_path} {len(executed_lines)}/{len(lines)}")
  54. covered_lines[str(file_path)] = executed_lines
  55. return covered_lines
  56. def find_missing_files(first, second):
  57. """
  58. Return a list of files not covered in the second set
  59. """
  60. missing_files = []
  61. for f in sorted(first):
  62. file_a = first[f]
  63. try:
  64. file_b = second[f]
  65. except KeyError:
  66. missing_files.append(f)
  67. return missing_files
  68. def main():
  69. """
  70. Script entry point
  71. """
  72. parser = create_parser()
  73. args = parser.parse_args()
  74. if not args.a or not args.b:
  75. print("We need two files to compare")
  76. sys.exit(1)
  77. first_coverage = load_json(args.a, args.verbose)
  78. second_coverage = load_json(args.b, args.verbose)
  79. first_missing = find_missing_files(first_coverage,
  80. second_coverage)
  81. second_missing = find_missing_files(second_coverage,
  82. first_coverage)
  83. a_name = args.a.parent.name
  84. b_name = args.b.parent.name
  85. print(f"{b_name} missing coverage in {len(first_missing)} files")
  86. for f in first_missing:
  87. print(f" {f}")
  88. print(f"{a_name} missing coverage in {len(second_missing)} files")
  89. for f in second_missing:
  90. print(f" {f}")
  91. if __name__ == '__main__':
  92. main()