forked from llvm-mirror/llvm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
check-coverage-regressions.py
executable file
·139 lines (114 loc) · 5.15 KB
/
check-coverage-regressions.py
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
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env python
'''Compare two coverage summaries for regressions.
You can create a coverage summary by using the `llvm-cov report` command.
Alternatively, you can use `utils/prepare-code-coverage-artifact.py` which
creates summaries as well as file-based html reports.
'''
from __future__ import print_function
import argparse
import collections
import re
import sys
# This threshold must be in [0, 1]. The lower the threshold, the less likely
# it is that a regression will be flagged and vice versa.
kThresh = 1.0
FileCoverage = collections.namedtuple('FileCoverage',
['Regions', 'MissedRegions', 'RegionCoverage',
'Functions', 'MissedFunctions', 'Executed',
'Lines', 'MissedLines', 'LineCoverage'])
CoverageEntry = re.compile(r'^(.*)'
r' +(\d+) +(\d+) +([\d.]+)%'
r' +(\d+) +(\d+) +([\d.]+)%'
r' +(\d+) +(\d+) +([\d.]+)%$')
def parse_file_coverage_line(line):
'''Parse @line as a summary of a file's coverage information.
>>> parse_file_coverage_line('report.cpp 5 2 60.00% 4 1 75.00% 13 4 69.23%')
('report.cpp', FileCoverage(\
Regions=5, MissedRegions=2, RegionCoverage=60.0, \
Functions=4, MissedFunctions=1, Executed=75.0, \
Lines=13, MissedLines=4, LineCoverage=69.23))
'''
m = re.match(CoverageEntry, line)
if not m:
print("Could not read coverage summary:", line)
exit(1)
groups = m.groups()
filename = groups[0].strip()
regions = int(groups[1])
missed_regions = int(groups[2])
region_coverage = float(groups[3])
functions = int(groups[4])
missed_functions = int(groups[5])
executed = float(groups[6])
lines = int(groups[7])
missed_lines = int(groups[8])
line_coverage = float(groups[9])
return (filename,
FileCoverage(regions, missed_regions, region_coverage,
functions, missed_functions, executed,
lines, missed_lines, line_coverage))
def parse_summary(path):
'''Parse the summary at @path. Return a dictionary mapping filenames to
FileCoverage instances.'''
with open(path, 'r') as f:
lines = f.readlines()
# Drop the header and the cell dividers. Include "TOTAL" in this list.
file_coverages = lines[2:-2] + [lines[-1]]
summary = {}
for line in file_coverages:
filename, fc = parse_file_coverage_line(line)
summary[filename] = fc
return summary
def find_coverage_regressions(old_coverage, new_coverage):
'''Given two coverage summaries, generate coverage regressions of the form:
(filename, old FileCoverage, new FileCoverage).'''
for filename in old_coverage.keys():
if filename not in new_coverage:
continue
old_fc = old_coverage[filename]
new_fc = new_coverage[filename]
if new_fc.RegionCoverage < kThresh * old_fc.RegionCoverage or \
new_fc.Executed < kThresh * old_fc.Executed:
yield (filename, old_fc, new_fc)
def print_regression(filename, old_fc, new_fc):
'''Pretty-print a coverage regression in @filename. @old_fc is the old
FileCoverage and @new_fc is the new one.
>>> print_regression('foo.cpp', \
FileCoverage(10, 5, 50.0, 10, 10, 0, 20, 10, 50.0), \
FileCoverage(10, 7, 30.0, 10, 10, 0, 20, 14, 30.0))
Code coverage regression:
File: foo.cpp
Change in function coverage: 0.00% (0/10 -> 0/10)
Change in line coverage : -20.00% (10/20 -> 6/20)
Change in region coverage : -20.00% (5/10 -> 3/10)
'''
func_coverage_delta = new_fc.Executed - old_fc.Executed
line_coverage_delta = new_fc.LineCoverage - old_fc.LineCoverage
region_coverage_delta = new_fc.RegionCoverage - old_fc.RegionCoverage
print("Code coverage regression:")
print(" File:", filename)
print(" Change in function coverage: {0:.2f}% ({1}/{2} -> {3}/{4})".format(
func_coverage_delta, old_fc.Functions - old_fc.MissedFunctions,
old_fc.Functions, new_fc.Functions - new_fc.MissedFunctions,
new_fc.Functions))
print(" Change in line coverage : {0:.2f}% ({1}/{2} -> {3}/{4})".format(
line_coverage_delta, old_fc.Lines - old_fc.MissedLines, old_fc.Lines,
new_fc.Lines - new_fc.MissedLines, new_fc.Lines))
print(" Change in region coverage : {0:.2f}% ({1}/{2} -> {3}/{4})".format(
region_coverage_delta, old_fc.Regions - old_fc.MissedRegions,
old_fc.Regions, new_fc.Regions - new_fc.MissedRegions, new_fc.Regions))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('old_summary', help='Path to the old coverage summary')
parser.add_argument('new_summary', help='Path to the new coverage summary')
args = parser.parse_args()
old_coverage = parse_summary(args.old_summary)
new_coverage = parse_summary(args.new_summary)
num_regressions = 0
for filename, old_fc, new_fc in \
find_coverage_regressions(old_coverage, new_coverage):
print_regression(filename, old_fc, new_fc)
num_regressions += 1
if num_regressions > 0:
exit(1)
exit(0)