1
# This file is part of MAUS: http://micewww.pp.rl.ac.uk/projects/maus
3
# MAUS is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
8
# MAUS is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with MAUS. If not, see <http://www.gnu.org/licenses/>.
17
Handler for lcov output.
19
Parses the lcov output to generate a list of coverage items; then we use a
20
series of filters to select the appropriate files on which to report; and a
21
function to sum over the list to return total reporting numbers for the list.
29
class Coverage(): # pylint: disable=R0903
31
Data structure reflecting lcov coverage information
34
"""Initialise data to 0."""
36
self.lines = {"percentage":0., "covered":0., "total":0.}
37
self.functions = copy.deepcopy(self.lines)
38
self.branches = copy.deepcopy(self.lines)
41
"""Return json representation of the data"""
45
"function":self.functions,
46
"branch":self.branches
50
"""String representation of the data is a json.dumps of json_repr"""
51
return json.dumps(self.json_repr())
53
class CoverageParser(HTMLParser.HTMLParser): # pylint: disable=R0904
55
Parse a lcov coverage html file to extract test coverage information
57
To parse a file, use parse_file(file_name) which returns a list of Coverage
60
HTMLParser module uses a linear mode to parse html. Makes it quite horrible
61
to figure out where in the html structure I am, especially with e.g. nested
62
tables and such like. I use an obscure system of flags to do it - not nice.
65
"""Initialise coverage and some flags"""
66
HTMLParser.HTMLParser.__init__(self)
72
def handle_starttag(self, tag, attrs_list):
74
Handler for opening e.g. <tag> tag
79
for attr in attrs_list:
80
attrs_dict[attr[0]] = attr[1]
82
self.handle_tr_start(tag, attrs_dict)
84
self.handle_td_start(tag, attrs_dict)
89
def handle_endtag(self, tag):
91
Handler for closing e.g. </tag> tag
96
if self.td_index == -1:
104
def handle_tr_start(self, tag, attrs_dict): # pylint: disable=W0613
105
"""Handler for new table row"""
106
if self.td_index > -1 and self.td_index < 3:
108
self.coverage.append(Coverage())
111
def handle_td_start(self, tag, attrs_dict): # pylint: disable=W0613
112
"""Handler for new table element"""
113
if 'class' in attrs_dict:
114
if attrs_dict['class'][0:5] == 'cover' and \
115
attrs_dict['class'][5:8] != 'Bar':
119
def handle_data(self, data):
120
"""Handler for data"""
121
if self.td_index == -1:
125
if self.td_index == 0:
127
self.coverage[-1].file = data
128
if self.td_index == 1:
129
self.coverage[-1].lines['percentage'] = data
130
if self.td_index == 2:
131
words = data.split('/')
132
self.coverage[-1].lines['covered'] = int(words[0])
133
self.coverage[-1].lines['total'] = int(words[1])
134
if self.td_index == 3:
135
self.coverage[-1].functions['percentage'] = data
136
if self.td_index == 4:
137
words = data.split('/')
138
self.coverage[-1].functions['covered'] = int(words[0])
139
self.coverage[-1].functions['total'] = int(words[1])
140
if self.td_index == 5:
141
self.coverage[-1].branches['percentage'] = data
142
if self.td_index == 6:
143
words = data.split('/')
144
self.coverage[-1].branches['covered'] = int(words[0])
145
self.coverage[-1].branches['total'] = int(words[1])
149
def parse_file(self, file_name):
150
"""Parse a file with the specified file name"""
151
html_file = open(file_name)
152
for line in html_file.readlines():
156
def total_coverage(coverage_items):
158
Sum the coverage of all items in the coverage_items list and return a
159
Coverage() item with appropriate elements
161
cover_sum = Coverage()
162
cover_sum.file = 'total'
163
for item in 'lines', 'functions', 'branches':
166
for coverage in coverage_items:
167
covered += coverage.__dict__[item]['covered']
168
total += coverage.__dict__[item]['total']
169
cover_sum.__dict__[item]['covered'] = covered
170
cover_sum.__dict__[item]['total'] = total
171
cover_sum.__dict__[item]['percentage'] = float(covered)/float(total)
174
def maus_filter(coverage_item):
176
Return true for items that are in MAUS (i.e. not stl and
177
third_party). Else return false
179
if coverage_item.file[0:3] != 'src':
181
if coverage_item.file[-5:] == 'build':
185
def datastructure_filter(coverage_item):
187
Return true for items that are in the DataStructure directory
189
if coverage_item.file.find('src/common_cpp/DataStructure') == 0:
193
def non_legacy_filter(coverage_item):
195
Return true for items that are not legacy (i.e. not in src/legacy). Else
198
if coverage_item.file[4:10] == 'legacy':
204
Extract coverage information from the lcov output and make a few different
207
coverage_list = CoverageParser().parse_file(
208
os.path.expandvars('$MAUS_ROOT_DIR/doc/cpp_coverage/index.html')
210
maus_coverage_list = [x for x in coverage_list if maus_filter(x)]
211
for item in maus_coverage_list:
213
print 'ALL MAUS\n', total_coverage(maus_coverage_list)
214
non_legacy_list = [x for x in maus_coverage_list if non_legacy_filter(x)]
215
print 'NON-LEGACY MAUS\n', total_coverage(non_legacy_list)
216
legacy_list = [x for x in maus_coverage_list if not non_legacy_filter(x)]
217
print 'LEGACY MAUS\n', total_coverage(legacy_list)
218
not_ds_list = [x for x in maus_coverage_list if not datastructure_filter(x)]
219
print 'MAUS EXCLUDING DATASTRUCTURE\n', total_coverage(not_ds_list)
220
not_legacy_not_ds_list = [x for x in not_ds_list if non_legacy_filter(x)]
221
print 'MAUS EXCLUDING DATASTRUCTURE AND LEGACY\n', \
222
total_coverage(not_legacy_not_ds_list)
224
if __name__ == "__main__":