~sophie-middleton08/maus/devel

« back to all changes in this revision

Viewing changes to doc/doc_tools/coverage_tool.py

  • Committer: Chris Rogers
  • Date: 2012-10-03 07:19:33 UTC
  • mfrom: (659.1.40 release-candidate)
  • Revision ID: chris.rogers@stfc.ac.uk-20121003071933-kgrhvl1ec6w2jmug
Tags: MAUS-v0.3, MAUS-v0.3.3
MAUS-v0.3.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#  This file is part of MAUS: http://micewww.pp.rl.ac.uk/projects/maus
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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/>.
 
15
 
 
16
"""
 
17
Handler for lcov output.
 
18
 
 
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.
 
22
"""
 
23
 
 
24
import copy
 
25
import os
 
26
import json
 
27
import HTMLParser
 
28
 
 
29
class Coverage(): # pylint: disable=R0903
 
30
    """
 
31
    Data structure reflecting lcov coverage information
 
32
    """
 
33
    def __init__(self):
 
34
        """Initialise data to 0."""
 
35
        self.file = ""
 
36
        self.lines = {"percentage":0., "covered":0., "total":0.}
 
37
        self.functions = copy.deepcopy(self.lines)
 
38
        self.branches = copy.deepcopy(self.lines)
 
39
 
 
40
    def json_repr(self):
 
41
        """Return json representation of the data"""
 
42
        return {
 
43
            "file":self.file,
 
44
            "line":self.lines,
 
45
            "function":self.functions,
 
46
            "branch":self.branches
 
47
        }
 
48
 
 
49
    def __str__(self):
 
50
        """String representation of the data is a json.dumps of json_repr"""
 
51
        return json.dumps(self.json_repr())
 
52
 
 
53
class CoverageParser(HTMLParser.HTMLParser): # pylint: disable=R0904
 
54
    """
 
55
    Parse a lcov coverage html file to extract test coverage information
 
56
 
 
57
    To parse a file, use parse_file(file_name) which returns a list of Coverage
 
58
    objects.
 
59
 
 
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.
 
63
    """
 
64
    def __init__(self):
 
65
        """Initialise coverage and some flags"""
 
66
        HTMLParser.HTMLParser.__init__(self)
 
67
        self.coverage = []
 
68
        self.td_index = -1
 
69
        self.in_td = -1
 
70
        self.in_a = 0
 
71
 
 
72
    def handle_starttag(self, tag, attrs_list):
 
73
        """
 
74
        Handler for opening e.g. <tag> tag
 
75
 
 
76
        Handles tr, td, a tag
 
77
        """
 
78
        attrs_dict = {}
 
79
        for attr in attrs_list:
 
80
            attrs_dict[attr[0]] = attr[1]
 
81
        if tag == "tr":
 
82
            self.handle_tr_start(tag, attrs_dict)
 
83
        if tag == "td":
 
84
            self.handle_td_start(tag, attrs_dict)
 
85
        if tag == "a":
 
86
            self.in_a += 1
 
87
 
 
88
 
 
89
    def handle_endtag(self, tag):
 
90
        """
 
91
        Handler for closing e.g. </tag> tag
 
92
 
 
93
        Handles tr, td, a tag
 
94
        """
 
95
        if tag == "tr":
 
96
            if self.td_index == -1:
 
97
                self.coverage.pop()
 
98
        if tag == "a":
 
99
            self.in_a -= 1
 
100
        if tag == "td":
 
101
            self.in_td = -1
 
102
 
 
103
 
 
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:
 
107
            return
 
108
        self.coverage.append(Coverage())
 
109
        self.td_index = -1
 
110
 
 
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':
 
116
                self.td_index += 1
 
117
        self.in_td = 0
 
118
 
 
119
    def handle_data(self, data):
 
120
        """Handler for data"""
 
121
        if self.td_index == -1:
 
122
            return
 
123
        if self.in_td != 0:
 
124
            return
 
125
        if self.td_index == 0:
 
126
            if self.in_a > 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])
 
146
        if self.in_td > -1:
 
147
            self.in_td += 1
 
148
 
 
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():
 
153
            self.feed(line)
 
154
        return self.coverage
 
155
 
 
156
def total_coverage(coverage_items):
 
157
    """
 
158
    Sum the coverage of all items in the coverage_items list and return a
 
159
    Coverage() item with appropriate elements
 
160
    """    
 
161
    cover_sum = Coverage()
 
162
    cover_sum.file = 'total'
 
163
    for item in 'lines', 'functions', 'branches':
 
164
        covered = 0
 
165
        total = 0
 
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)
 
172
    return cover_sum
 
173
 
 
174
def maus_filter(coverage_item):
 
175
    """
 
176
    Return true for items that are in MAUS (i.e. not stl and 
 
177
    third_party). Else return false
 
178
    """    
 
179
    if coverage_item.file[0:3] != 'src':
 
180
        return False
 
181
    if coverage_item.file[-5:] == 'build':
 
182
        return False
 
183
    return True    
 
184
 
 
185
def datastructure_filter(coverage_item):
 
186
    """
 
187
    Return true for items that are in the DataStructure directory
 
188
    """
 
189
    if coverage_item.file.find('src/common_cpp/DataStructure') == 0:
 
190
        return True
 
191
    return False
 
192
 
 
193
def non_legacy_filter(coverage_item):
 
194
    """
 
195
    Return true for items that are not legacy (i.e. not in src/legacy). Else
 
196
    return false
 
197
    """
 
198
    if coverage_item.file[4:10] == 'legacy':
 
199
        return False
 
200
    return True
 
201
 
 
202
def main():
 
203
    """
 
204
    Extract coverage information from the lcov output and make a few different
 
205
    summary reports
 
206
    """
 
207
    coverage_list = CoverageParser().parse_file(
 
208
        os.path.expandvars('$MAUS_ROOT_DIR/doc/cpp_coverage/index.html')
 
209
    )   
 
210
    maus_coverage_list = [x for x in coverage_list if maus_filter(x)]
 
211
    for item in maus_coverage_list:
 
212
        print item.file
 
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)
 
223
 
 
224
if __name__ == "__main__":
 
225
    main()
 
226