]>
Commit | Line | Data |
---|---|---|
20effc67 | 1 | #!/usr/bin/env python |
f67539c2 | 2 | # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. |
20effc67 TL |
3 | |
4 | from __future__ import print_function | |
5 | ||
6 | import optparse | |
7c673cae FG |
7 | import re |
8 | import sys | |
9 | ||
7c673cae FG |
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. | |
13 | def 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 | ||
50 | def get_option_parser(): | |
51 | usage = "Parse the gcov output and generate more human-readable code " +\ | |
52 | "coverage report." | |
20effc67 | 53 | parser = optparse.OptionParser(usage) |
7c673cae FG |
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 | ||
65 | def 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) | |
20effc67 TL |
78 | print(header_template % ("Filename", "Coverage", "Lines")) # noqa: E999 T25377293 Grandfathered in |
79 | print(separator) | |
7c673cae FG |
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 | |
20effc67 | 87 | print(record_template % (fname, coverage, lines)) |
7c673cae FG |
88 | |
89 | # -- Print footer | |
90 | if total_coverage: | |
20effc67 TL |
91 | print(separator) |
92 | print(record_template % ("Total", total_coverage[0], total_coverage[1])) | |
7c673cae FG |
93 | |
94 | def 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): | |
20effc67 | 116 | print("Cannot find coverage info for the given files.", file=sys.stderr) |
7c673cae FG |
117 | return |
118 | display_file_coverage(per_file_coverage, total_coverage) | |
119 | ||
120 | if __name__ == "__main__": | |
121 | report_coverage() |