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