DataViews/PyCoverageDialog.py

changeset 0
de9c2efb9d02
child 6
52e8c820d0dd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DataViews/PyCoverageDialog.py	Mon Dec 28 16:03:33 2009 +0000
@@ -0,0 +1,353 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2003 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a Python code coverage dialog.
+"""
+
+import sys
+import os
+import types
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+from Ui_PyCoverageDialog import Ui_PyCoverageDialog
+
+import Utilities
+from DebugClients.Python.coverage import coverage
+
+class PyCoverageDialog(QDialog, Ui_PyCoverageDialog):
+    """
+    Class implementing a dialog to display the collected code coverage data.
+    """
+    def __init__(self, parent = None):
+        """
+        Constructor
+        
+        @param parent parent widget (QWidget)
+        """
+        QDialog.__init__(self, parent)
+        self.setupUi(self)
+        
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
+        
+        self.summaryList.headerItem().setText(self.summaryList.columnCount(), "")
+        self.resultList.headerItem().setText(self.resultList.columnCount(), "")
+        
+        self.cancelled = False
+        self.path = '.'
+        self.reload = False
+        
+        self.excludeList = ['# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]']
+        
+        self.__menu = QMenu(self)
+        self.annotate = self.__menu.addAction(self.trUtf8('Annotate'), 
+            self.__annotate)
+        self.__menu.addAction(self.trUtf8('Annotate all'), self.__annotateAll)
+        self.__menu.addAction(self.trUtf8('Delete annotated files'), 
+            self.__deleteAnnotated)
+        self.__menu.addSeparator()
+        self.__menu.addAction(self.trUtf8('Erase Coverage Info'), self.__erase)
+        self.resultList.setContextMenuPolicy(Qt.CustomContextMenu)
+        self.connect(self.resultList, 
+                     SIGNAL('customContextMenuRequested(const QPoint &)'),
+                     self.__showContextMenu)
+    
+    def __format_lines(self, lines):
+        """
+        Private method to format a list of integers into string by coalescing groups.
+        
+        @param lines list of integers
+        @return string representing the list
+        """
+        pairs = []
+        lines.sort()
+        maxValue = lines[-1]
+        start = None
+        
+        i = lines[0]
+        while i <= maxValue:
+            try:
+                if start is None:
+                    start = i
+                ind = lines.index(i)
+                end = i
+                i += 1
+            except ValueError:
+                pairs.append((start, end))
+                start = None
+                if ind + 1 >= len(lines):
+                    break
+                i = lines[ind+1]
+        if start:
+            pairs.append((start, end))
+        
+        def stringify(pair):
+            """
+            Private helper function to generate a string representation of a pair
+            
+            @param pair pair of integers
+            """
+            start, end = pair
+            if start == end:
+                return "%d" % start
+            else:
+                return "%d-%d" % (start, end)
+        
+        return ", ".join(map(stringify, pairs))
+    
+    def __createResultItem(self, file, statements, executed, coverage, excluded, missing):
+        """
+        Private method to create an entry in the result list.
+        
+        @param file filename of file (string)
+        @param statements amount of statements (integer)
+        @param executed amount of executed statements (integer)
+        @param coverage percent of coverage (integer)
+        @param excluded list of excluded lines (string)
+        @param missing list of lines without coverage (string)
+        """
+        itm = QTreeWidgetItem(self.resultList, [
+            file, 
+            str(statements), 
+            str(executed),
+            "%d%%" % coverage, 
+            excluded, 
+            missing
+        ])
+        for col in range(1, 4):
+            itm.setTextAlignment(col, Qt.AlignRight)
+        
+    def start(self, cfn, fn):
+        """
+        Public slot to start the coverage data evaluation.
+        
+        @param cfn basename of the coverage file (string)
+        @param fn file or list of files or directory to be checked
+                (string or list of strings)
+        """
+        self.__cfn = cfn
+        self.__fn = fn
+        
+        self.basename = os.path.splitext(cfn)[0]
+        
+        self.cfn = "%s.coverage" % self.basename
+        
+        if type(fn) is types.ListType:
+            files = fn
+            self.path = os.path.dirname(cfn)
+        elif os.path.isdir(fn):
+            files = Utilities.direntries(fn, True, '*.py', False)
+            self.path = fn
+        else:
+            files = [fn]
+            self.path = os.path.dirname(cfn)
+        files.sort()
+        
+        cover = coverage(data_file = self.cfn)
+        cover.use_cache(True)
+        cover.load()
+        
+        # set the exclude pattern
+        self.excludeCombo.clear()
+        self.excludeCombo.addItems(self.excludeList)
+        
+        self.checkProgress.setMaximum(len(files))
+        QApplication.processEvents()
+        
+        total_statements = 0
+        total_executed = 0
+        
+        cover.exclude(self.excludeList[0])
+        progress = 0
+        
+        try:
+            # disable updates of the list for speed
+            self.resultList.setUpdatesEnabled(False)
+            self.resultList.setSortingEnabled(False)
+            
+            # now go through all the files
+            for file in files:
+                if self.cancelled:
+                    return
+                
+                statements, excluded, missing, readable  = cover.analysis2(file)[1:]
+                readableEx = excluded and self.__format_lines(excluded) or ''
+                n = len(statements)
+                m = n - len(missing)
+                if n > 0:
+                    pc = 100.0 * m / n
+                else:
+                    pc = 100.0
+                self.__createResultItem(file, str(n), str(m), pc, readableEx, readable)
+                
+                total_statements = total_statements + n
+                total_executed = total_executed + m
+                
+                progress += 1
+                self.checkProgress.setValue(progress)
+                QApplication.processEvents()
+        finally:
+            # reenable updates of the list
+            self.resultList.setSortingEnabled(True)
+            self.resultList.setUpdatesEnabled(True)
+            self.checkProgress.reset()
+        
+        # show summary info
+        if len(files) > 1:
+            if total_statements > 0:
+                pc = 100.0 * total_executed / total_statements
+            else:
+                pc = 100.0
+            itm = QTreeWidgetItem(self.summaryList, [
+                str(total_statements), 
+                str(total_executed),
+                "%d%%" % pc
+            ])
+            for col in range(0, 3):
+                itm.setTextAlignment(col, Qt.AlignRight)
+        
+        self.__finish()
+        
+    def __finish(self):
+        """
+        Private slot called when the action finished or the user pressed the button.
+        """
+        self.cancelled = True
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
+        QApplication.processEvents()
+        self.resultList.header().resizeSections(QHeaderView.ResizeToContents)
+        self.resultList.header().setStretchLastSection(True)
+        self.summaryList.header().resizeSections(QHeaderView.ResizeToContents)
+        self.summaryList.header().setStretchLastSection(True)
+        
+    def on_buttonBox_clicked(self, button):
+        """
+        Private slot called by a button of the button box clicked.
+        
+        @param button button that was clicked (QAbstractButton)
+        """
+        if button == self.buttonBox.button(QDialogButtonBox.Close):
+            self.close()
+        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
+            self.__finish()
+        
+    def __showContextMenu(self, coord):
+        """
+        Private slot to show the context menu of the listview.
+        
+        @param coord the position of the mouse pointer (QPoint)
+        """
+        itm = self.resultList.itemAt(coord)
+        if itm:
+            self.annotate.setEnabled(True)
+        else:
+            self.annotate.setEnabled(False)
+        self.__menu.popup(self.mapToGlobal(coord))
+        
+    def __annotate(self):
+        """
+        Private slot to handle the annotate context menu action.
+        
+        This method produce an annotated coverage file of the
+        selected file.
+        """
+        itm = self.resultList.currentItem()
+        fn = unicode(itm.text(0))
+        
+        cover = coverage(data_file = self.cfn)
+        cover.use_cache(True)
+        cover.exclude(self.excludeList[0])
+        cover.load()
+        cover.annotate([fn], None, True)
+        
+    def __annotateAll(self):
+        """
+        Private slot to handle the annotate all context menu action.
+        
+        This method produce an annotated coverage file of every
+        file listed in the listview.
+        """
+        amount = self.resultList.topLevelItemCount()
+        if amount == 0:
+            return
+        
+        # get list of all filenames
+        files = []
+        for index in range(amount):
+            itm = self.resultList.topLevelItem(index)
+            files.append(itm.text(0))
+        
+        cover = coverage(data_file = self.cfn)
+        cover.use_cache(True)
+        cover.exclude(self.excludeList[0])
+        cover.load()
+        
+        # now process them
+        progress = QProgressDialog(self.trUtf8("Annotating files..."), 
+            self.trUtf8("Abort"), 0, len(files), self)
+        progress.setMinimumDuration(0)
+        count = 0
+        
+        for file in files:
+            progress.setValue(count)
+            if progress.wasCanceled():
+                break
+            cover.annotate([file], None)#, True)
+            count += 1
+        
+        progress.setValue(len(files))
+        
+    def __erase(self):
+        """
+        Private slot to handle the erase context menu action.
+        
+        This method erases the collected coverage data that is
+        stored in the .coverage file.
+        """
+        cover = coverage(data_file = self.cfn)
+        cover.use_cache(True)
+        cover.load()
+        cover.erase()
+        
+        self.reloadButton.setEnabled(False)
+        self.resultList.clear()
+        self.summaryList.clear()
+        
+    def __deleteAnnotated(self):
+        """
+        Private slot to handle the delete annotated context menu action.
+        
+        This method deletes all annotated files. These are files
+        ending with ',cover'.
+        """
+        files = Utilities.direntries(self.path, True, '*,cover', False)
+        for file in files:
+            try:
+                os.remove(file)
+            except EnvironmentError:
+                pass
+
+    @pyqtSlot()
+    def on_reloadButton_clicked(self):
+        """
+        Private slot to reload the coverage info.
+        """
+        self.resultList.clear()
+        self.summaryList.clear()
+        self.reload = True
+        excludePattern = self.excludeCombo.currentText()
+        if excludePattern in self.excludeList:
+            self.excludeList.remove(excludePattern)
+        self.excludeList.insert(0, excludePattern)
+        self.cancelled = False
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
+        self.start(self.__cfn, self.__fn)

eric ide

mercurial