eric7/Unittest/UnittestWidget.py

branch
unittest
changeset 9065
39405e6eba20
parent 9064
339bb8c8007d
equal deleted inserted replaced
9064:339bb8c8007d 9065:39405e6eba20
5 5
6 """ 6 """
7 Module implementing a widget to orchestrate unit test execution. 7 Module implementing a widget to orchestrate unit test execution.
8 """ 8 """
9 9
10 import contextlib
10 import enum 11 import enum
11 import locale 12 import locale
12 import os 13 import os
13 14
14 from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QCoreApplication 15 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QCoreApplication
15 from PyQt6.QtWidgets import ( 16 from PyQt6.QtWidgets import (
16 QAbstractButton, QComboBox, QDialogButtonBox, QWidget 17 QAbstractButton, QComboBox, QDialogButtonBox, QWidget
17 ) 18 )
18 19
19 from EricWidgets import EricMessageBox 20 from EricWidgets import EricMessageBox
52 # TODO: add a "Show Coverage" function using PyCoverageDialog 53 # TODO: add a "Show Coverage" function using PyCoverageDialog
53 54
54 class UnittestWidget(QWidget, Ui_UnittestWidget): 55 class UnittestWidget(QWidget, Ui_UnittestWidget):
55 """ 56 """
56 Class implementing a widget to orchestrate unit test execution. 57 Class implementing a widget to orchestrate unit test execution.
58
59 @signal unittestFile(str, int, bool) emitted to show the source of a
60 unittest file
61 @signal unittestStopped() emitted after a unit test was run
57 """ 62 """
63 unittestFile = pyqtSignal(str, int, bool)
64 unittestStopped = pyqtSignal()
65
58 def __init__(self, testfile=None, parent=None): 66 def __init__(self, testfile=None, parent=None):
59 """ 67 """
60 Constructor 68 Constructor
61 69
62 @param testfile file name of the test to load 70 @param testfile file name of the test to load
69 77
70 self.__resultsModel = TestResultsModel(self) 78 self.__resultsModel = TestResultsModel(self)
71 self.__resultsModel.summary.connect(self.__setStatusLabel) 79 self.__resultsModel.summary.connect(self.__setStatusLabel)
72 self.__resultsTree = TestResultsTreeView(self) 80 self.__resultsTree = TestResultsTreeView(self)
73 self.__resultsTree.setModel(self.__resultsModel) 81 self.__resultsTree.setModel(self.__resultsModel)
82 self.__resultsTree.goto.connect(self.__showSource)
74 self.resultsGroupBox.layout().addWidget(self.__resultsTree) 83 self.resultsGroupBox.layout().addWidget(self.__resultsTree)
75 84
76 self.versionsButton.setIcon( 85 self.versionsButton.setIcon(
77 UI.PixmapCache.getIcon("info")) 86 UI.PixmapCache.getIcon("info"))
78 self.clearHistoriesButton.setIcon( 87 self.clearHistoriesButton.setIcon(
123 Qt.WindowType.WindowContextHelpButtonHint 132 Qt.WindowType.WindowContextHelpButtonHint
124 ) 133 )
125 self.setWindowIcon(UI.PixmapCache.getIcon("eric")) 134 self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
126 self.setWindowTitle(self.tr("Unittest")) 135 self.setWindowTitle(self.tr("Unittest"))
127 136
128 from VirtualEnv.VirtualenvManager import VirtualenvManager 137 try:
129 self.__venvManager = VirtualenvManager(self) 138 # we are called from within the eric IDE
130 self.__venvManager.virtualEnvironmentAdded.connect( 139 self.__venvManager = ericApp().getObject("VirtualEnvManager")
131 self.__populateVenvComboBox) 140 self.__project = ericApp().getObject("Project")
132 self.__venvManager.virtualEnvironmentRemoved.connect( 141 self.__project.projectOpened.connect(self.__projectOpened)
133 self.__populateVenvComboBox) 142 self.__project.projectClosed.connect(self.__projectClosed)
134 self.__venvManager.virtualEnvironmentChanged.connect( 143 except KeyError:
135 self.__populateVenvComboBox) 144 # we were called as a standalone application
136 145 from VirtualEnv.VirtualenvManager import VirtualenvManager
137 # TODO: implement project mode 146 self.__venvManager = VirtualenvManager(self)
138 self.__forProject = False 147 self.__venvManager.virtualEnvironmentAdded.connect(
148 self.__populateVenvComboBox)
149 self.__venvManager.virtualEnvironmentRemoved.connect(
150 self.__populateVenvComboBox)
151 self.__venvManager.virtualEnvironmentChanged.connect(
152 self.__populateVenvComboBox)
153
154 self.__project = None
139 155
140 self.__discoverHistory = [] 156 self.__discoverHistory = []
141 self.__fileHistory = [] 157 self.__fileHistory = []
142 self.__testNameHistory = [] 158 self.__testNameHistory = []
143 self.__recentFramework = "" 159 self.__recentFramework = ""
147 self.__editors = [] 163 self.__editors = []
148 self.__testExecutor = None 164 self.__testExecutor = None
149 165
150 # connect some signals 166 # connect some signals
151 self.frameworkComboBox.currentIndexChanged.connect( 167 self.frameworkComboBox.currentIndexChanged.connect(
152 self.__resetResults)
153 self.discoverCheckBox.toggled.connect(
154 self.__resetResults) 168 self.__resetResults)
155 self.discoveryPicker.editTextChanged.connect( 169 self.discoveryPicker.editTextChanged.connect(
156 self.__resetResults) 170 self.__resetResults)
157 self.testsuitePicker.editTextChanged.connect( 171 self.testsuitePicker.editTextChanged.connect(
158 self.__resetResults) 172 self.__resetResults)
166 self.__setIdleMode() 180 self.__setIdleMode()
167 181
168 self.__loadRecent() 182 self.__loadRecent()
169 self.__populateVenvComboBox() 183 self.__populateVenvComboBox()
170 184
171 if self.__forProject: 185 if self.__project and self.__project.isOpen():
172 project = ericApp().getObject("Project") 186 self.venvComboBox.setCurrentText(self.__project.getProjectVenv())
173 if project.isOpen(): 187 self.frameworkComboBox.setCurrentText(
174 self.__insertDiscovery(project.getProjectPath()) 188 self.__project.getProjectTestingFramework())
175 else: 189 self.__insertDiscovery(self.__project.getProjectPath())
176 self.__insertDiscovery("")
177 else: 190 else:
178 self.__insertDiscovery("") 191 self.__insertDiscovery("")
192
179 self.__insertTestFile(testfile) 193 self.__insertTestFile(testfile)
180 self.__insertTestName("") 194 self.__insertTestName("")
181 195
182 self.clearHistoriesButton.clicked.connect(self.clearRecent) 196 self.clearHistoriesButton.clicked.connect(self.clearRecent)
183 197
193 207
194 self.venvComboBox.clear() 208 self.venvComboBox.clear()
195 self.venvComboBox.addItem("") 209 self.venvComboBox.addItem("")
196 self.venvComboBox.addItems( 210 self.venvComboBox.addItems(
197 sorted(self.__venvManager.getVirtualenvNames())) 211 sorted(self.__venvManager.getVirtualenvNames()))
198 index = self.venvComboBox.findText(currentText) 212 self.venvComboBox.setCurrentText(currentText)
199 if index < 0:
200 index = 0
201 self.venvComboBox.setCurrentIndex(index)
202 213
203 def __populateTestFrameworkComboBox(self): 214 def __populateTestFrameworkComboBox(self):
204 """ 215 """
205 Private method to (re-)populate the test framework selector. 216 Private method to (re-)populate the test framework selector.
206 """ 217 """
238 @return reference to the test results model 249 @return reference to the test results model
239 @rtype TestResultsModel 250 @rtype TestResultsModel
240 """ 251 """
241 return self.__resultsModel 252 return self.__resultsModel
242 253
254 def hasFailedTests(self):
255 """
256 Public method to check for failed tests.
257
258 @return flag indicating the existence of failed tests
259 @rtype bool
260 """
261 return bool(self.__resultsModel.getFailedTests())
262
243 def getFailedTests(self): 263 def getFailedTests(self):
244 """ 264 """
245 Public method to get the list of failed tests (if any). 265 Public method to get the list of failed tests (if any).
246 266
247 @return list of IDs of failed tests 267 @return list of IDs of failed tests
259 @param history array containing the history 279 @param history array containing the history
260 @type list of str 280 @type list of str
261 @param item item to be inserted 281 @param item item to be inserted
262 @type str 282 @type str
263 """ 283 """
264 current = widget.currentText()
265
266 # prepend the given directory to the discovery picker 284 # prepend the given directory to the discovery picker
267 if item is None: 285 if item is None:
268 item = "" 286 item = ""
269 if item in history: 287 if item in history:
270 history.remove(item) 288 history.remove(item)
271 history.insert(0, item) 289 history.insert(0, item)
272 widget.clear() 290 widget.clear()
273 widget.addItems(history) 291 widget.addItems(history)
274 292 widget.setEditText(item)
275 if current:
276 widget.setEditText(current)
277 293
278 @pyqtSlot(str) 294 @pyqtSlot(str)
279 def __insertDiscovery(self, start): 295 def __insertDiscovery(self, start):
280 """ 296 """
281 Private slot to insert the discovery start directory into the 297 Private slot to insert the discovery start directory into the
284 @param start start directory name to be inserted 300 @param start start directory name to be inserted
285 @type str 301 @type str
286 """ 302 """
287 self.__insertHistory(self.discoveryPicker, self.__discoverHistory, 303 self.__insertHistory(self.discoveryPicker, self.__discoverHistory,
288 start) 304 start)
305
306 @pyqtSlot(str)
307 def setTestFile(self, testFile):
308 """
309 Public slot to set the given test file as the current one.
310
311 @param testFile path of the test file
312 @type str
313 """
314 if testFile:
315 self.__insertTestFile(testFile)
316
317 self.discoverCheckBox.setChecked(not bool(testFile))
318
319 self.tabWidget.setCurrentIndex(0)
289 320
290 @pyqtSlot(str) 321 @pyqtSlot(str)
291 def __insertTestFile(self, prog): 322 def __insertTestFile(self, prog):
292 """ 323 """
293 Private slot to insert a test file name into the testsuitePicker 324 Private slot to insert a test file name into the testsuitePicker
506 537
507 self.progressGroupBox.hide() 538 self.progressGroupBox.hide()
508 539
509 self.__updateButtonBoxButtons() 540 self.__updateButtonBoxButtons()
510 541
542 self.unittestStopped.emit()
543
511 self.raise_() 544 self.raise_()
512 self.activateWindow() 545 self.activateWindow()
513 546
547 @pyqtSlot(bool)
548 def on_discoverCheckBox_toggled(self, checked):
549 """
550 Private slot handling state changes of the 'discover' checkbox.
551
552 @param checked state of the checkbox
553 @type bool
554 """
555 if not bool(self.discoveryPicker.currentText()):
556 if self.__project and self.__project.isOpen():
557 self.__insertDiscovery(self.__project.getProjectPath())
558 else:
559 self.__insertDiscovery(
560 Preferences.getMultiProject("Workspace"))
561
562 self.__resetResults()
563
514 @pyqtSlot() 564 @pyqtSlot()
515 def on_testsuitePicker_aboutToShowPathPickerDialog(self): 565 def on_testsuitePicker_aboutToShowPathPickerDialog(self):
516 """ 566 """
517 Private slot called before the test file selection dialog is shown. 567 Private slot called before the test file selection dialog is shown.
518 """ 568 """
519 # TODO: implement eric-ide mode 569 if self.__project:
520 # if self.__dbs: 570 # we were called from within eric
521 # py3Extensions = ' '.join( 571 py3Extensions = ' '.join([
522 # ["*{0}".format(ext) 572 "*{0}".format(ext)
523 # for ext in self.__dbs.getExtensions('Python3')] 573 for ext in
524 # ) 574 ericApp().getObject("DebugServer").getExtensions('Python3')
525 # fileFilter = self.tr( 575 ])
526 # "Python3 Files ({0});;All Files (*)" 576 fileFilter = self.tr(
527 # ).format(py3Extensions) 577 "Python3 Files ({0});;All Files (*)"
528 # else: 578 ).format(py3Extensions)
529 fileFilter = self.tr("Python Files (*.py);;All Files (*)") 579 else:
580 # standalone application
581 fileFilter = self.tr("Python Files (*.py);;All Files (*)")
530 self.testsuitePicker.setFilters(fileFilter) 582 self.testsuitePicker.setFilters(fileFilter)
531 583
532 defaultDirectory = Preferences.getMultiProject("Workspace") 584 defaultDirectory = (
585 self.__project.getProjectPath()
586 if self.__project and self.__project.isOpen() else
587 Preferences.getMultiProject("Workspace")
588 )
533 if not defaultDirectory: 589 if not defaultDirectory:
534 defaultDirectory = os.path.expanduser("~") 590 defaultDirectory = os.path.expanduser("~")
535 # if self.__dbs:
536 # project = ericApp().getObject("Project")
537 # if self.__forProject and project.isOpen():
538 # defaultDirectory = project.getProjectPath()
539 self.testsuitePicker.setDefaultDirectory(defaultDirectory) 591 self.testsuitePicker.setDefaultDirectory(defaultDirectory)
540 592
541 @pyqtSlot(QAbstractButton) 593 @pyqtSlot(QAbstractButton)
542 def on_buttonBox_clicked(self, button): 594 def on_buttonBox_clicked(self, button):
543 """ 595 """
846 898
847 @param statusText text to be shown 899 @param statusText text to be shown
848 @type str 900 @type str
849 """ 901 """
850 self.statusLabel.setText(f"<b>{statusText}</b>") 902 self.statusLabel.setText(f"<b>{statusText}</b>")
903
904 @pyqtSlot()
905 def __projectOpened(self):
906 """
907 Private slot to handle a project being opened.
908 """
909 self.venvComboBox.setCurrentText(self.__project.getProjectVenv())
910 self.frameworkComboBox.setCurrentText(
911 self.__project.getProjectTestingFramework())
912 self.__insertDiscovery(self.__project.getProjectPath())
913
914 @pyqtSlot()
915 def __projectClosed(self):
916 """
917 Private slot to handle a project being closed.
918 """
919 self.venvComboBox.setCurrentText("")
920 self.frameworkComboBox.setCurrentText("")
921 self.__insertDiscovery("")
922
923 @pyqtSlot(str, int)
924 def __showSource(self, filename, lineno):
925 """
926 Private slot to show the source of a traceback in an editor.
927
928 @param filename file name of the file to be shown
929 @type str
930 @param lineno line number to go to in the file
931 @type int
932 """
933 if self.__project:
934 # running as part of eric IDE
935 self.unittestFile.emit(filename, lineno, True)
936 else:
937 self.__openEditor(filename, lineno)
938
939 def __openEditor(self, filename, linenumber):
940 """
941 Private method to open an editor window for the given file.
942
943 Note: This method opens an editor window when the unittest dialog
944 is called as a standalone application.
945
946 @param filename path of the file to be opened
947 @type str
948 @param linenumber line number to place the cursor at
949 @type int
950 """
951 from QScintilla.MiniEditor import MiniEditor
952 editor = MiniEditor(filename, "Python3", self)
953 editor.gotoLine(linenumber)
954 editor.show()
955
956 self.__editors.append(editor)
957
958 def closeEvent(self, event):
959 """
960 Protected method to handle the close event.
961
962 @param event close event
963 @type QCloseEvent
964 """
965 event.accept()
966
967 for editor in self.__editors:
968 with contextlib.suppress(Exception):
969 editor.close()
851 970
852 971
853 class UnittestWindow(EricMainWindow): 972 class UnittestWindow(EricMainWindow):
854 """ 973 """
855 Main window class for the standalone dialog. 974 Main window class for the standalone dialog.

eric ide

mercurial