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
|
#!/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', 'Missed', 'Coverage', 'Functions', 'Executed'])
CoverageEntry = re.compile('^(.*) +(\d+) +(\d+) +([\d.]+)% +(\d+) +([\d.]+)%$')
def parse_file_coverage_line(line):
'''Parse @line as a summary of a file's coverage information.
>>> parse_file_coverage_line('foo.cpp 10 5 50.0% 10 0.0%')
('foo.cpp', FileCoverage(Regions=10, Missed=5, Coverage=50.0, Functions=10, Executed=0.0))
'''
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 = int(groups[2])
coverage = float(groups[3])
functions = int(groups[4])
executed = float(groups[5])
return (filename,
FileCoverage(regions, missed, coverage, functions, executed))
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.Coverage < kThresh * old_fc.Coverage 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, 0), \
FileCoverage(10, 7, 30.0, 10, 0))
Code coverage regression:
File: foo.cpp
Change in region coverage: -20.00%
Change in function coverage: 0.00%
No functions were added or removed.
No regions were added or removed.
>>> print_regression('foo.cpp', FileCoverage(10, 5, 50.0, 10, 0), \
FileCoverage(5, 4, 20.0, 5, 0))
Code coverage regression:
File: foo.cpp
Change in region coverage: -30.00%
Change in function coverage: 0.00%
Change in the number of functions: -5
Change in the number of regions: -5
'''
region_coverage_delta = new_fc.Coverage - old_fc.Coverage
func_coverage_delta = new_fc.Executed - old_fc.Executed
num_functions_delta = new_fc.Functions - old_fc.Functions
num_regions_delta = new_fc.Regions - old_fc.Regions
print("Code coverage regression:")
print(" File:", filename)
print(" Change in region coverage: {0:.2f}%".format(region_coverage_delta))
print(" Change in function coverage: {0:.2f}%".format(func_coverage_delta))
if num_functions_delta:
print(" Change in the number of functions:", num_functions_delta)
else:
print(" No functions were added or removed.")
if num_regions_delta:
print(" Change in the number of regions:", num_regions_delta)
else:
print(" No regions were added or removed.")
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)
|