eric7/DataViews/PyCoverageDialog.py

branch
unittest
changeset 9078
44d1d68096b6
parent 9070
eab09a1ab8ce
child 9084
ee36935f4edd
equal deleted inserted replaced
9075:f6f0236eacbc 9078:44d1d68096b6
5 5
6 """ 6 """
7 Module implementing a Python code coverage dialog. 7 Module implementing a Python code coverage dialog.
8 """ 8 """
9 9
10 import contextlib
11 import os 10 import os
12 import time 11 import time
13 12
14 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt 13 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl
14 from PyQt6.QtGui import QDesktopServices
15 from PyQt6.QtWidgets import ( 15 from PyQt6.QtWidgets import (
16 QDialog, QDialogButtonBox, QMenu, QHeaderView, QTreeWidgetItem, 16 QDialog, QDialogButtonBox, QMenu, QHeaderView, QTreeWidgetItem,
17 QApplication 17 QApplication
18 ) 18 )
19 19
20 from EricWidgets import EricMessageBox 20 from EricWidgets import EricMessageBox
21 from EricWidgets.EricApplication import ericApp 21 from EricWidgets.EricApplication import ericApp
22 from EricWidgets.EricProgressDialog import EricProgressDialog
23 22
24 from .Ui_PyCoverageDialog import Ui_PyCoverageDialog 23 from .Ui_PyCoverageDialog import Ui_PyCoverageDialog
25 24
26 import Utilities 25 import Utilities
27 from coverage import Coverage 26 from coverage import Coverage
59 self.cancelled = False 58 self.cancelled = False
60 self.path = '.' 59 self.path = '.'
61 self.reload = False 60 self.reload = False
62 61
63 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.addAction(self.tr("JSON Report"), self.__jsonReport)
67 self.__reportsMenu.addAction(self.tr("LCOV Report"), self.__lcovReport)
64 68
65 self.__menu = QMenu(self) 69 self.__menu = QMenu(self)
66 self.__menu.addSeparator() 70 self.__menu.addSeparator()
67 self.openAct = self.__menu.addAction( 71 self.openAct = self.__menu.addAction(
68 self.tr("Open"), self.__openFile) 72 self.tr("Open"), self.__openFile)
69 self.__menu.addSeparator() 73 self.__menu.addSeparator()
70 self.annotate = self.__menu.addAction( 74 self.__menu.addMenu(self.__reportsMenu)
71 self.tr('Annotate'), self.__annotate)
72 self.__menu.addAction(self.tr('Annotate all'), self.__annotateAll)
73 self.__menu.addAction(
74 self.tr('Delete annotated files'), self.__deleteAnnotated)
75 self.__menu.addSeparator() 75 self.__menu.addSeparator()
76 self.__menu.addAction(self.tr('Erase Coverage Info'), self.__erase) 76 self.__menu.addAction(self.tr('Erase Coverage Info'), self.__erase)
77 self.resultList.setContextMenuPolicy( 77 self.resultList.setContextMenuPolicy(
78 Qt.ContextMenuPolicy.CustomContextMenu) 78 Qt.ContextMenuPolicy.CustomContextMenu)
79 self.resultList.customContextMenuRequested.connect( 79 self.resultList.customContextMenuRequested.connect(
312 @param coord position of the mouse pointer 312 @param coord position of the mouse pointer
313 @type QPoint 313 @type QPoint
314 """ 314 """
315 itm = self.resultList.itemAt(coord) 315 itm = self.resultList.itemAt(coord)
316 if itm: 316 if itm:
317 self.annotate.setEnabled(True)
318 self.openAct.setEnabled(True) 317 self.openAct.setEnabled(True)
319 else: 318 else:
320 self.annotate.setEnabled(False)
321 self.openAct.setEnabled(False) 319 self.openAct.setEnabled(False)
320 self.__reportsMenu.setEnabled(
321 bool(self.resultList.topLevelItemCount()))
322 self.__menu.popup(self.mapToGlobal(coord)) 322 self.__menu.popup(self.mapToGlobal(coord))
323 323
324 def __openFile(self, itm=None): 324 def __openFile(self, itm=None):
325 """ 325 """
326 Private slot to open the selected file. 326 Private slot to open the selected file.
338 editor = vm.getOpenEditor(fn) 338 editor = vm.getOpenEditor(fn)
339 editor.codeCoverageShowAnnotations(coverageFile=self.cfn) 339 editor.codeCoverageShowAnnotations(coverageFile=self.cfn)
340 except KeyError: 340 except KeyError:
341 self.openFile.emit(fn) 341 self.openFile.emit(fn)
342 342
343 # TODO: Coverage.annotate is deprecated 343 def __prepareReportGeneration(self):
344 def __annotate(self): 344 """
345 """ 345 Private method to prepare a report generation.
346 Private slot to handle the annotate context menu action. 346
347 347 @return tuple containing a reference to the Coverage object and the
348 This method produces an annotated coverage file of the 348 list of files to report
349 selected file. 349 @rtype tuple of (Coverage, list of str)
350 """ 350 """
351 itm = self.resultList.currentItem() 351 count = self.resultList.topLevelItemCount()
352 fn = itm.text(0) 352 if count == 0:
353 return None, []
354
355 # get list of all filenames
356 files = [
357 self.resultList.topLevelItem(index).text(0)
358 for index in range(count)
359 ]
353 360
354 cover = Coverage(data_file=self.cfn) 361 cover = Coverage(data_file=self.cfn)
355 cover.exclude(self.excludeList[0]) 362 cover.exclude(self.excludeList[0])
356 cover.load() 363 cover.load()
357 cover.annotate([fn], None, True) 364
358 365 return cover, files
359 # TODO: Coverage.annotate is deprecated 366
360 def __annotateAll(self): 367 @pyqtSlot()
361 """ 368 def __htmlReport(self):
362 Private slot to handle the annotate all context menu action. 369 """
363 370 Private slot to generate a HTML report of the shown data.
364 This method produce an annotated coverage file of every 371 """
365 file listed in the listview. 372 from .PyCoverageHtmlReportDialog import PyCoverageHtmlReportDialog
366 """ 373
367 amount = self.resultList.topLevelItemCount() 374 dlg = PyCoverageHtmlReportDialog(os.path.dirname(self.cfn), self)
368 if amount == 0: 375 if dlg.exec() == QDialog.DialogCode.Accepted:
369 return 376 title, outputDirectory, extraCSS, openReport = dlg.getData()
370 377
371 # get list of all filenames 378 cover, files = self.__prepareReportGeneration()
372 files = [] 379 cover.html_report(morfs=files, directory=outputDirectory,
373 for index in range(amount): 380 ignore_errors=True, extra_css=extraCSS,
374 itm = self.resultList.topLevelItem(index) 381 title=title)
375 files.append(itm.text(0)) 382
376 383 if openReport:
377 cover = Coverage(data_file=self.cfn) 384 QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.join(
378 cover.exclude(self.excludeList[0]) 385 outputDirectory, "index.html")))
379 cover.load() 386
380 387 @pyqtSlot()
381 # now process them 388 def __jsonReport(self):
382 progress = EricProgressDialog( 389 """
383 self.tr("Annotating files..."), self.tr("Abort"), 390 Private slot to generate a JSON report of the shown data.
384 0, len(files), self.tr("%v/%m Files"), self) 391 """
385 progress.setMinimumDuration(0) 392 from .PyCoverageJsonReportDialog import PyCoverageJsonReportDialog
386 progress.setWindowTitle(self.tr("Coverage")) 393
387 394 dlg = PyCoverageJsonReportDialog(os.path.dirname(self.cfn), self)
388 for count, file in enumerate(files): 395 if dlg.exec() == QDialog.DialogCode.Accepted:
389 progress.setValue(count) 396 filename, compact = dlg.getData()
390 if progress.wasCanceled(): 397 cover, files = self.__prepareReportGeneration()
391 break 398 cover.json_report(morfs=files, outfile=filename,
392 cover.annotate([file], None) # , True) 399 ignore_errors=True, pretty_print=not compact)
393 400
394 progress.setValue(len(files)) 401 @pyqtSlot()
402 def __lcovReport(self):
403 """
404 Private slot to generate a LCOV report of the shown data.
405 """
406 from EricWidgets import EricPathPickerDialog
407 from EricWidgets.EricPathPicker import EricPathPickerModes
408
409 filename, ok = EricPathPickerDialog.getPath(
410 self,
411 self.tr("LCOV Report"),
412 self.tr("Enter the path of the output file:"),
413 mode=EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE,
414 path=os.path.join(os.path.dirname(self.cfn), "coverage.lcov"),
415 defaultDirectory=os.path.dirname(self.cfn),
416 filters=self.tr("LCOV Files (*.lcov);;All Files (*)")
417 )
418 if ok:
419 cover, files = self.__prepareReportGeneration()
420 cover.lcov_report(morfs=files, outfile=filename,
421 ignore_errors=True)
395 422
396 def __erase(self): 423 def __erase(self):
397 """ 424 """
398 Private slot to handle the erase context menu action. 425 Private slot to handle the erase context menu action.
399 426
405 cover.erase() 432 cover.erase()
406 433
407 self.reloadButton.setEnabled(False) 434 self.reloadButton.setEnabled(False)
408 self.resultList.clear() 435 self.resultList.clear()
409 self.summaryList.clear() 436 self.summaryList.clear()
410
411 def __deleteAnnotated(self):
412 """
413 Private slot to handle the delete annotated context menu action.
414
415 This method deletes all annotated files. These are files
416 ending with ',cover'.
417 """
418 files = Utilities.direntries(self.path, True, '*,cover', False)
419 for file in files:
420 with contextlib.suppress(OSError):
421 os.remove(file)
422 437
423 @pyqtSlot() 438 @pyqtSlot()
424 def on_reloadButton_clicked(self): 439 def on_reloadButton_clicked(self):
425 """ 440 """
426 Private slot to reload the coverage info. 441 Private slot to reload the coverage info.

eric ide

mercurial