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