parse_gcov_output.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. #!/usr/bin/env python
  2. # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  3. import optparse
  4. import re
  5. import sys
  6. # the gcov report follows certain pattern. Each file will have two lines
  7. # of report, from which we can extract the file name, total lines and coverage
  8. # percentage.
  9. def parse_gcov_report(gcov_input):
  10. per_file_coverage = {}
  11. total_coverage = None
  12. for line in sys.stdin:
  13. line = line.strip()
  14. # --First line of the coverage report (with file name in it)?
  15. match_obj = re.match("^File '(.*)'$", line)
  16. if match_obj:
  17. # fetch the file name from the first line of the report.
  18. current_file = match_obj.group(1)
  19. continue
  20. # -- Second line of the file report (with coverage percentage)
  21. match_obj = re.match("^Lines executed:(.*)% of (.*)", line)
  22. if match_obj:
  23. coverage = float(match_obj.group(1))
  24. lines = int(match_obj.group(2))
  25. if current_file is not None:
  26. per_file_coverage[current_file] = (coverage, lines)
  27. current_file = None
  28. else:
  29. # If current_file is not set, we reach the last line of report,
  30. # which contains the summarized coverage percentage.
  31. total_coverage = (coverage, lines)
  32. continue
  33. # If the line's pattern doesn't fall into the above categories. We
  34. # can simply ignore them since they're either empty line or doesn't
  35. # find executable lines of the given file.
  36. current_file = None
  37. return per_file_coverage, total_coverage
  38. def get_option_parser():
  39. usage = (
  40. "Parse the gcov output and generate more human-readable code "
  41. + "coverage report."
  42. )
  43. parser = optparse.OptionParser(usage)
  44. parser.add_option(
  45. "--interested-files",
  46. "-i",
  47. dest="filenames",
  48. help="Comma separated files names. if specified, we will display "
  49. + "the coverage report only for interested source files. "
  50. + "Otherwise we will display the coverage report for all "
  51. + "source files.",
  52. )
  53. return parser
  54. def display_file_coverage(per_file_coverage, total_coverage):
  55. # To print out auto-adjustable column, we need to know the longest
  56. # length of file names.
  57. max_file_name_length = max(len(fname) for fname in per_file_coverage.keys())
  58. # -- Print header
  59. # size of separator is determined by 3 column sizes:
  60. # file name, coverage percentage and lines.
  61. header_template = "%" + str(max_file_name_length) + "s\t%s\t%s"
  62. separator = "-" * (max_file_name_length + 10 + 20)
  63. print(
  64. header_template % ("Filename", "Coverage", "Lines")
  65. ) # noqa: E999 T25377293 Grandfathered in
  66. print(separator)
  67. # -- Print body
  68. # template for printing coverage report for each file.
  69. record_template = "%" + str(max_file_name_length) + "s\t%5.2f%%\t%10d"
  70. for fname, coverage_info in per_file_coverage.items():
  71. coverage, lines = coverage_info
  72. print(record_template % (fname, coverage, lines))
  73. # -- Print footer
  74. if total_coverage:
  75. print(separator)
  76. print(record_template % ("Total", total_coverage[0], total_coverage[1]))
  77. def report_coverage():
  78. parser = get_option_parser()
  79. (options, args) = parser.parse_args()
  80. interested_files = set()
  81. if options.filenames is not None:
  82. interested_files = {f.strip() for f in options.filenames.split(",")}
  83. # To make things simple, right now we only read gcov report from the input
  84. per_file_coverage, total_coverage = parse_gcov_report(sys.stdin)
  85. # Check if we need to display coverage info for interested files.
  86. if len(interested_files):
  87. per_file_coverage = {
  88. fname: per_file_coverage[fname]
  89. for fname in interested_files
  90. if fname in per_file_coverage
  91. }
  92. # If we only interested in several files, it makes no sense to report
  93. # the total_coverage
  94. total_coverage = None
  95. if not len(per_file_coverage):
  96. print("Cannot find coverage info for the given files.", file=sys.stderr)
  97. return
  98. display_file_coverage(per_file_coverage, total_coverage)
  99. if __name__ == "__main__":
  100. report_coverage()