eric7/Unittest/UnittestWidget.py

branch
unittest
changeset 9062
7f27bf3b50c3
parent 9059
e7fd342f8bfc
child 9063
f1d7dd7ae471
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):

eric ide

mercurial