diff -r 22dab1be7953 -r 7f27bf3b50c3 eric7/Unittest/UnittestWidget.py --- a/eric7/Unittest/UnittestWidget.py Thu May 12 09:00:35 2022 +0200 +++ b/eric7/Unittest/UnittestWidget.py Fri May 13 17:23:21 2022 +0200 @@ -8,6 +8,7 @@ """ import enum +import locale import os from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QCoreApplication @@ -24,7 +25,9 @@ from .UTTestResultsTree import TestResultsModel, TestResultsTreeView from .Interfaces import Frameworks -from .Interfaces.UTExecutorBase import UTTestConfig, UTTestResult +from .Interfaces.UTExecutorBase import ( + UTTestConfig, UTTestResult, ResultCategory +) from .Interfaces.UTFrameworkRegistry import UTFrameworkRegistry import Preferences @@ -46,6 +49,8 @@ STOPPED = 2 # test run finished +# TODO: add a "Show Coverage" function using PyCoverageDialog + class UnittestWidget(QWidget, Ui_UnittestWidget): """ Class implementing a widget to orchestrate unit test execution. @@ -175,7 +180,7 @@ self.__insertDiscovery("") else: self.__insertDiscovery("") - self.__insertProg(testfile) + self.__insertTestFile(testfile) self.__insertTestName("") self.clearHistoriesButton.clicked.connect(self.clearRecent) @@ -253,7 +258,7 @@ widget.addItems(history) if current: - widget.setText(current) + widget.setEditText(current) @pyqtSlot(str) def __insertDiscovery(self, start): @@ -268,7 +273,7 @@ start) @pyqtSlot(str) - def __insertProg(self, prog): + def __insertTestFile(self, prog): """ Private slot to insert a test file name into the testsuitePicker object. @@ -392,13 +397,31 @@ self.__startButton.setDefault(False) # Start Failed button - # TODO: not implemented yet + # TODO: not implemented yet (Start Failed button) # Stop button self.__stopButton.setEnabled( self.__mode == UnittestWidgetModes.RUNNING) self.__stopButton.setDefault( self.__mode == UnittestWidgetModes.RUNNING) + + # Close button + self.buttonBox.button( + QDialogButtonBox.StandardButton.Close + ).setEnabled(self.__mode in ( + UnittestWidgetModes.IDLE, UnittestWidgetModes.STOPPED + )) + + def __updateProgress(self): + """ + Private method update the progress indicators. + """ + self.progressCounterRunCount.setText( + str(self.__runCount)) + self.progressCounterRemCount.setText( + str(self.__totalCount - self.__runCount)) + self.progressProgressBar.setMaximum(self.__totalCount) + self.progressProgressBar.setValue(self.__runCount) def __setIdleMode(self): """ @@ -406,20 +429,37 @@ """ self.__mode = UnittestWidgetModes.IDLE self.__updateButtonBoxButtons() + self.tabWidget.setCurrentIndex(0) def __setRunningMode(self): """ Private method to switch the widget to running mode. """ - # TODO: not implemented yet - pass + self.__mode = UnittestWidgetModes.RUNNING + + self.__totalCount = 0 + self.__runCount = 0 + + self.__coverageFile = "" + # TODO: implement the handling of the 'Show Coverage' button + + self.sbLabel.setText(self.tr("Running")) + self.tabWidget.setCurrentIndex(1) + self.__updateButtonBoxButtons() + self.__updateProgress() + + self.__resultsModel.clear() def __setStoppedMode(self): """ Private method to switch the widget to stopped mode. """ - # TODO: not implemented yet - pass + self.__mode = UnittestWidgetModes.STOPPED + + self.__updateButtonBoxButtons() + + self.raise_() + self.activateWindow() @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): @@ -429,10 +469,6 @@ @param button button that was clicked @type QAbstractButton """ -## if button == self.discoverButton: -## self.__discover() -## self.__saveRecent() -## elif button == self.__startButton: if button == self.__startButton: self.startTests() self.__saveRecent() @@ -523,13 +559,16 @@ discoveryStart = "" testFileName = self.testsuitePicker.currentText() if testFileName: - self.__insertProg(testFileName) + self.__insertTestFile(testFileName) testName = self.testComboBox.currentText() if testName: - self.insertTestName(testName) + self.__insertTestName(testName) if testFileName and not testName: testName = "suite" + self.sbLabel.setText(self.tr("Preparing Testsuite")) + QCoreApplication.processEvents() + interpreter = self.__venvManager.getVirtualenvInterpreter( self.__recentEnvironment) config = UTTestConfig( @@ -546,27 +585,47 @@ self.__resultsModel.clear() self.__testExecutor = self.__frameworkRegistry.createExecutor( self.__recentFramework, self) - self.__testExecutor.collected.connect(self.__testCollected) + self.__testExecutor.collected.connect(self.__testsCollected) self.__testExecutor.collectError.connect(self.__testsCollectError) - self.__testExecutor.startTest.connect(self.__testsStarted) + self.__testExecutor.startTest.connect(self.__testStarted) self.__testExecutor.testResult.connect(self.__processTestResult) self.__testExecutor.testFinished.connect(self.__testProcessFinished) + self.__testExecutor.testRunFinished.connect(self.__testRunFinished) self.__testExecutor.stop.connect(self.__testsStopped) - self.__testExecutor.start(config, []) + self.__testExecutor.coverageDataSaved.connect(self.__coverageData) - # TODO: not yet implemented - pass + self.__setRunningMode() + self.__testExecutor.start(config, []) + + @pyqtSlot() + def __stopTests(self): + """ + Private slot to stop the current test run. + """ + self.__testExecutor.stopIfRunning() @pyqtSlot(list) - def __testCollected(self, testNames): + def __testsCollected(self, testNames): """ Private slot handling the 'collected' signal of the executor. - @param testNames list of names of collected tests - @type list of str + @param testNames list of tuples containing the test id and test name + of collected tests + @type list of tuple of (str, str) """ - # TODO: not implemented yet - pass + testResults = [ + UTTestResult( + category=ResultCategory.PENDING, + status=self.tr("pending"), + name=name, + id=id, + message=desc, + ) for id, name, desc in testNames + ] + self.__resultsModel.setTestResults(testResults) + + self.__totalCount = len(testResults) + self.__updateProgress() @pyqtSlot(list) def __testsCollectError(self, errors): @@ -577,19 +636,49 @@ of the error @type list of tuple of (str, str) """ - # TODO: not implemented yet - pass + testResults = [] + + for testFile, error in errors: + if testFile: + testResults.append(UTTestResult( + category=ResultCategory.FAIL, + status=self.tr("Failure"), + name=testFile, + id=testFile, + message=self.tr("Collection Error"), + extra=error.splitlines() + )) + else: + EricMessageBox.critical( + self, + self.tr("Collection Error"), + self.tr( + "<p>There was an error while collecting unit tests." + "</p><p>{0}</p>" + ).format("<br/>".join(error.splitlines())) + ) + + if testResults: + self.__resultsModel.addTestResults(testResults) - @pyqtSlot(list) - def __testsStarted(self, testNames): + @pyqtSlot(tuple) + def __testStarted(self, test): """ Private slot handling the 'startTest' signal of the executor. - @param testNames list of names of tests about to be run - @type list of str + @param test tuple containing the id, name and short description of the + tests about to be run + @type tuple of (str, str, str) """ - # TODO: not implemented yet - pass + self.__resultsModel.updateTestResults([ + UTTestResult( + category=ResultCategory.RUNNING, + status=self.tr("running"), + id=test[0], + name=test[1], + message="" if test[2] is None else test[2], + ) + ]) @pyqtSlot(UTTestResult) def __processTestResult(self, result): @@ -599,8 +688,10 @@ @param result test result object @type UTTestResult """ - # TODO: not implemented yet - pass + self.__runCount += 1 + self.__updateProgress() + + self.__resultsModel.updateTestResults([result]) @pyqtSlot(list, str) def __testProcessFinished(self, results, output): @@ -613,16 +704,47 @@ @param output string containing the test process output (if any) @type str """ - # TODO: not implemented yet - pass + self.__setStoppedMode() + self.__testExecutor = None + + @pyqtSlot(int, float) + def __testRunFinished(self, noTests, duration): + """ + Private slot to handle the 'testRunFinished' signal of the executor. + + @param noTests number of tests run by the executor + @type int + @param duration time needed in seconds to run the tests + @type float + """ + self.sbLabel.setText( + self.tr("Ran %n test(s) in {0}s", "", noTests).format( + locale.format_string("%.3f", duration, grouping=True) + ) + ) + + self.__setStoppedMode() @pyqtSlot() def __testsStopped(self): """ Private slot to handle the 'stop' signal of the executor. """ - # TODO: not implemented yet - pass + self.sbLabel.setText(self.tr("Ran %n test(s)", "", self.__runCount)) + + self.__setStoppedMode() + + @pyqtSlot(str) + def __coverageData(self, coverageFile): + """ + Private slot to handle the 'coverageData' signal of the executor. + + @param coverageFile file containing the coverage data + @type str + """ + self.__coverageFile = coverageFile + + # TODO: implement the handling of the 'Show Coverage' button class UnittestWindow(EricMainWindow):