DataViews/PyCoverageDialog.py

changeset 0
de9c2efb9d02
child 6
52e8c820d0dd
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a Python code coverage dialog.
8 """
9
10 import sys
11 import os
12 import types
13
14 from PyQt4.QtCore import *
15 from PyQt4.QtGui import *
16
17 from Ui_PyCoverageDialog import Ui_PyCoverageDialog
18
19 import Utilities
20 from DebugClients.Python.coverage import coverage
21
22 class PyCoverageDialog(QDialog, Ui_PyCoverageDialog):
23 """
24 Class implementing a dialog to display the collected code coverage data.
25 """
26 def __init__(self, parent = None):
27 """
28 Constructor
29
30 @param parent parent widget (QWidget)
31 """
32 QDialog.__init__(self, parent)
33 self.setupUi(self)
34
35 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
36 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
37
38 self.summaryList.headerItem().setText(self.summaryList.columnCount(), "")
39 self.resultList.headerItem().setText(self.resultList.columnCount(), "")
40
41 self.cancelled = False
42 self.path = '.'
43 self.reload = False
44
45 self.excludeList = ['# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]']
46
47 self.__menu = QMenu(self)
48 self.annotate = self.__menu.addAction(self.trUtf8('Annotate'),
49 self.__annotate)
50 self.__menu.addAction(self.trUtf8('Annotate all'), self.__annotateAll)
51 self.__menu.addAction(self.trUtf8('Delete annotated files'),
52 self.__deleteAnnotated)
53 self.__menu.addSeparator()
54 self.__menu.addAction(self.trUtf8('Erase Coverage Info'), self.__erase)
55 self.resultList.setContextMenuPolicy(Qt.CustomContextMenu)
56 self.connect(self.resultList,
57 SIGNAL('customContextMenuRequested(const QPoint &)'),
58 self.__showContextMenu)
59
60 def __format_lines(self, lines):
61 """
62 Private method to format a list of integers into string by coalescing groups.
63
64 @param lines list of integers
65 @return string representing the list
66 """
67 pairs = []
68 lines.sort()
69 maxValue = lines[-1]
70 start = None
71
72 i = lines[0]
73 while i <= maxValue:
74 try:
75 if start is None:
76 start = i
77 ind = lines.index(i)
78 end = i
79 i += 1
80 except ValueError:
81 pairs.append((start, end))
82 start = None
83 if ind + 1 >= len(lines):
84 break
85 i = lines[ind+1]
86 if start:
87 pairs.append((start, end))
88
89 def stringify(pair):
90 """
91 Private helper function to generate a string representation of a pair
92
93 @param pair pair of integers
94 """
95 start, end = pair
96 if start == end:
97 return "%d" % start
98 else:
99 return "%d-%d" % (start, end)
100
101 return ", ".join(map(stringify, pairs))
102
103 def __createResultItem(self, file, statements, executed, coverage, excluded, missing):
104 """
105 Private method to create an entry in the result list.
106
107 @param file filename of file (string)
108 @param statements amount of statements (integer)
109 @param executed amount of executed statements (integer)
110 @param coverage percent of coverage (integer)
111 @param excluded list of excluded lines (string)
112 @param missing list of lines without coverage (string)
113 """
114 itm = QTreeWidgetItem(self.resultList, [
115 file,
116 str(statements),
117 str(executed),
118 "%d%%" % coverage,
119 excluded,
120 missing
121 ])
122 for col in range(1, 4):
123 itm.setTextAlignment(col, Qt.AlignRight)
124
125 def start(self, cfn, fn):
126 """
127 Public slot to start the coverage data evaluation.
128
129 @param cfn basename of the coverage file (string)
130 @param fn file or list of files or directory to be checked
131 (string or list of strings)
132 """
133 self.__cfn = cfn
134 self.__fn = fn
135
136 self.basename = os.path.splitext(cfn)[0]
137
138 self.cfn = "%s.coverage" % self.basename
139
140 if type(fn) is types.ListType:
141 files = fn
142 self.path = os.path.dirname(cfn)
143 elif os.path.isdir(fn):
144 files = Utilities.direntries(fn, True, '*.py', False)
145 self.path = fn
146 else:
147 files = [fn]
148 self.path = os.path.dirname(cfn)
149 files.sort()
150
151 cover = coverage(data_file = self.cfn)
152 cover.use_cache(True)
153 cover.load()
154
155 # set the exclude pattern
156 self.excludeCombo.clear()
157 self.excludeCombo.addItems(self.excludeList)
158
159 self.checkProgress.setMaximum(len(files))
160 QApplication.processEvents()
161
162 total_statements = 0
163 total_executed = 0
164
165 cover.exclude(self.excludeList[0])
166 progress = 0
167
168 try:
169 # disable updates of the list for speed
170 self.resultList.setUpdatesEnabled(False)
171 self.resultList.setSortingEnabled(False)
172
173 # now go through all the files
174 for file in files:
175 if self.cancelled:
176 return
177
178 statements, excluded, missing, readable = cover.analysis2(file)[1:]
179 readableEx = excluded and self.__format_lines(excluded) or ''
180 n = len(statements)
181 m = n - len(missing)
182 if n > 0:
183 pc = 100.0 * m / n
184 else:
185 pc = 100.0
186 self.__createResultItem(file, str(n), str(m), pc, readableEx, readable)
187
188 total_statements = total_statements + n
189 total_executed = total_executed + m
190
191 progress += 1
192 self.checkProgress.setValue(progress)
193 QApplication.processEvents()
194 finally:
195 # reenable updates of the list
196 self.resultList.setSortingEnabled(True)
197 self.resultList.setUpdatesEnabled(True)
198 self.checkProgress.reset()
199
200 # show summary info
201 if len(files) > 1:
202 if total_statements > 0:
203 pc = 100.0 * total_executed / total_statements
204 else:
205 pc = 100.0
206 itm = QTreeWidgetItem(self.summaryList, [
207 str(total_statements),
208 str(total_executed),
209 "%d%%" % pc
210 ])
211 for col in range(0, 3):
212 itm.setTextAlignment(col, Qt.AlignRight)
213
214 self.__finish()
215
216 def __finish(self):
217 """
218 Private slot called when the action finished or the user pressed the button.
219 """
220 self.cancelled = True
221 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
222 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
223 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
224 QApplication.processEvents()
225 self.resultList.header().resizeSections(QHeaderView.ResizeToContents)
226 self.resultList.header().setStretchLastSection(True)
227 self.summaryList.header().resizeSections(QHeaderView.ResizeToContents)
228 self.summaryList.header().setStretchLastSection(True)
229
230 def on_buttonBox_clicked(self, button):
231 """
232 Private slot called by a button of the button box clicked.
233
234 @param button button that was clicked (QAbstractButton)
235 """
236 if button == self.buttonBox.button(QDialogButtonBox.Close):
237 self.close()
238 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
239 self.__finish()
240
241 def __showContextMenu(self, coord):
242 """
243 Private slot to show the context menu of the listview.
244
245 @param coord the position of the mouse pointer (QPoint)
246 """
247 itm = self.resultList.itemAt(coord)
248 if itm:
249 self.annotate.setEnabled(True)
250 else:
251 self.annotate.setEnabled(False)
252 self.__menu.popup(self.mapToGlobal(coord))
253
254 def __annotate(self):
255 """
256 Private slot to handle the annotate context menu action.
257
258 This method produce an annotated coverage file of the
259 selected file.
260 """
261 itm = self.resultList.currentItem()
262 fn = unicode(itm.text(0))
263
264 cover = coverage(data_file = self.cfn)
265 cover.use_cache(True)
266 cover.exclude(self.excludeList[0])
267 cover.load()
268 cover.annotate([fn], None, True)
269
270 def __annotateAll(self):
271 """
272 Private slot to handle the annotate all context menu action.
273
274 This method produce an annotated coverage file of every
275 file listed in the listview.
276 """
277 amount = self.resultList.topLevelItemCount()
278 if amount == 0:
279 return
280
281 # get list of all filenames
282 files = []
283 for index in range(amount):
284 itm = self.resultList.topLevelItem(index)
285 files.append(itm.text(0))
286
287 cover = coverage(data_file = self.cfn)
288 cover.use_cache(True)
289 cover.exclude(self.excludeList[0])
290 cover.load()
291
292 # now process them
293 progress = QProgressDialog(self.trUtf8("Annotating files..."),
294 self.trUtf8("Abort"), 0, len(files), self)
295 progress.setMinimumDuration(0)
296 count = 0
297
298 for file in files:
299 progress.setValue(count)
300 if progress.wasCanceled():
301 break
302 cover.annotate([file], None)#, True)
303 count += 1
304
305 progress.setValue(len(files))
306
307 def __erase(self):
308 """
309 Private slot to handle the erase context menu action.
310
311 This method erases the collected coverage data that is
312 stored in the .coverage file.
313 """
314 cover = coverage(data_file = self.cfn)
315 cover.use_cache(True)
316 cover.load()
317 cover.erase()
318
319 self.reloadButton.setEnabled(False)
320 self.resultList.clear()
321 self.summaryList.clear()
322
323 def __deleteAnnotated(self):
324 """
325 Private slot to handle the delete annotated context menu action.
326
327 This method deletes all annotated files. These are files
328 ending with ',cover'.
329 """
330 files = Utilities.direntries(self.path, True, '*,cover', False)
331 for file in files:
332 try:
333 os.remove(file)
334 except EnvironmentError:
335 pass
336
337 @pyqtSlot()
338 def on_reloadButton_clicked(self):
339 """
340 Private slot to reload the coverage info.
341 """
342 self.resultList.clear()
343 self.summaryList.clear()
344 self.reload = True
345 excludePattern = self.excludeCombo.currentText()
346 if excludePattern in self.excludeList:
347 self.excludeList.remove(excludePattern)
348 self.excludeList.insert(0, excludePattern)
349 self.cancelled = False
350 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
351 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
352 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
353 self.start(self.__cfn, self.__fn)

eric ide

mercurial