eric7/Unittest/UnittestWidget.py

branch
unittest
changeset 9065
39405e6eba20
parent 9064
339bb8c8007d
diff -r 339bb8c8007d -r 39405e6eba20 eric7/Unittest/UnittestWidget.py
--- a/eric7/Unittest/UnittestWidget.py	Sun May 15 18:08:31 2022 +0200
+++ b/eric7/Unittest/UnittestWidget.py	Mon May 16 17:22:43 2022 +0200
@@ -7,11 +7,12 @@
 Module implementing a widget to orchestrate unit test execution.
 """
 
+import contextlib
 import enum
 import locale
 import os
 
-from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QCoreApplication
+from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QCoreApplication
 from PyQt6.QtWidgets import (
     QAbstractButton, QComboBox, QDialogButtonBox, QWidget
 )
@@ -54,7 +55,14 @@
 class UnittestWidget(QWidget, Ui_UnittestWidget):
     """
     Class implementing a widget to orchestrate unit test execution.
+    
+    @signal unittestFile(str, int, bool) emitted to show the source of a
+        unittest file
+    @signal unittestStopped() emitted after a unit test was run
     """
+    unittestFile = pyqtSignal(str, int, bool)
+    unittestStopped = pyqtSignal()
+    
     def __init__(self, testfile=None, parent=None):
         """
         Constructor
@@ -71,6 +79,7 @@
         self.__resultsModel.summary.connect(self.__setStatusLabel)
         self.__resultsTree = TestResultsTreeView(self)
         self.__resultsTree.setModel(self.__resultsModel)
+        self.__resultsTree.goto.connect(self.__showSource)
         self.resultsGroupBox.layout().addWidget(self.__resultsTree)
         
         self.versionsButton.setIcon(
@@ -125,17 +134,24 @@
         self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
         self.setWindowTitle(self.tr("Unittest"))
         
-        from VirtualEnv.VirtualenvManager import VirtualenvManager
-        self.__venvManager = VirtualenvManager(self)
-        self.__venvManager.virtualEnvironmentAdded.connect(
-            self.__populateVenvComboBox)
-        self.__venvManager.virtualEnvironmentRemoved.connect(
-            self.__populateVenvComboBox)
-        self.__venvManager.virtualEnvironmentChanged.connect(
-            self.__populateVenvComboBox)
-        
-        # TODO: implement project mode
-        self.__forProject = False
+        try:
+            # we are called from within the eric IDE
+            self.__venvManager = ericApp().getObject("VirtualEnvManager")
+            self.__project = ericApp().getObject("Project")
+            self.__project.projectOpened.connect(self.__projectOpened)
+            self.__project.projectClosed.connect(self.__projectClosed)
+        except KeyError:
+            # we were called as a standalone application
+            from VirtualEnv.VirtualenvManager import VirtualenvManager
+            self.__venvManager = VirtualenvManager(self)
+            self.__venvManager.virtualEnvironmentAdded.connect(
+                self.__populateVenvComboBox)
+            self.__venvManager.virtualEnvironmentRemoved.connect(
+                self.__populateVenvComboBox)
+            self.__venvManager.virtualEnvironmentChanged.connect(
+                self.__populateVenvComboBox)
+            
+            self.__project = None
         
         self.__discoverHistory = []
         self.__fileHistory = []
@@ -150,8 +166,6 @@
         # connect some signals
         self.frameworkComboBox.currentIndexChanged.connect(
             self.__resetResults)
-        self.discoverCheckBox.toggled.connect(
-            self.__resetResults)
         self.discoveryPicker.editTextChanged.connect(
             self.__resetResults)
         self.testsuitePicker.editTextChanged.connect(
@@ -168,14 +182,14 @@
         self.__loadRecent()
         self.__populateVenvComboBox()
         
-        if self.__forProject:
-            project = ericApp().getObject("Project")
-            if project.isOpen():
-                self.__insertDiscovery(project.getProjectPath())
-            else:
-                self.__insertDiscovery("")
+        if self.__project and self.__project.isOpen():
+            self.venvComboBox.setCurrentText(self.__project.getProjectVenv())
+            self.frameworkComboBox.setCurrentText(
+                self.__project.getProjectTestingFramework())
+            self.__insertDiscovery(self.__project.getProjectPath())
         else:
             self.__insertDiscovery("")
+        
         self.__insertTestFile(testfile)
         self.__insertTestName("")
         
@@ -195,10 +209,7 @@
         self.venvComboBox.addItem("")
         self.venvComboBox.addItems(
             sorted(self.__venvManager.getVirtualenvNames()))
-        index = self.venvComboBox.findText(currentText)
-        if index < 0:
-            index = 0
-        self.venvComboBox.setCurrentIndex(index)
+        self.venvComboBox.setCurrentText(currentText)
     
     def __populateTestFrameworkComboBox(self):
         """
@@ -240,6 +251,15 @@
         """
         return self.__resultsModel
     
+    def hasFailedTests(self):
+        """
+        Public method to check for failed tests.
+        
+        @return flag indicating the existence of failed tests
+        @rtype bool
+        """
+        return bool(self.__resultsModel.getFailedTests())
+        
     def getFailedTests(self):
         """
         Public method to get the list of failed tests (if any).
@@ -261,8 +281,6 @@
         @param item item to be inserted
         @type str
         """
-        current = widget.currentText()
-        
         # prepend the given directory to the discovery picker
         if item is None:
             item = ""
@@ -271,9 +289,7 @@
         history.insert(0, item)
         widget.clear()
         widget.addItems(history)
-        
-        if current:
-            widget.setEditText(current)
+        widget.setEditText(item)
     
     @pyqtSlot(str)
     def __insertDiscovery(self, start):
@@ -288,6 +304,21 @@
                              start)
     
     @pyqtSlot(str)
+    def setTestFile(self, testFile):
+        """
+        Public slot to set the given test file as the current one.
+        
+        @param testFile path of the test file
+        @type str
+        """
+        if testFile:
+            self.__insertTestFile(testFile)
+        
+        self.discoverCheckBox.setChecked(not bool(testFile))
+        
+        self.tabWidget.setCurrentIndex(0)
+    
+    @pyqtSlot(str)
     def __insertTestFile(self, prog):
         """
         Private slot to insert a test file name into the testsuitePicker
@@ -508,34 +539,55 @@
         
         self.__updateButtonBoxButtons()
         
+        self.unittestStopped.emit()
+        
         self.raise_()
         self.activateWindow()
     
+    @pyqtSlot(bool)
+    def on_discoverCheckBox_toggled(self, checked):
+        """
+        Private slot handling state changes of the 'discover' checkbox.
+        
+        @param checked state of the checkbox
+        @type bool
+        """
+        if not bool(self.discoveryPicker.currentText()):
+            if self.__project and self.__project.isOpen():
+                self.__insertDiscovery(self.__project.getProjectPath())
+            else:
+                self.__insertDiscovery(
+                    Preferences.getMultiProject("Workspace"))
+        
+        self.__resetResults()
+    
     @pyqtSlot()
     def on_testsuitePicker_aboutToShowPathPickerDialog(self):
         """
         Private slot called before the test file selection dialog is shown.
         """
-        # TODO: implement eric-ide mode
-#        if self.__dbs:
-#            py3Extensions = ' '.join(
-#                ["*{0}".format(ext)
-#                 for ext in self.__dbs.getExtensions('Python3')]
-#            )
-#            fileFilter = self.tr(
-#                "Python3 Files ({0});;All Files (*)"
-#            ).format(py3Extensions)
-#        else:
-        fileFilter = self.tr("Python Files (*.py);;All Files (*)")
+        if self.__project:
+            # we were called from within eric
+            py3Extensions = ' '.join([
+                "*{0}".format(ext)
+                for ext in
+                ericApp().getObject("DebugServer").getExtensions('Python3')
+            ])
+            fileFilter = self.tr(
+                "Python3 Files ({0});;All Files (*)"
+            ).format(py3Extensions)
+        else:
+            # standalone application
+            fileFilter = self.tr("Python Files (*.py);;All Files (*)")
         self.testsuitePicker.setFilters(fileFilter)
         
-        defaultDirectory = Preferences.getMultiProject("Workspace")
+        defaultDirectory = (
+            self.__project.getProjectPath()
+            if self.__project and self.__project.isOpen() else
+            Preferences.getMultiProject("Workspace")
+        )
         if not defaultDirectory:
             defaultDirectory = os.path.expanduser("~")
-#        if self.__dbs:
-#            project = ericApp().getObject("Project")
-#            if self.__forProject and project.isOpen():
-#                defaultDirectory = project.getProjectPath()
         self.testsuitePicker.setDefaultDirectory(defaultDirectory)
     
     @pyqtSlot(QAbstractButton)
@@ -848,6 +900,73 @@
         @type str
         """
         self.statusLabel.setText(f"<b>{statusText}</b>")
+    
+    @pyqtSlot()
+    def __projectOpened(self):
+        """
+        Private slot to handle a project being opened.
+        """
+        self.venvComboBox.setCurrentText(self.__project.getProjectVenv())
+        self.frameworkComboBox.setCurrentText(
+            self.__project.getProjectTestingFramework())
+        self.__insertDiscovery(self.__project.getProjectPath())
+    
+    @pyqtSlot()
+    def __projectClosed(self):
+        """
+        Private slot to handle a project being closed.
+        """
+        self.venvComboBox.setCurrentText("")
+        self.frameworkComboBox.setCurrentText("")
+        self.__insertDiscovery("")
+    
+    @pyqtSlot(str, int)
+    def __showSource(self, filename, lineno):
+        """
+        Private slot to show the source of a traceback in an editor.
+        
+        @param filename file name of the file to be shown
+        @type str
+        @param lineno line number to go to in the file
+        @type int
+        """
+        if self.__project:
+            # running as part of eric IDE
+            self.unittestFile.emit(filename, lineno, True)
+        else:
+            self.__openEditor(filename, lineno)
+    
+    def __openEditor(self, filename, linenumber):
+        """
+        Private method to open an editor window for the given file.
+        
+        Note: This method opens an editor window when the unittest dialog
+        is called as a standalone application.
+        
+        @param filename path of the file to be opened
+        @type str
+        @param linenumber line number to place the cursor at
+        @type int
+        """
+        from QScintilla.MiniEditor import MiniEditor
+        editor = MiniEditor(filename, "Python3", self)
+        editor.gotoLine(linenumber)
+        editor.show()
+        
+        self.__editors.append(editor)
+    
+    def closeEvent(self, event):
+        """
+        Protected method to handle the close event.
+        
+        @param event close event
+        @type QCloseEvent
+        """
+        event.accept()
+        
+        for editor in self.__editors:
+            with contextlib.suppress(Exception):
+                editor.close()
 
 
 class UnittestWindow(EricMainWindow):

eric ide

mercurial