import optparse import re import sys from optparse import OptionParser # the gcov report follows certain pattern. Each file will have two lines # of report, from which we can extract the file name, total lines and coverage # percentage. def parse_gcov_report(gcov_input): per_file_coverage = {} total_coverage = None for line in sys.stdin: line = line.strip() # --First line of the coverage report (with file name in it)? match_obj = re.match("^File '(.*)'$", line) if match_obj: # fetch the file name from the first line of the report. current_file = match_obj.group(1) continue # -- Second line of the file report (with coverage percentage) match_obj = re.match("^Lines executed:(.*)% of (.*)", line) if match_obj: coverage = float(match_obj.group(1)) lines = int(match_obj.group(2)) if current_file is not None: per_file_coverage[current_file] = (coverage, lines) current_file = None else: # If current_file is not set, we reach the last line of report, # which contains the summarized coverage percentage. total_coverage = (coverage, lines) continue # If the line's pattern doesn't fall into the above categories. We # can simply ignore them since they're either empty line or doesn't # find executable lines of the given file. current_file = None return per_file_coverage, total_coverage def get_option_parser(): usage = "Parse the gcov output and generate more human-readable code " +\ "coverage report." parser = OptionParser(usage) parser.add_option( "--interested-files", "-i", dest="filenames", help="Comma separated files names. if specified, we will display " + "the coverage report only for interested source files. " + "Otherwise we will display the coverage report for all " + "source files." ) return parser def display_file_coverage(per_file_coverage, total_coverage): # To print out auto-adjustable column, we need to know the longest # length of file names. max_file_name_length = max( len(fname) for fname in per_file_coverage.keys() ) # -- Print header # size of separator is determined by 3 column sizes: # file name, coverage percentage and lines. header_template = \ "%" + str(max_file_name_length) + "s\t%s\t%s" separator = "-" * (max_file_name_length + 10 + 20) print header_template % ("Filename", "Coverage", "Lines") print separator # -- Print body # template for printing coverage report for each file. record_template = "%" + str(max_file_name_length) + "s\t%5.2f%%\t%10d" for fname, coverage_info in per_file_coverage.items(): coverage, lines = coverage_info print record_template % (fname, coverage, lines) # -- Print footer if total_coverage: print separator print record_template % ("Total", total_coverage[0], total_coverage[1]) def report_coverage(): parser = get_option_parser() (options, args) = parser.parse_args() interested_files = set() if options.filenames is not None: interested_files = set(f.strip() for f in options.filenames.split(',')) # To make things simple, right now we only read gcov report from the input per_file_coverage, total_coverage = parse_gcov_report(sys.stdin) # Check if we need to display coverage info for interested files. if len(interested_files): per_file_coverage = dict( (fname, per_file_coverage[fname]) for fname in interested_files if fname in per_file_coverage ) # If we only interested in several files, it makes no sense to report # the total_coverage total_coverage = None if not len(per_file_coverage): print >> sys.stderr, "Cannot find coverage info for the given files." return display_file_coverage(per_file_coverage, total_coverage) if __name__ == "__main__": report_coverage()