54 self.cancelled = False |
58 self.cancelled = False |
55 self.path = '.' |
59 self.path = '.' |
56 self.reload = False |
60 self.reload = False |
57 |
61 |
58 self.excludeList = ['# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]'] |
62 self.excludeList = ['# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]'] |
|
63 |
|
64 self.__reportsMenu = QMenu(self.tr("Create Report"), self) |
|
65 self.__reportsMenu.addAction(self.tr("HTML Report"), self.__htmlReport) |
|
66 self.__reportsMenu.addSeparator() |
|
67 self.__reportsMenu.addAction(self.tr("JSON Report"), self.__jsonReport) |
|
68 self.__reportsMenu.addAction(self.tr("LCOV Report"), self.__lcovReport) |
59 |
69 |
60 self.__menu = QMenu(self) |
70 self.__menu = QMenu(self) |
61 self.__menu.addSeparator() |
71 self.__menu.addSeparator() |
62 self.openAct = self.__menu.addAction( |
72 self.openAct = self.__menu.addAction( |
63 self.tr("Open"), self.__openFile) |
73 self.tr("Open"), self.__openFile) |
64 self.__menu.addSeparator() |
74 self.__menu.addSeparator() |
65 self.annotate = self.__menu.addAction( |
75 self.__menu.addMenu(self.__reportsMenu) |
66 self.tr('Annotate'), self.__annotate) |
|
67 self.__menu.addAction(self.tr('Annotate all'), self.__annotateAll) |
|
68 self.__menu.addAction( |
|
69 self.tr('Delete annotated files'), self.__deleteAnnotated) |
|
70 self.__menu.addSeparator() |
76 self.__menu.addSeparator() |
71 self.__menu.addAction(self.tr('Erase Coverage Info'), self.__erase) |
77 self.__menu.addAction(self.tr('Erase Coverage Info'), self.__erase) |
72 self.resultList.setContextMenuPolicy( |
78 self.resultList.setContextMenuPolicy( |
73 Qt.ContextMenuPolicy.CustomContextMenu) |
79 Qt.ContextMenuPolicy.CustomContextMenu) |
74 self.resultList.customContextMenuRequested.connect( |
80 self.resultList.customContextMenuRequested.connect( |
144 if statements != executed: |
161 if statements != executed: |
145 font = itm.font(0) |
162 font = itm.font(0) |
146 font.setBold(True) |
163 font.setBold(True) |
147 for col in range(itm.columnCount()): |
164 for col in range(itm.columnCount()): |
148 itm.setFont(col, font) |
165 itm.setFont(col, font) |
149 |
166 |
150 def start(self, cfn, fn): |
167 def start(self, cfn, fn): |
151 """ |
168 """ |
152 Public slot to start the coverage data evaluation. |
169 Public slot to start the coverage data evaluation. |
153 |
170 |
154 @param cfn basename of the coverage file (string) |
171 @param cfn basename of the coverage file |
|
172 @type str |
155 @param fn file or list of files or directory to be checked |
173 @param fn file or list of files or directory to be checked |
156 (string or list of strings) |
174 @type str or list of str |
157 """ |
175 """ |
|
176 # initialize the dialog |
|
177 self.resultList.clear() |
|
178 self.summaryList.clear() |
|
179 self.cancelled = False |
|
180 self.buttonBox.button( |
|
181 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
|
182 self.buttonBox.button( |
|
183 QDialogButtonBox.StandardButton.Cancel).setEnabled(True) |
|
184 self.buttonBox.button( |
|
185 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
186 |
158 self.__cfn = cfn |
187 self.__cfn = cfn |
159 self.__fn = fn |
188 self.__fn = fn |
160 |
189 |
161 self.basename = os.path.splitext(cfn)[0] |
190 self.cfn = ( |
162 |
191 cfn |
163 self.cfn = "{0}.coverage".format(self.basename) |
192 if cfn.endswith(".coverage") else |
|
193 "{0}.coverage".format(os.path.splitext(cfn)[0]) |
|
194 ) |
164 |
195 |
165 if isinstance(fn, list): |
196 if isinstance(fn, list): |
166 files = fn |
197 files = fn |
167 self.path = os.path.dirname(cfn) |
198 self.path = os.path.dirname(cfn) |
168 elif os.path.isdir(fn): |
199 elif os.path.isdir(fn): |
269 QHeaderView.ResizeMode.ResizeToContents) |
300 QHeaderView.ResizeMode.ResizeToContents) |
270 self.resultList.header().setStretchLastSection(True) |
301 self.resultList.header().setStretchLastSection(True) |
271 self.summaryList.header().resizeSections( |
302 self.summaryList.header().resizeSections( |
272 QHeaderView.ResizeMode.ResizeToContents) |
303 QHeaderView.ResizeMode.ResizeToContents) |
273 self.summaryList.header().setStretchLastSection(True) |
304 self.summaryList.header().setStretchLastSection(True) |
274 |
305 |
275 def on_buttonBox_clicked(self, button): |
306 def on_buttonBox_clicked(self, button): |
276 """ |
307 """ |
277 Private slot called by a button of the button box clicked. |
308 Private slot called by a button of the button box clicked. |
278 |
309 |
279 @param button button that was clicked (QAbstractButton) |
310 @param button button that was clicked |
|
311 @type QAbstractButton |
280 """ |
312 """ |
281 if button == self.buttonBox.button( |
313 if button == self.buttonBox.button( |
282 QDialogButtonBox.StandardButton.Close |
314 QDialogButtonBox.StandardButton.Close |
283 ): |
315 ): |
284 self.close() |
316 self.close() |
285 elif button == self.buttonBox.button( |
317 elif button == self.buttonBox.button( |
286 QDialogButtonBox.StandardButton.Cancel |
318 QDialogButtonBox.StandardButton.Cancel |
287 ): |
319 ): |
288 self.__finish() |
320 self.__finish() |
289 |
321 |
290 def __showContextMenu(self, coord): |
322 def __showContextMenu(self, coord): |
291 """ |
323 """ |
292 Private slot to show the context menu of the listview. |
324 Private slot to show the context menu of the listview. |
293 |
325 |
294 @param coord the position of the mouse pointer (QPoint) |
326 @param coord position of the mouse pointer |
|
327 @type QPoint |
295 """ |
328 """ |
296 itm = self.resultList.itemAt(coord) |
329 itm = self.resultList.itemAt(coord) |
297 if itm: |
330 if itm: |
298 self.annotate.setEnabled(True) |
|
299 self.openAct.setEnabled(True) |
331 self.openAct.setEnabled(True) |
300 else: |
332 else: |
301 self.annotate.setEnabled(False) |
|
302 self.openAct.setEnabled(False) |
333 self.openAct.setEnabled(False) |
|
334 self.__reportsMenu.setEnabled( |
|
335 bool(self.resultList.topLevelItemCount())) |
303 self.__menu.popup(self.mapToGlobal(coord)) |
336 self.__menu.popup(self.mapToGlobal(coord)) |
304 |
337 |
305 def __openFile(self, itm=None): |
338 def __openFile(self, itm=None): |
306 """ |
339 """ |
307 Private slot to open the selected file. |
340 Private slot to open the selected file. |
308 |
341 |
309 @param itm reference to the item to be opened (QTreeWidgetItem) |
342 @param itm reference to the item to be opened |
|
343 @type QTreeWidgetItem |
310 """ |
344 """ |
311 if itm is None: |
345 if itm is None: |
312 itm = self.resultList.currentItem() |
346 itm = self.resultList.currentItem() |
313 fn = itm.text(0) |
347 fn = itm.text(0) |
314 |
348 |
315 vm = ericApp().getObject("ViewManager") |
349 try: |
316 vm.openSourceFile(fn) |
350 vm = ericApp().getObject("ViewManager") |
317 editor = vm.getOpenEditor(fn) |
351 vm.openSourceFile(fn) |
318 editor.codeCoverageShowAnnotations() |
352 editor = vm.getOpenEditor(fn) |
319 |
353 editor.codeCoverageShowAnnotations(coverageFile=self.cfn) |
320 def __annotate(self): |
354 except KeyError: |
321 """ |
355 self.openFile.emit(fn) |
322 Private slot to handle the annotate context menu action. |
356 |
323 |
357 def __prepareReportGeneration(self): |
324 This method produce an annotated coverage file of the |
358 """ |
325 selected file. |
359 Private method to prepare a report generation. |
326 """ |
360 |
327 itm = self.resultList.currentItem() |
361 @return tuple containing a reference to the Coverage object and the |
328 fn = itm.text(0) |
362 list of files to report |
|
363 @rtype tuple of (Coverage, list of str) |
|
364 """ |
|
365 count = self.resultList.topLevelItemCount() |
|
366 if count == 0: |
|
367 return None, [] |
|
368 |
|
369 # get list of all filenames |
|
370 files = [ |
|
371 self.resultList.topLevelItem(index).text(0) |
|
372 for index in range(count) |
|
373 ] |
329 |
374 |
330 cover = Coverage(data_file=self.cfn) |
375 cover = Coverage(data_file=self.cfn) |
331 cover.exclude(self.excludeList[0]) |
376 cover.exclude(self.excludeList[0]) |
332 cover.load() |
377 cover.load() |
333 cover.annotate([fn], None, True) |
378 |
334 |
379 return cover, files |
335 def __annotateAll(self): |
380 |
336 """ |
381 @pyqtSlot() |
337 Private slot to handle the annotate all context menu action. |
382 def __htmlReport(self): |
338 |
383 """ |
339 This method produce an annotated coverage file of every |
384 Private slot to generate a HTML report of the shown data. |
340 file listed in the listview. |
385 """ |
341 """ |
386 from .PyCoverageHtmlReportDialog import PyCoverageHtmlReportDialog |
342 amount = self.resultList.topLevelItemCount() |
387 |
343 if amount == 0: |
388 dlg = PyCoverageHtmlReportDialog(os.path.dirname(self.cfn), self) |
344 return |
389 if dlg.exec() == QDialog.DialogCode.Accepted: |
345 |
390 title, outputDirectory, extraCSS, openReport = dlg.getData() |
346 # get list of all filenames |
391 |
347 files = [] |
392 cover, files = self.__prepareReportGeneration() |
348 for index in range(amount): |
393 cover.html_report(morfs=files, directory=outputDirectory, |
349 itm = self.resultList.topLevelItem(index) |
394 ignore_errors=True, extra_css=extraCSS, |
350 files.append(itm.text(0)) |
395 title=title) |
351 |
396 |
352 cover = Coverage(data_file=self.cfn) |
397 if openReport: |
353 cover.exclude(self.excludeList[0]) |
398 QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.join( |
354 cover.load() |
399 outputDirectory, "index.html"))) |
355 |
400 |
356 # now process them |
401 @pyqtSlot() |
357 progress = EricProgressDialog( |
402 def __jsonReport(self): |
358 self.tr("Annotating files..."), self.tr("Abort"), |
403 """ |
359 0, len(files), self.tr("%v/%m Files"), self) |
404 Private slot to generate a JSON report of the shown data. |
360 progress.setMinimumDuration(0) |
405 """ |
361 progress.setWindowTitle(self.tr("Coverage")) |
406 from .PyCoverageJsonReportDialog import PyCoverageJsonReportDialog |
362 |
407 |
363 for count, file in enumerate(files): |
408 dlg = PyCoverageJsonReportDialog(os.path.dirname(self.cfn), self) |
364 progress.setValue(count) |
409 if dlg.exec() == QDialog.DialogCode.Accepted: |
365 if progress.wasCanceled(): |
410 filename, compact = dlg.getData() |
366 break |
411 cover, files = self.__prepareReportGeneration() |
367 cover.annotate([file], None) # , True) |
412 cover.json_report(morfs=files, outfile=filename, |
368 |
413 ignore_errors=True, pretty_print=not compact) |
369 progress.setValue(len(files)) |
414 |
370 |
415 @pyqtSlot() |
|
416 def __lcovReport(self): |
|
417 """ |
|
418 Private slot to generate a LCOV report of the shown data. |
|
419 """ |
|
420 from EricWidgets import EricPathPickerDialog |
|
421 from EricWidgets.EricPathPicker import EricPathPickerModes |
|
422 |
|
423 filename, ok = EricPathPickerDialog.getPath( |
|
424 self, |
|
425 self.tr("LCOV Report"), |
|
426 self.tr("Enter the path of the output file:"), |
|
427 mode=EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE, |
|
428 path=os.path.join(os.path.dirname(self.cfn), "coverage.lcov"), |
|
429 defaultDirectory=os.path.dirname(self.cfn), |
|
430 filters=self.tr("LCOV Files (*.lcov);;All Files (*)") |
|
431 ) |
|
432 if ok: |
|
433 cover, files = self.__prepareReportGeneration() |
|
434 cover.lcov_report(morfs=files, outfile=filename, |
|
435 ignore_errors=True) |
|
436 |
371 def __erase(self): |
437 def __erase(self): |
372 """ |
438 """ |
373 Private slot to handle the erase context menu action. |
439 Private slot to handle the erase context menu action. |
374 |
440 |
375 This method erases the collected coverage data that is |
441 This method erases the collected coverage data that is |
380 cover.erase() |
446 cover.erase() |
381 |
447 |
382 self.reloadButton.setEnabled(False) |
448 self.reloadButton.setEnabled(False) |
383 self.resultList.clear() |
449 self.resultList.clear() |
384 self.summaryList.clear() |
450 self.summaryList.clear() |
385 |
451 |
386 def __deleteAnnotated(self): |
|
387 """ |
|
388 Private slot to handle the delete annotated context menu action. |
|
389 |
|
390 This method deletes all annotated files. These are files |
|
391 ending with ',cover'. |
|
392 """ |
|
393 files = Utilities.direntries(self.path, True, '*,cover', False) |
|
394 for file in files: |
|
395 with contextlib.suppress(OSError): |
|
396 os.remove(file) |
|
397 |
|
398 @pyqtSlot() |
452 @pyqtSlot() |
399 def on_reloadButton_clicked(self): |
453 def on_reloadButton_clicked(self): |
400 """ |
454 """ |
401 Private slot to reload the coverage info. |
455 Private slot to reload the coverage info. |
402 """ |
456 """ |
403 self.resultList.clear() |
|
404 self.summaryList.clear() |
|
405 self.reload = True |
457 self.reload = True |
406 excludePattern = self.excludeCombo.currentText() |
458 excludePattern = self.excludeCombo.currentText() |
407 if excludePattern in self.excludeList: |
459 if excludePattern in self.excludeList: |
408 self.excludeList.remove(excludePattern) |
460 self.excludeList.remove(excludePattern) |
409 self.excludeList.insert(0, excludePattern) |
461 self.excludeList.insert(0, excludePattern) |
410 self.cancelled = False |
|
411 self.buttonBox.button( |
|
412 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
|
413 self.buttonBox.button( |
|
414 QDialogButtonBox.StandardButton.Cancel).setEnabled(True) |
|
415 self.buttonBox.button( |
|
416 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
417 self.start(self.__cfn, self.__fn) |
462 self.start(self.__cfn, self.__fn) |
418 |
463 |
419 @pyqtSlot(QTreeWidgetItem, int) |
464 @pyqtSlot(QTreeWidgetItem, int) |
420 def on_resultList_itemActivated(self, item, column): |
465 def on_resultList_itemActivated(self, item, column): |
421 """ |
466 """ |