parse_gcov_output.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #!/usr/bin/env python2
  2. # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  3. import re
  4. import sys
  5. from optparse import OptionParser
  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 = "Parse the gcov output and generate more human-readable code " +\
  40. "coverage report."
  41. parser = OptionParser(usage)
  42. parser.add_option(
  43. "--interested-files", "-i",
  44. dest="filenames",
  45. help="Comma separated files names. if specified, we will display " +
  46. "the coverage report only for interested source files. " +
  47. "Otherwise we will display the coverage report for all " +
  48. "source files."
  49. )
  50. return parser
  51. def display_file_coverage(per_file_coverage, total_coverage):
  52. # To print out auto-adjustable column, we need to know the longest
  53. # length of file names.
  54. max_file_name_length = max(
  55. len(fname) for fname in per_file_coverage.keys()
  56. )
  57. # -- Print header
  58. # size of separator is determined by 3 column sizes:
  59. # file name, coverage percentage and lines.
  60. header_template = \
  61. "%" + str(max_file_name_length) + "s\t%s\t%s"
  62. separator = "-" * (max_file_name_length + 10 + 20)
  63. print header_template % ("Filename", "Coverage", "Lines") # noqa: E999 T25377293 Grandfathered in
  64. print separator
  65. # -- Print body
  66. # template for printing coverage report for each file.
  67. record_template = "%" + str(max_file_name_length) + "s\t%5.2f%%\t%10d"
  68. for fname, coverage_info in per_file_coverage.items():
  69. coverage, lines = coverage_info
  70. print record_template % (fname, coverage, lines)
  71. # -- Print footer
  72. if total_coverage:
  73. print separator
  74. print record_template % ("Total", total_coverage[0], total_coverage[1])
  75. def report_coverage():
  76. parser = get_option_parser()
  77. (options, args) = parser.parse_args()
  78. interested_files = set()
  79. if options.filenames is not None:
  80. interested_files = set(f.strip() for f in options.filenames.split(','))
  81. # To make things simple, right now we only read gcov report from the input
  82. per_file_coverage, total_coverage = parse_gcov_report(sys.stdin)
  83. # Check if we need to display coverage info for interested files.
  84. if len(interested_files):
  85. per_file_coverage = dict(
  86. (fname, per_file_coverage[fname]) for fname in interested_files
  87. if fname in per_file_coverage
  88. )
  89. # If we only interested in several files, it makes no sense to report
  90. # the total_coverage
  91. total_coverage = None
  92. if not len(per_file_coverage):
  93. print >> sys.stderr, "Cannot find coverage info for the given files."
  94. return
  95. display_file_coverage(per_file_coverage, total_coverage)
  96. if __name__ == "__main__":
  97. report_coverage()