--- 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):