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