Tue, 12 Dec 2023 16:43:51 +0100
Testing
- Extended the testing dialog to allow filtering of the result list based on the result status.
--- a/docs/changelog.md Tue Dec 12 09:35:39 2023 +0100 +++ b/docs/changelog.md Tue Dec 12 16:43:51 2023 +0100 @@ -22,6 +22,9 @@ external file manager. - Added an entry to the background context menu to show the project directory in an external file manager. +- Testing + - Extended the testing dialog to allow filtering of the result list + based on the result status. - Viewmanager - Added `Close Tabs to the Left` and `Close Tabs to the Right` context menu entries to the tabview view manager.
--- a/src/eric7/Testing/Interfaces/PytestExecutor.py Tue Dec 12 09:35:39 2023 +0100 +++ b/src/eric7/Testing/Interfaces/PytestExecutor.py Tue Dec 12 16:43:51 2023 +0100 @@ -179,6 +179,8 @@ if config.testNamePattern: args.append("-k") args.append(config.testNamePattern) +## + ##args.append("--collect-only") if config.testFilename: if config.testName: @@ -257,7 +259,15 @@ # tests collected elif data["event"] == "collected": self.collected.emit( - [(data["nodeid"], self.__nodeid2testname(data["nodeid"]), "")] + [ + ( + data["nodeid"], + self.__nodeid2testname(data["nodeid"]), + "", + data["filename"], + data["linenumber"], + ) + ] ) # test started
--- a/src/eric7/Testing/Interfaces/PytestRunner.py Tue Dec 12 09:35:39 2023 +0100 +++ b/src/eric7/Testing/Interfaces/PytestRunner.py Tue Dec 12 16:43:51 2023 +0100 @@ -173,6 +173,8 @@ "event": "collected", "nodeid": item.nodeid, "name": item.name, + "filename": item.location[0], + "linenumber": item.location[1], } )
--- a/src/eric7/Testing/Interfaces/TestExecutorBase.py Tue Dec 12 09:35:39 2023 +0100 +++ b/src/eric7/Testing/Interfaces/TestExecutorBase.py Tue Dec 12 16:43:51 2023 +0100 @@ -44,8 +44,8 @@ message: str = "" # short result message extra: list = None # additional information text duration: float = None # test duration - filename: str = None # file name of a failed test - lineno: int = None # line number of a failed test + filename: str = None # file name of a (failed) test + lineno: int = None # line number of a (failed) test subtestResult: bool = False # flag indicating the result of a subtest @@ -73,9 +73,10 @@ """ Base class for test framework specific implementations. - @signal collected(list of tuple of (str, str, str)) emitted after all tests - have been collected. Tuple elements are the test id, the test name and - a short description of the test. + @signal collected(list of tuple of (str, str, str, str, int)) emitted after all + tests have been collected. Tuple elements are the test id, the test name, + a short description of the test, the test file name and the line number of + the test. @signal collectError(list of tuple of (str, str)) emitted when errors are encountered during test collection. Tuple elements are the test name and the error message.
--- a/src/eric7/Testing/Interfaces/UnittestExecutor.py Tue Dec 12 09:35:39 2023 +0100 +++ b/src/eric7/Testing/Interfaces/UnittestExecutor.py Tue Dec 12 16:43:51 2023 +0100 @@ -197,7 +197,7 @@ # tests collected elif data["event"] == "collected": self.collected.emit( - [(t["id"], t["name"], t["description"]) for t in data["tests"]] + [(t["id"], t["name"], t["description"], "", 0) for t in data["tests"]] ) # test started
--- a/src/eric7/Testing/Interfaces/UnittestRunner.py Tue Dec 12 09:35:39 2023 +0100 +++ b/src/eric7/Testing/Interfaces/UnittestRunner.py Tue Dec 12 16:43:51 2023 +0100 @@ -255,7 +255,7 @@ @type unittest.TestSuite @return list of tuples containing the test case ID, the string representation and the short description - @rtype list of tuples of (str, str) + @rtype list of tuples of (str, str, str) """ testCases = [] for test in suite:
--- a/src/eric7/Testing/TestResultsTree.py Tue Dec 12 09:35:39 2023 +0100 +++ b/src/eric7/Testing/TestResultsTree.py Tue Dec 12 16:43:51 2023 +0100 @@ -20,6 +20,7 @@ QCoreApplication, QModelIndex, QPoint, + QSortFilterProxyModel, Qt, pyqtSignal, pyqtSlot, @@ -422,6 +423,60 @@ counts[TestResultCategory.PENDING], ) + def getStatusFilterList(self): + """ + Public method to get a list of the unique test result status. + + @return test result status + @rtype set of str + """ + return {t.status for t in self.__testResults} + + +class TestResultsFilterModel(QSortFilterProxyModel): + """ + Class implementing a filter model to filter the test results by status. + """ + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object (QObject) + """ + super().__init__(parent) + + self.__statusFilterString = "" + + def filterAcceptsRow(self, sourceRow, sourceParent): + """ + Public method to determine, if the row is acceptable. + + @param sourceRow row number in the source model + @type int + @param sourceParent index of the source item + @type QModelIndex + @return flag indicating acceptance + @rtype bool + """ + sm = self.sourceModel() + idx = sm.index(sourceRow, 0, sourceParent) + status = sm.data(idx, Qt.ItemDataRole.DisplayRole) + return ( + sourceParent.isValid() + or self.__statusFilterString == "" + or status == self.__statusFilterString + ) + + def setStatusFilterString(self, filterString): + """ + Public method to set the status filter string. + + @param filterString status filter string + @type str + """ + self.__statusFilterString = filterString + self.invalidateRowsFilter() class TestResultsTreeView(QTreeView): """ @@ -452,7 +507,7 @@ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) # connect signals and slots - self.doubleClicked.connect(self.__gotoTestDefinition) + self.activated.connect(self.__gotoTestDefinition) self.customContextMenuRequested.connect(self.__showContextMenu) self.header().sortIndicatorChanged.connect(self.sortByColumn) @@ -559,7 +614,7 @@ """ cindex = self.__canonicalIndex(index) filename, lineno = self.model().data(cindex, Qt.ItemDataRole.UserRole) - if filename is not None: + if filename: if lineno is None: lineno = 1 self.goto.emit(filename, lineno)
--- a/src/eric7/Testing/TestingWidget.py Tue Dec 12 09:35:39 2023 +0100 +++ b/src/eric7/Testing/TestingWidget.py Tue Dec 12 16:43:51 2023 +0100 @@ -33,7 +33,7 @@ from .Interfaces import Frameworks from .Interfaces.TestExecutorBase import TestConfig, TestResult, TestResultCategory from .Interfaces.TestFrameworkRegistry import TestFrameworkRegistry -from .TestResultsTree import TestResultsModel, TestResultsTreeView +from .TestResultsTree import TestResultsFilterModel, TestResultsModel, TestResultsTreeView from .Ui_TestingWidget import Ui_TestingWidget @@ -73,8 +73,10 @@ self.__resultsModel = TestResultsModel(self) self.__resultsModel.summary.connect(self.__setStatusLabel) + self.__resultFilterModel = TestResultsFilterModel(self) + self.__resultFilterModel.setSourceModel(self.__resultsModel) self.__resultsTree = TestResultsTreeView(self) - self.__resultsTree.setModel(self.__resultsModel) + self.__resultsTree.setModel(self.__resultFilterModel) self.__resultsTree.goto.connect(self.__showSource) self.resultsGroupBox.layout().addWidget(self.__resultsTree) @@ -99,6 +101,8 @@ ) self.testComboBox.lineEdit().setClearButtonEnabled(True) + self.__allFilter = self.tr("<all>") + # create some more dialog buttons for orchestration self.__showLogButton = self.buttonBox.addButton( self.tr("Show Output..."), QDialogButtonBox.ButtonRole.ActionRole @@ -932,8 +936,10 @@ name=name, id=id, message=desc, + filename=filename, + lineno=lineno, ) - for id, name, desc in testNames + for id, name, desc, filename, lineno in testNames ] self.__resultsModel.addTestResults(testResults) self.__resultsTree.resizeColumns() @@ -1029,6 +1035,7 @@ self.__testExecutor = None self.__adjustPendingState() + self.__updateStatusFilterComboBox() @pyqtSlot(int, float) def __testRunFinished(self, noTests, duration): @@ -1064,6 +1071,7 @@ executor. """ self.__resultsModel.clear() + self.statusFilterComboBox.clear() def __adjustPendingState(self): """ @@ -1202,6 +1210,26 @@ with contextlib.suppress(RuntimeError): editor.close() + @pyqtSlot(str) + def on_statusFilterComboBox_currentTextChanged(self, status): + """ + Private slot handling the selection of a status for items to be shown. + + @param status selected status + @type str + """ + # TODO: not yet implemented + if status == self.__allFilter: + status = "" + + self.__resultFilterModel.setStatusFilterString(status) + + def __updateStatusFilterComboBox(self): + statusFilters = self.__resultsModel.getStatusFilterList() + self.statusFilterComboBox.clear() + self.statusFilterComboBox.addItem(self.__allFilter) + self.statusFilterComboBox.addItems(sorted(statusFilters)) + class TestingWindow(EricMainWindow): """
--- a/src/eric7/Testing/TestingWidget.ui Tue Dec 12 09:35:39 2023 +0100 +++ b/src/eric7/Testing/TestingWidget.ui Tue Dec 12 16:43:51 2023 +0100 @@ -402,6 +402,46 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_5"> <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Status Filter:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="statusFilterComboBox"> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="toolTip"> + <string>Select the status of items to be shown (empty for all).</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> <widget class="QLabel" name="statusLabel"> <property name="text"> <string/> @@ -484,6 +524,7 @@ <tabstop>coverageCheckBox</tabstop> <tabstop>coverageEraseCheckBox</tabstop> <tabstop>failfastCheckBox</tabstop> + <tabstop>statusFilterComboBox</tabstop> </tabstops> <resources/> <connections>