eric7/Unittest/UTTestResultsTree.py

branch
unittest
changeset 9066
a219ade50f7c
parent 9065
39405e6eba20
child 9069
938039ea15ca
diff -r 39405e6eba20 -r a219ade50f7c eric7/Unittest/UTTestResultsTree.py
--- a/eric7/Unittest/UTTestResultsTree.py	Mon May 16 17:22:43 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,611 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing a tree view and associated model to show the test result
-data.
-"""
-
-import contextlib
-import copy
-import locale
-
-from collections import Counter
-from operator import attrgetter
-
-from PyQt6.QtCore import (
-    pyqtSignal, pyqtSlot, Qt, QAbstractItemModel, QCoreApplication,
-    QModelIndex, QPoint
-)
-from PyQt6.QtGui import QBrush, QColor
-from PyQt6.QtWidgets import QMenu, QTreeView
-
-from EricWidgets.EricApplication import ericApp
-
-import Preferences
-
-from .Interfaces.UTExecutorBase import ResultCategory
-
-TopLevelId = 2 ** 32 - 1
-
-
-class TestResultsModel(QAbstractItemModel):
-    """
-    Class implementing the item model containing the test data.
-    
-    @signal summary(str) emitted whenever the model data changes. The element
-        is a summary of the test results of the model.
-    """
-    summary = pyqtSignal(str)
-    
-    Headers = [
-        QCoreApplication.translate("TestResultsModel", "Status"),
-        QCoreApplication.translate("TestResultsModel", "Name"),
-        QCoreApplication.translate("TestResultsModel", "Message"),
-        QCoreApplication.translate("TestResultsModel", "Duration [ms]"),
-    ]
-    
-    StatusColumn = 0
-    NameColumn = 1
-    MessageColumn = 2
-    DurationColumn = 3
-    
-    def __init__(self, parent=None):
-        """
-        Constructor
-        
-        @param parent reference to the parent object (defaults to None)
-        @type QObject (optional)
-        """
-        super().__init__(parent)
-        
-        if ericApp().usesDarkPalette():
-            self.__backgroundColors = {
-                ResultCategory.RUNNING: None,
-                ResultCategory.FAIL: QBrush(QColor("#880000")),
-                ResultCategory.OK: QBrush(QColor("#005500")),
-                ResultCategory.SKIP: QBrush(QColor("#3f3f3f")),
-                ResultCategory.PENDING: QBrush(QColor("#004768")),
-            }
-        else:
-            self.__backgroundColors = {
-                ResultCategory.RUNNING: None,
-                ResultCategory.FAIL: QBrush(QColor("#ff8080")),
-                ResultCategory.OK: QBrush(QColor("#c1ffba")),
-                ResultCategory.SKIP: QBrush(QColor("#c5c5c5")),
-                ResultCategory.PENDING: QBrush(QColor("#6fbaff")),
-            }
-        
-        self.__testResults = []
-    
-    def index(self, row, column, parent=QModelIndex()):
-        """
-        Public method to generate an index for the given row and column to
-        identify the item.
-        
-        @param row row for the index
-        @type int
-        @param column column for the index
-        @type int
-        @param parent index of the parent item (defaults to QModelIndex())
-        @type QModelIndex (optional)
-        @return index for the item
-        @rtype QModelIndex
-        """
-        if not self.hasIndex(row, column, parent):  # check bounds etc.
-            return QModelIndex()
-        
-        if not parent.isValid():
-            # top level item
-            return self.createIndex(row, column, TopLevelId)
-        else:
-            testResultIndex = parent.row()
-            return self.createIndex(row, column, testResultIndex)
-    
-    def data(self, index, role):
-        """
-        Public method to get the data for the various columns and roles.
-        
-        @param index index of the data to be returned
-        @type QModelIndex
-        @param role role designating the data to return
-        @type Qt.ItemDataRole
-        @return requested data item
-        @rtype Any
-        """
-        if not index.isValid():
-            return None
-        
-        row = index.row()
-        column = index.column()
-        idx = index.internalId()
-        
-        if role == Qt.ItemDataRole.DisplayRole:
-            if idx != TopLevelId:
-                if bool(self.__testResults[idx].extra):
-                    return self.__testResults[idx].extra[index.row()]
-                else:
-                    return None
-            elif column == TestResultsModel.StatusColumn:
-                return self.__testResults[row].status
-            elif column == TestResultsModel.NameColumn:
-                return self.__testResults[row].name
-            elif column == TestResultsModel.MessageColumn:
-                return self.__testResults[row].message
-            elif column == TestResultsModel.DurationColumn:
-                duration = self.__testResults[row].duration
-                return (
-                    ""
-                    if duration is None else
-                    locale.format_string("%.2f", duration, grouping=True)
-                )
-        elif role == Qt.ItemDataRole.ToolTipRole:
-            if idx == TopLevelId and column == TestResultsModel.NameColumn:
-                return self.__testResults[row].name
-        elif role == Qt.ItemDataRole.FontRole:
-            if idx != TopLevelId:
-                return Preferences.getEditorOtherFonts("MonospacedFont")
-        elif role == Qt.ItemDataRole.BackgroundRole:
-            if idx == TopLevelId:
-                testResult = self.__testResults[row]
-                with contextlib.suppress(KeyError):
-                    return self.__backgroundColors[testResult.category]
-        elif role == Qt.ItemDataRole.TextAlignmentRole:
-            if idx == TopLevelId and column == TestResultsModel.DurationColumn:
-                return Qt.AlignmentFlag.AlignRight
-        elif role == Qt.ItemDataRole.UserRole:      # __IGNORE_WARNING_Y102__
-            if idx == TopLevelId:
-                testresult = self.__testResults[row]
-                return (testresult.filename, testresult.lineno)
-        
-        return None
-    
-    def headerData(self, section, orientation,
-                   role=Qt.ItemDataRole.DisplayRole):
-        """
-        Public method to get the header string for the various sections.
-        
-        @param section section number
-        @type int
-        @param orientation orientation of the header
-        @type Qt.Orientation
-        @param role data role (defaults to Qt.ItemDataRole.DisplayRole)
-        @type Qt.ItemDataRole (optional)
-        @return header string of the section
-        @rtype str
-        """
-        if (
-            orientation == Qt.Orientation.Horizontal and
-            role == Qt.ItemDataRole.DisplayRole
-        ):
-            return TestResultsModel.Headers[section]
-        else:
-            return None
-    
-    def parent(self, index):
-        """
-        Public method to get the parent of the item pointed to by index.
-        
-        @param index index of the item
-        @type QModelIndex
-        @return index of the parent item
-        @rtype QModelIndex
-        """
-        if not index.isValid():
-            return QModelIndex()
-        
-        idx = index.internalId()
-        if idx == TopLevelId:
-            return QModelIndex()
-        else:
-            return self.index(idx, 0)
-    
-    def rowCount(self, parent=QModelIndex()):
-        """
-        Public method to get the number of row for a given parent index.
-        
-        @param parent index of the parent item (defaults to QModelIndex())
-        @type QModelIndex (optional)
-        @return number of rows
-        @rtype int
-        """
-        if not parent.isValid():
-            return len(self.__testResults)
-        
-        if (
-            parent.internalId() == TopLevelId and
-            parent.column() == 0 and
-            self.__testResults[parent.row()].extra is not None
-        ):
-            return len(self.__testResults[parent.row()].extra)
-        
-        return 0
-
-    def columnCount(self, parent=QModelIndex()):
-        """
-        Public method to get the number of columns.
-        
-        @param parent index of the parent item (defaults to QModelIndex())
-        @type QModelIndex (optional)
-        @return number of columns
-        @rtype int
-        """
-        if not parent.isValid():
-            return len(TestResultsModel.Headers)
-        else:
-            return 1
-    
-    def clear(self):
-        """
-        Public method to clear the model data.
-        """
-        self.beginResetModel()
-        self.__testResults.clear()
-        self.endResetModel()
-        
-        self.summary.emit("")
-    
-    def sort(self, column, order):
-        """
-        Public method to sort the model data by column in order.
-        
-        @param column sort column number
-        @type int
-        @param order sort order
-        @type Qt.SortOrder
-        """             # __IGNORE_WARNING_D234r__
-        def durationKey(result):
-            """
-            Function to generate a key for duration sorting
-            
-            @param result result object
-            @type UTTestResult
-            @return sort key
-            @rtype float
-            """
-            return result.duration or -1.0
-
-        self.beginResetModel()
-        reverse = order == Qt.SortOrder.DescendingOrder
-        if column == TestResultsModel.StatusColumn:
-            self.__testResults.sort(key=attrgetter('category', 'status'),
-                                    reverse=reverse)
-        elif column == TestResultsModel.NameColumn:
-            self.__testResults.sort(key=attrgetter('name'), reverse=reverse)
-        elif column == TestResultsModel.MessageColumn:
-            self.__testResults.sort(key=attrgetter('message'), reverse=reverse)
-        elif column == TestResultsModel.DurationColumn:
-            self.__testResults.sort(key=durationKey, reverse=reverse)
-        self.endResetModel()
-    
-    def getTestResults(self):
-        """
-        Public method to get the list of test results managed by the model.
-        
-        @return list of test results managed by the model
-        @rtype list of UTTestResult
-        """
-        return copy.deepcopy(self.__testResults)
-    
-    def setTestResults(self, testResults):
-        """
-        Public method to set the list of test results of the model.
-        
-        @param testResults test results to be managed by the model
-        @type list of UTTestResult
-        """
-        self.beginResetModel()
-        self.__testResults = copy.deepcopy(testResults)
-        self.endResetModel()
-        
-        self.summary.emit(self.__summary())
-    
-    def addTestResults(self, testResults):
-        """
-        Public method to add test results to the ones already managed by the
-        model.
-        
-        @param testResults test results to be added to the model
-        @type list of UTTestResult
-        """
-        firstRow = len(self.__testResults)
-        lastRow = firstRow + len(testResults) - 1
-        self.beginInsertRows(QModelIndex(), firstRow, lastRow)
-        self.__testResults.extend(testResults)
-        self.endInsertRows()
-        
-        self.summary.emit(self.__summary())
-    
-    def updateTestResults(self, testResults):
-        """
-        Public method to update the data of managed test result items.
-        
-        @param testResults test results to be updated
-        @type list of UTTestResult
-        """
-        minIndex = None
-        maxIndex = None
-        
-        testResultsToBeAdded = []
-        
-        for testResult in testResults:
-            for (index, currentResult) in enumerate(self.__testResults):
-                if currentResult.id == testResult.id:
-                    self.__testResults[index] = testResult
-                    if minIndex is None:
-                        minIndex = index
-                        maxIndex = index
-                    else:
-                        minIndex = min(minIndex, index)
-                        maxIndex = max(maxIndex, index)
-                    
-                    break
-            else:
-                # Test result with given id was not found.
-                # Just add it to the list (could be a sub test)
-                testResultsToBeAdded.append(testResult)
-        
-        if minIndex is not None:
-            self.dataChanged.emit(
-                self.index(minIndex, 0),
-                self.index(maxIndex, len(TestResultsModel.Headers) - 1)
-            )
-            
-            self.summary.emit(self.__summary())
-        
-        if testResultsToBeAdded:
-            self.addTestResults(testResultsToBeAdded)
-    
-    def getFailedTests(self):
-        """
-        Public method to extract the test ids of all failed tests.
-        
-        @return test ids of all failed tests
-        @rtype list of str
-        """
-        failedIds = [
-            res.id for res in self.__testResults if (
-                res.category == ResultCategory.FAIL and
-                not res.subtestResult
-            )
-        ]
-        return failedIds
-    
-    def __summary(self):
-        """
-        Private method to generate a test results summary text.
-        
-        @return test results summary text
-        @rtype str
-        """
-        if len(self.__testResults) == 0:
-            return self.tr("No results to show")
-        
-        counts = Counter(res.category for res in self.__testResults)
-        if all(
-            counts[category] == 0
-            for category in (ResultCategory.FAIL, ResultCategory.OK,
-                             ResultCategory.SKIP)
-        ):
-            return self.tr("Collected %n test(s)", "", len(self.__testResults))
-        
-        return self.tr(
-            "%n test(s)/subtest(s) total, {0} failed, {1} passed,"
-            " {2} skipped, {3} pending",
-            "", len(self.__testResults)
-        ).format(
-            counts[ResultCategory.FAIL],
-            counts[ResultCategory.OK],
-            counts[ResultCategory.SKIP],
-            counts[ResultCategory.PENDING]
-        )
-
-
-class TestResultsTreeView(QTreeView):
-    """
-    Class implementing a tree view to show the test result data.
-    
-    @signal goto(str, int) emitted to go to the position given by file name
-        and line number
-    """
-    goto = pyqtSignal(str, int)
-    
-    def __init__(self, parent=None):
-        """
-        Constructor
-        
-        @param parent reference to the parent widget (defaults to None)
-        @type QWidget (optional)
-        """
-        super().__init__(parent)
-        
-        self.setItemsExpandable(True)
-        self.setExpandsOnDoubleClick(False)
-        self.setSortingEnabled(True)
-        
-        self.header().setDefaultAlignment(Qt.AlignmentFlag.AlignCenter)
-        self.header().setSortIndicatorShown(False)
-        
-        self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
-        
-        # connect signals and slots
-        self.doubleClicked.connect(self.__gotoTestDefinition)
-        self.customContextMenuRequested.connect(self.__showContextMenu)
-        
-        self.header().sortIndicatorChanged.connect(self.sortByColumn)
-        self.header().sortIndicatorChanged.connect(
-            lambda column, order: self.header().setSortIndicatorShown(True))
-    
-    def reset(self):
-        """
-        Public method to reset the internal state of the view.
-        """
-        super().reset()
-        
-        self.resizeColumns()
-        self.spanFirstColumn(0, self.model().rowCount() - 1)
-    
-    def rowsInserted(self, parent, startRow, endRow):
-        """
-        Public method called when rows are inserted.
-        
-        @param parent model index of the parent item
-        @type QModelIndex
-        @param startRow first row been inserted
-        @type int
-        @param endRow last row been inserted
-        @type int
-        """
-        super().rowsInserted(parent, startRow, endRow)
-        
-        self.resizeColumns()
-        self.spanFirstColumn(startRow, endRow)
-    
-    def dataChanged(self, topLeft, bottomRight, roles=[]):
-        """
-        Public method called when the model data has changed.
-        
-        @param topLeft index of the top left element
-        @type QModelIndex
-        @param bottomRight index of the bottom right element
-        @type QModelIndex
-        @param roles list of roles changed (defaults to [])
-        @type list of Qt.ItemDataRole (optional)
-        """
-        super().dataChanged(topLeft, bottomRight, roles)
-        
-        self.resizeColumns()
-        while topLeft.parent().isValid():
-            topLeft = topLeft.parent()
-        while bottomRight.parent().isValid():
-            bottomRight = bottomRight.parent()
-        self.spanFirstColumn(topLeft.row(), bottomRight.row())
-    
-    def resizeColumns(self):
-        """
-        Public method to resize the columns to their contents.
-        """
-        for column in range(self.model().columnCount()):
-            self.resizeColumnToContents(column)
-    
-    def spanFirstColumn(self, startRow, endRow):
-        """
-        Public method to make the first column span the row for second level
-        items.
-        
-        These items contain the test results.
-        
-        @param startRow index of the first row to span
-        @type QModelIndex
-        @param endRow index of the last row (including) to span
-        @type QModelIndex
-        """
-        model = self.model()
-        for row in range(startRow, endRow + 1):
-            index = model.index(row, 0)
-            for i in range(model.rowCount(index)):
-                self.setFirstColumnSpanned(i, index, True)
-    
-    def __canonicalIndex(self, index):
-        """
-        Private method to create the canonical index for a given index.
-        
-        The canonical index is the index of the first column of the test
-        result entry (i.e. the top-level item). If the index is invalid,
-        None is returned.
-        
-        @param index index to determine the canonical index for
-        @type QModelIndex
-        @return index of the firt column of the associated top-level item index
-        @rtype QModelIndex
-        """
-        if not index.isValid():
-            return None
-        
-        while index.parent().isValid():  # find the top-level node
-            index = index.parent()
-        index = index.sibling(index.row(), 0)  # go to first column
-        return index
-    
-    @pyqtSlot(QModelIndex)
-    def __gotoTestDefinition(self, index):
-        """
-        Private slot to show the test definition.
-        
-        @param index index for the double-clicked item
-        @type QModelIndex
-        """
-        cindex = self.__canonicalIndex(index)
-        filename, lineno = self.model().data(cindex, Qt.ItemDataRole.UserRole)
-        if filename is not None:
-            if lineno is None:
-                lineno = 1
-            self.goto.emit(filename, lineno)
-    
-    @pyqtSlot(QPoint)
-    def __showContextMenu(self, pos):
-        """
-        Private slot to show the context menu.
-        
-        @param pos relative position for the context menu
-        @type QPoint
-        """
-        index = self.indexAt(pos)
-        cindex = self.__canonicalIndex(index)
-        
-        contextMenu = (
-            self.__createContextMenu(cindex)
-            if cindex else
-            self.__createBackgroundContextMenu()
-        )
-        contextMenu.exec(self.mapToGlobal(pos))
-    
-    def __createContextMenu(self, index):
-        """
-        Private method to create a context menu for the item pointed to by the
-        given index.
-        
-        @param index index of the item
-        @type QModelIndex
-        @return created context menu
-        @rtype QMenu
-        """
-        menu = QMenu(self)
-        if self.isExpanded(index):
-            menu.addAction(self.tr("Collapse"),
-                           lambda: self.collapse(index))
-        else:
-            act = menu.addAction(self.tr("Expand"),
-                                 lambda: self.expand(index))
-            act.setEnabled(self.model().hasChildren(index))
-        menu.addSeparator()
-        
-        act = menu.addAction(self.tr("Show Source"),
-                             lambda: self.__gotoTestDefinition(index))
-        act.setEnabled(
-            self.model().data(index, Qt.ItemDataRole.UserRole) is not None
-        )
-        menu.addSeparator()
-        
-        menu.addAction(self.tr("Collapse All"), self.collapseAll)
-        menu.addAction(self.tr("Expand All"), self.expandAll)
-        
-        return menu
-    
-    def __createBackgroundContextMenu(self):
-        """
-        Private method to create a context menu for the background.
-        
-        @return created context menu
-        @rtype QMenu
-        """
-        menu = QMenu(self)
-        menu.addAction(self.tr("Collapse All"), self.collapseAll)
-        menu.addAction(self.tr("Expand All"), self.expandAll)
-        
-        return menu
-
-#
-# eflag: noqa = M821, M822

eric ide

mercurial