summaryrefslogtreecommitdiffstats
path: root/src/rocksdb/coverage/parse_gcov_output.py
blob: b9788ec815bd1f0da78ed287b80562c93f63132b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.

from __future__ import print_function

import optparse
import re
import sys

# 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 = optparse.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")
    )  # noqa: E999 T25377293 Grandfathered in
    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 = {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("Cannot find coverage info for the given files.", file=sys.stderr)
        return
    display_file_coverage(per_file_coverage, total_coverage)


if __name__ == "__main__":
    report_coverage()