src/eric7/Testing/TestingWidget.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9264
18a7312cfdb3
child 9311
8e588f403fd9
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
11 import enum 11 import enum
12 import locale 12 import locale
13 import os 13 import os
14 14
15 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QCoreApplication 15 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QCoreApplication
16 from PyQt6.QtWidgets import ( 16 from PyQt6.QtWidgets import QAbstractButton, QComboBox, QDialogButtonBox, QWidget
17 QAbstractButton, QComboBox, QDialogButtonBox, QWidget
18 )
19 17
20 from EricWidgets import EricMessageBox 18 from EricWidgets import EricMessageBox
21 from EricWidgets.EricApplication import ericApp 19 from EricWidgets.EricApplication import ericApp
22 from EricWidgets.EricMainWindow import EricMainWindow 20 from EricWidgets.EricMainWindow import EricMainWindow
23 from EricWidgets.EricPathPicker import EricPathPickerModes 21 from EricWidgets.EricPathPicker import EricPathPickerModes
24 22
25 from .Ui_TestingWidget import Ui_TestingWidget 23 from .Ui_TestingWidget import Ui_TestingWidget
26 24
27 from .TestResultsTree import TestResultsModel, TestResultsTreeView 25 from .TestResultsTree import TestResultsModel, TestResultsTreeView
28 from .Interfaces import Frameworks 26 from .Interfaces import Frameworks
29 from .Interfaces.TestExecutorBase import ( 27 from .Interfaces.TestExecutorBase import TestConfig, TestResult, TestResultCategory
30 TestConfig, TestResult, TestResultCategory
31 )
32 from .Interfaces.TestFrameworkRegistry import TestFrameworkRegistry 28 from .Interfaces.TestFrameworkRegistry import TestFrameworkRegistry
33 29
34 import Preferences 30 import Preferences
35 import UI.PixmapCache 31 import UI.PixmapCache
36 32
37 from Globals import ( 33 from Globals import (
38 recentNameTestDiscoverHistory, recentNameTestFileHistory, 34 recentNameTestDiscoverHistory,
39 recentNameTestNameHistory, recentNameTestFramework, 35 recentNameTestFileHistory,
40 recentNameTestEnvironment 36 recentNameTestNameHistory,
37 recentNameTestFramework,
38 recentNameTestEnvironment,
41 ) 39 )
42 40
43 41
44 class TestingWidgetModes(enum.Enum): 42 class TestingWidgetModes(enum.Enum):
45 """ 43 """
46 Class defining the various modes of the testing widget. 44 Class defining the various modes of the testing widget.
47 """ 45 """
48 IDLE = 0 # idle, no test were run yet 46
49 RUNNING = 1 # test run being performed 47 IDLE = 0 # idle, no test were run yet
50 STOPPED = 2 # test run finished 48 RUNNING = 1 # test run being performed
49 STOPPED = 2 # test run finished
51 50
52 51
53 class TestingWidget(QWidget, Ui_TestingWidget): 52 class TestingWidget(QWidget, Ui_TestingWidget):
54 """ 53 """
55 Class implementing a widget to orchestrate unit test execution. 54 Class implementing a widget to orchestrate unit test execution.
56 55
57 @signal testFile(str, int, bool) emitted to show the source of a 56 @signal testFile(str, int, bool) emitted to show the source of a
58 test file 57 test file
59 @signal testRunStopped() emitted after a test run has finished 58 @signal testRunStopped() emitted after a test run has finished
60 """ 59 """
60
61 testFile = pyqtSignal(str, int, bool) 61 testFile = pyqtSignal(str, int, bool)
62 testRunStopped = pyqtSignal() 62 testRunStopped = pyqtSignal()
63 63
64 def __init__(self, testfile=None, parent=None): 64 def __init__(self, testfile=None, parent=None):
65 """ 65 """
66 Constructor 66 Constructor
67 67
68 @param testfile file name of the test to load 68 @param testfile file name of the test to load
69 @type str 69 @type str
70 @param parent reference to the parent widget (defaults to None) 70 @param parent reference to the parent widget (defaults to None)
71 @type QWidget (optional) 71 @type QWidget (optional)
72 """ 72 """
73 super().__init__(parent) 73 super().__init__(parent)
74 self.setupUi(self) 74 self.setupUi(self)
75 75
76 self.__resultsModel = TestResultsModel(self) 76 self.__resultsModel = TestResultsModel(self)
77 self.__resultsModel.summary.connect(self.__setStatusLabel) 77 self.__resultsModel.summary.connect(self.__setStatusLabel)
78 self.__resultsTree = TestResultsTreeView(self) 78 self.__resultsTree = TestResultsTreeView(self)
79 self.__resultsTree.setModel(self.__resultsModel) 79 self.__resultsTree.setModel(self.__resultsModel)
80 self.__resultsTree.goto.connect(self.__showSource) 80 self.__resultsTree.goto.connect(self.__showSource)
81 self.resultsGroupBox.layout().addWidget(self.__resultsTree) 81 self.resultsGroupBox.layout().addWidget(self.__resultsTree)
82 82
83 self.versionsButton.setIcon( 83 self.versionsButton.setIcon(UI.PixmapCache.getIcon("info"))
84 UI.PixmapCache.getIcon("info")) 84 self.clearHistoriesButton.setIcon(UI.PixmapCache.getIcon("clearPrivateData"))
85 self.clearHistoriesButton.setIcon( 85
86 UI.PixmapCache.getIcon("clearPrivateData"))
87
88 self.testsuitePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) 86 self.testsuitePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
89 self.testsuitePicker.setInsertPolicy( 87 self.testsuitePicker.setInsertPolicy(QComboBox.InsertPolicy.InsertAtTop)
90 QComboBox.InsertPolicy.InsertAtTop)
91 self.testsuitePicker.setSizeAdjustPolicy( 88 self.testsuitePicker.setSizeAdjustPolicy(
92 QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) 89 QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon
93 90 )
91
94 self.discoveryPicker.setMode(EricPathPickerModes.DIRECTORY_MODE) 92 self.discoveryPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
95 self.discoveryPicker.setInsertPolicy( 93 self.discoveryPicker.setInsertPolicy(QComboBox.InsertPolicy.InsertAtTop)
96 QComboBox.InsertPolicy.InsertAtTop)
97 self.discoveryPicker.setSizeAdjustPolicy( 94 self.discoveryPicker.setSizeAdjustPolicy(
98 QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) 95 QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon
99 96 )
97
100 self.testComboBox.lineEdit().setClearButtonEnabled(True) 98 self.testComboBox.lineEdit().setClearButtonEnabled(True)
101 99
102 # create some more dialog buttons for orchestration 100 # create some more dialog buttons for orchestration
103 self.__showLogButton = self.buttonBox.addButton( 101 self.__showLogButton = self.buttonBox.addButton(
104 self.tr("Show Output..."), 102 self.tr("Show Output..."), QDialogButtonBox.ButtonRole.ActionRole
105 QDialogButtonBox.ButtonRole.ActionRole) 103 )
106 self.__showLogButton.setToolTip( 104 self.__showLogButton.setToolTip(
107 self.tr("Show the output of the test runner process")) 105 self.tr("Show the output of the test runner process")
108 self.__showLogButton.setWhatsThis(self.tr( 106 )
109 """<b>Show Output...</b""" 107 self.__showLogButton.setWhatsThis(
110 """<p>This button opens a dialog containing the output of the""" 108 self.tr(
111 """ test runner process of the most recent run.</p>""")) 109 """<b>Show Output...</b"""
112 110 """<p>This button opens a dialog containing the output of the"""
111 """ test runner process of the most recent run.</p>"""
112 )
113 )
114
113 self.__showCoverageButton = self.buttonBox.addButton( 115 self.__showCoverageButton = self.buttonBox.addButton(
114 self.tr("Show Coverage..."), 116 self.tr("Show Coverage..."), QDialogButtonBox.ButtonRole.ActionRole
115 QDialogButtonBox.ButtonRole.ActionRole) 117 )
116 self.__showCoverageButton.setToolTip( 118 self.__showCoverageButton.setToolTip(
117 self.tr("Show code coverage in a new dialog")) 119 self.tr("Show code coverage in a new dialog")
118 self.__showCoverageButton.setWhatsThis(self.tr( 120 )
119 """<b>Show Coverage...</b>""" 121 self.__showCoverageButton.setWhatsThis(
120 """<p>This button opens a dialog containing the collected code""" 122 self.tr(
121 """ coverage data.</p>""")) 123 """<b>Show Coverage...</b>"""
122 124 """<p>This button opens a dialog containing the collected code"""
125 """ coverage data.</p>"""
126 )
127 )
128
123 self.__startButton = self.buttonBox.addButton( 129 self.__startButton = self.buttonBox.addButton(
124 self.tr("Start"), QDialogButtonBox.ButtonRole.ActionRole) 130 self.tr("Start"), QDialogButtonBox.ButtonRole.ActionRole
125 131 )
126 self.__startButton.setToolTip(self.tr( 132
127 "Start the selected testsuite")) 133 self.__startButton.setToolTip(self.tr("Start the selected testsuite"))
128 self.__startButton.setWhatsThis(self.tr( 134 self.__startButton.setWhatsThis(
129 """<b>Start Test</b>""" 135 self.tr(
130 """<p>This button starts the test run.</p>""")) 136 """<b>Start Test</b>""" """<p>This button starts the test run.</p>"""
131 137 )
138 )
139
132 self.__startFailedButton = self.buttonBox.addButton( 140 self.__startFailedButton = self.buttonBox.addButton(
133 self.tr("Rerun Failed"), QDialogButtonBox.ButtonRole.ActionRole) 141 self.tr("Rerun Failed"), QDialogButtonBox.ButtonRole.ActionRole
142 )
134 self.__startFailedButton.setToolTip( 143 self.__startFailedButton.setToolTip(
135 self.tr("Reruns failed tests of the selected testsuite")) 144 self.tr("Reruns failed tests of the selected testsuite")
136 self.__startFailedButton.setWhatsThis(self.tr( 145 )
137 """<b>Rerun Failed</b>""" 146 self.__startFailedButton.setWhatsThis(
138 """<p>This button reruns all failed tests of the most recent""" 147 self.tr(
139 """ test run.</p>""")) 148 """<b>Rerun Failed</b>"""
140 149 """<p>This button reruns all failed tests of the most recent"""
150 """ test run.</p>"""
151 )
152 )
153
141 self.__stopButton = self.buttonBox.addButton( 154 self.__stopButton = self.buttonBox.addButton(
142 self.tr("Stop"), QDialogButtonBox.ButtonRole.ActionRole) 155 self.tr("Stop"), QDialogButtonBox.ButtonRole.ActionRole
156 )
143 self.__stopButton.setToolTip(self.tr("Stop the running test")) 157 self.__stopButton.setToolTip(self.tr("Stop the running test"))
144 self.__stopButton.setWhatsThis(self.tr( 158 self.__stopButton.setWhatsThis(
145 """<b>Stop Test</b>""" 159 self.tr(
146 """<p>This button stops a running test.</p>""")) 160 """<b>Stop Test</b>""" """<p>This button stops a running test.</p>"""
147 161 )
162 )
163
148 self.setWindowFlags( 164 self.setWindowFlags(
149 self.windowFlags() | 165 self.windowFlags() | Qt.WindowType.WindowContextHelpButtonHint
150 Qt.WindowType.WindowContextHelpButtonHint
151 ) 166 )
152 self.setWindowIcon(UI.PixmapCache.getIcon("eric")) 167 self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
153 self.setWindowTitle(self.tr("Testing")) 168 self.setWindowTitle(self.tr("Testing"))
154 169
155 try: 170 try:
156 # we are called from within the eric IDE 171 # we are called from within the eric IDE
157 self.__venvManager = ericApp().getObject("VirtualEnvManager") 172 self.__venvManager = ericApp().getObject("VirtualEnvManager")
158 self.__project = ericApp().getObject("Project") 173 self.__project = ericApp().getObject("Project")
159 self.__project.projectOpened.connect(self.__projectOpened) 174 self.__project.projectOpened.connect(self.__projectOpened)
160 self.__project.projectClosed.connect(self.__projectClosed) 175 self.__project.projectClosed.connect(self.__projectClosed)
161 except KeyError: 176 except KeyError:
162 # we were called as a standalone application 177 # we were called as a standalone application
163 from VirtualEnv.VirtualenvManager import VirtualenvManager 178 from VirtualEnv.VirtualenvManager import VirtualenvManager
179
164 self.__venvManager = VirtualenvManager(self) 180 self.__venvManager = VirtualenvManager(self)
165 self.__venvManager.virtualEnvironmentAdded.connect( 181 self.__venvManager.virtualEnvironmentAdded.connect(
166 self.__populateVenvComboBox) 182 self.__populateVenvComboBox
183 )
167 self.__venvManager.virtualEnvironmentRemoved.connect( 184 self.__venvManager.virtualEnvironmentRemoved.connect(
168 self.__populateVenvComboBox) 185 self.__populateVenvComboBox
186 )
169 self.__venvManager.virtualEnvironmentChanged.connect( 187 self.__venvManager.virtualEnvironmentChanged.connect(
170 self.__populateVenvComboBox) 188 self.__populateVenvComboBox
189 )
171 ericApp().registerObject("VirtualEnvManager", self.__venvManager) 190 ericApp().registerObject("VirtualEnvManager", self.__venvManager)
172 191
173 self.__project = None 192 self.__project = None
174 193
175 self.__discoverHistory = [] 194 self.__discoverHistory = []
176 self.__fileHistory = [] 195 self.__fileHistory = []
177 self.__testNameHistory = [] 196 self.__testNameHistory = []
178 self.__recentFramework = "" 197 self.__recentFramework = ""
179 self.__recentEnvironment = "" 198 self.__recentEnvironment = ""
180 self.__failedTests = [] 199 self.__failedTests = []
181 200
182 self.__coverageFile = "" 201 self.__coverageFile = ""
183 self.__coverageDialog = None 202 self.__coverageDialog = None
184 203
185 self.__editors = [] 204 self.__editors = []
186 self.__testExecutor = None 205 self.__testExecutor = None
187 self.__recentLog = "" 206 self.__recentLog = ""
188 207
189 # connect some signals 208 # connect some signals
190 self.discoveryPicker.editTextChanged.connect( 209 self.discoveryPicker.editTextChanged.connect(self.__resetResults)
191 self.__resetResults) 210 self.testsuitePicker.editTextChanged.connect(self.__resetResults)
192 self.testsuitePicker.editTextChanged.connect( 211 self.testComboBox.editTextChanged.connect(self.__resetResults)
193 self.__resetResults) 212
194 self.testComboBox.editTextChanged.connect(
195 self.__resetResults)
196
197 self.__frameworkRegistry = TestFrameworkRegistry() 213 self.__frameworkRegistry = TestFrameworkRegistry()
198 for framework in Frameworks: 214 for framework in Frameworks:
199 self.__frameworkRegistry.register(framework) 215 self.__frameworkRegistry.register(framework)
200 216
201 self.__setIdleMode() 217 self.__setIdleMode()
202 218
203 self.__loadRecent() 219 self.__loadRecent()
204 self.__populateVenvComboBox() 220 self.__populateVenvComboBox()
205 221
206 if self.__project and self.__project.isOpen(): 222 if self.__project and self.__project.isOpen():
207 self.venvComboBox.setCurrentText(self.__project.getProjectVenv()) 223 self.venvComboBox.setCurrentText(self.__project.getProjectVenv())
208 self.frameworkComboBox.setCurrentText( 224 self.frameworkComboBox.setCurrentText(
209 self.__project.getProjectTestingFramework()) 225 self.__project.getProjectTestingFramework()
226 )
210 self.__insertDiscovery(self.__project.getProjectPath()) 227 self.__insertDiscovery(self.__project.getProjectPath())
211 else: 228 else:
212 self.__insertDiscovery("") 229 self.__insertDiscovery("")
213 230
214 self.__insertTestFile(testfile) 231 self.__insertTestFile(testfile)
215 self.__insertTestName("") 232 self.__insertTestName("")
216 233
217 self.clearHistoriesButton.clicked.connect(self.clearRecent) 234 self.clearHistoriesButton.clicked.connect(self.clearRecent)
218 235
219 self.tabWidget.setCurrentIndex(0) 236 self.tabWidget.setCurrentIndex(0)
220 237
221 def __populateVenvComboBox(self): 238 def __populateVenvComboBox(self):
222 """ 239 """
223 Private method to (re-)populate the virtual environments selector. 240 Private method to (re-)populate the virtual environments selector.
224 """ 241 """
225 currentText = self.venvComboBox.currentText() 242 currentText = self.venvComboBox.currentText()
226 if not currentText: 243 if not currentText:
227 currentText = self.__recentEnvironment 244 currentText = self.__recentEnvironment
228 245
229 self.venvComboBox.clear() 246 self.venvComboBox.clear()
230 self.venvComboBox.addItem("") 247 self.venvComboBox.addItem("")
231 self.venvComboBox.addItems( 248 self.venvComboBox.addItems(sorted(self.__venvManager.getVirtualenvNames()))
232 sorted(self.__venvManager.getVirtualenvNames()))
233 self.venvComboBox.setCurrentText(currentText) 249 self.venvComboBox.setCurrentText(currentText)
234 250
235 def __populateTestFrameworkComboBox(self): 251 def __populateTestFrameworkComboBox(self):
236 """ 252 """
237 Private method to (re-)populate the test framework selector. 253 Private method to (re-)populate the test framework selector.
238 """ 254 """
239 currentText = self.frameworkComboBox.currentText() 255 currentText = self.frameworkComboBox.currentText()
240 if not currentText: 256 if not currentText:
241 currentText = self.__recentFramework 257 currentText = self.__recentFramework
242 258
243 self.frameworkComboBox.clear() 259 self.frameworkComboBox.clear()
244 260
245 if bool(self.venvComboBox.currentText()): 261 if bool(self.venvComboBox.currentText()):
246 interpreter = self.__venvManager.getVirtualenvInterpreter( 262 interpreter = self.__venvManager.getVirtualenvInterpreter(
247 self.venvComboBox.currentText()) 263 self.venvComboBox.currentText()
264 )
248 self.frameworkComboBox.addItem("") 265 self.frameworkComboBox.addItem("")
249 for index, (name, executor) in enumerate( 266 for index, (name, executor) in enumerate(
250 sorted(self.__frameworkRegistry.getFrameworks().items()), 267 sorted(self.__frameworkRegistry.getFrameworks().items()), start=1
251 start=1
252 ): 268 ):
253 isInstalled = executor.isInstalled(interpreter) 269 isInstalled = executor.isInstalled(interpreter)
254 entry = ( 270 entry = (
255 name 271 name if isInstalled else self.tr("{0} (not available)").format(name)
256 if isInstalled else
257 self.tr("{0} (not available)").format(name)
258 ) 272 )
259 self.frameworkComboBox.addItem(entry) 273 self.frameworkComboBox.addItem(entry)
260 self.frameworkComboBox.model().item(index).setEnabled( 274 self.frameworkComboBox.model().item(index).setEnabled(isInstalled)
261 isInstalled) 275
262
263 self.frameworkComboBox.setCurrentText(self.__recentFramework) 276 self.frameworkComboBox.setCurrentText(self.__recentFramework)
264 277
265 def getResultsModel(self): 278 def getResultsModel(self):
266 """ 279 """
267 Public method to get a reference to the model containing the test 280 Public method to get a reference to the model containing the test
268 result data. 281 result data.
269 282
270 @return reference to the test results model 283 @return reference to the test results model
271 @rtype TestResultsModel 284 @rtype TestResultsModel
272 """ 285 """
273 return self.__resultsModel 286 return self.__resultsModel
274 287
275 def hasFailedTests(self): 288 def hasFailedTests(self):
276 """ 289 """
277 Public method to check for failed tests. 290 Public method to check for failed tests.
278 291
279 @return flag indicating the existence of failed tests 292 @return flag indicating the existence of failed tests
280 @rtype bool 293 @rtype bool
281 """ 294 """
282 return bool(self.__resultsModel.getFailedTests()) 295 return bool(self.__resultsModel.getFailedTests())
283 296
284 def getFailedTests(self): 297 def getFailedTests(self):
285 """ 298 """
286 Public method to get the list of failed tests (if any). 299 Public method to get the list of failed tests (if any).
287 300
288 @return list of IDs of failed tests 301 @return list of IDs of failed tests
289 @rtype list of str 302 @rtype list of str
290 """ 303 """
291 return self.__failedTests[:] 304 return self.__failedTests[:]
292 305
293 @pyqtSlot(str) 306 @pyqtSlot(str)
294 def __insertHistory(self, widget, history, item): 307 def __insertHistory(self, widget, history, item):
295 """ 308 """
296 Private slot to insert an item into a history object. 309 Private slot to insert an item into a history object.
297 310
298 @param widget reference to the widget 311 @param widget reference to the widget
299 @type QComboBox or EricComboPathPicker 312 @type QComboBox or EricComboPathPicker
300 @param history array containing the history 313 @param history array containing the history
301 @type list of str 314 @type list of str
302 @param item item to be inserted 315 @param item item to be inserted
309 history.remove(item) 322 history.remove(item)
310 history.insert(0, item) 323 history.insert(0, item)
311 widget.clear() 324 widget.clear()
312 widget.addItems(history) 325 widget.addItems(history)
313 widget.setEditText(item) 326 widget.setEditText(item)
314 327
315 @pyqtSlot(str) 328 @pyqtSlot(str)
316 def __insertDiscovery(self, start): 329 def __insertDiscovery(self, start):
317 """ 330 """
318 Private slot to insert the discovery start directory into the 331 Private slot to insert the discovery start directory into the
319 discoveryPicker object. 332 discoveryPicker object.
320 333
321 @param start start directory name to be inserted 334 @param start start directory name to be inserted
322 @type str 335 @type str
323 """ 336 """
324 self.__insertHistory(self.discoveryPicker, self.__discoverHistory, 337 self.__insertHistory(self.discoveryPicker, self.__discoverHistory, start)
325 start) 338
326
327 @pyqtSlot(str) 339 @pyqtSlot(str)
328 def setTestFile(self, testFile, forProject=False): 340 def setTestFile(self, testFile, forProject=False):
329 """ 341 """
330 Public slot to set the given test file as the current one. 342 Public slot to set the given test file as the current one.
331 343
332 @param testFile path of the test file 344 @param testFile path of the test file
333 @type str 345 @type str
334 @param forProject flag indicating that this call is for a project 346 @param forProject flag indicating that this call is for a project
335 (defaults to False) 347 (defaults to False)
336 @type bool (optional) 348 @type bool (optional)
337 """ 349 """
338 if testFile: 350 if testFile:
339 self.__insertTestFile(testFile) 351 self.__insertTestFile(testFile)
340 352
341 self.discoverCheckBox.setChecked(forProject or not bool(testFile)) 353 self.discoverCheckBox.setChecked(forProject or not bool(testFile))
342 354
343 if forProject: 355 if forProject:
344 self.__projectOpened() 356 self.__projectOpened()
345 357
346 self.tabWidget.setCurrentIndex(0) 358 self.tabWidget.setCurrentIndex(0)
347 359
348 @pyqtSlot(str) 360 @pyqtSlot(str)
349 def __insertTestFile(self, prog): 361 def __insertTestFile(self, prog):
350 """ 362 """
351 Private slot to insert a test file name into the testsuitePicker 363 Private slot to insert a test file name into the testsuitePicker
352 object. 364 object.
353 365
354 @param prog test file name to be inserted 366 @param prog test file name to be inserted
355 @type str 367 @type str
356 """ 368 """
357 self.__insertHistory(self.testsuitePicker, self.__fileHistory, 369 self.__insertHistory(self.testsuitePicker, self.__fileHistory, prog)
358 prog) 370
359
360 @pyqtSlot(str) 371 @pyqtSlot(str)
361 def __insertTestName(self, testName): 372 def __insertTestName(self, testName):
362 """ 373 """
363 Private slot to insert a test name into the testComboBox object. 374 Private slot to insert a test name into the testComboBox object.
364 375
365 @param testName name of the test to be inserted 376 @param testName name of the test to be inserted
366 @type str 377 @type str
367 """ 378 """
368 self.__insertHistory(self.testComboBox, self.__testNameHistory, 379 self.__insertHistory(self.testComboBox, self.__testNameHistory, testName)
369 testName) 380
370
371 def __loadRecent(self): 381 def __loadRecent(self):
372 """ 382 """
373 Private method to load the most recently used lists. 383 Private method to load the most recently used lists.
374 """ 384 """
375 Preferences.Prefs.rsettings.sync() 385 Preferences.Prefs.rsettings.sync()
376 386
377 # 1. recently selected test framework and virtual environment 387 # 1. recently selected test framework and virtual environment
378 self.__recentEnvironment = Preferences.Prefs.rsettings.value( 388 self.__recentEnvironment = Preferences.Prefs.rsettings.value(
379 recentNameTestEnvironment, "") 389 recentNameTestEnvironment, ""
390 )
380 self.__recentFramework = Preferences.Prefs.rsettings.value( 391 self.__recentFramework = Preferences.Prefs.rsettings.value(
381 recentNameTestFramework, "") 392 recentNameTestFramework, ""
382 393 )
394
383 # 2. discovery history 395 # 2. discovery history
384 self.__discoverHistory = [] 396 self.__discoverHistory = []
385 rs = Preferences.Prefs.rsettings.value( 397 rs = Preferences.Prefs.rsettings.value(recentNameTestDiscoverHistory)
386 recentNameTestDiscoverHistory)
387 if rs is not None: 398 if rs is not None:
388 recent = [f for f in Preferences.toList(rs) if os.path.exists(f)] 399 recent = [f for f in Preferences.toList(rs) if os.path.exists(f)]
389 self.__discoverHistory = recent[ 400 self.__discoverHistory = recent[: Preferences.getDebugger("RecentNumber")]
390 :Preferences.getDebugger("RecentNumber")] 401
391
392 # 3. test file history 402 # 3. test file history
393 self.__fileHistory = [] 403 self.__fileHistory = []
394 rs = Preferences.Prefs.rsettings.value( 404 rs = Preferences.Prefs.rsettings.value(recentNameTestFileHistory)
395 recentNameTestFileHistory)
396 if rs is not None: 405 if rs is not None:
397 recent = [f for f in Preferences.toList(rs) if os.path.exists(f)] 406 recent = [f for f in Preferences.toList(rs) if os.path.exists(f)]
398 self.__fileHistory = recent[ 407 self.__fileHistory = recent[: Preferences.getDebugger("RecentNumber")]
399 :Preferences.getDebugger("RecentNumber")] 408
400
401 # 4. test name history 409 # 4. test name history
402 self.__testNameHistory = [] 410 self.__testNameHistory = []
403 rs = Preferences.Prefs.rsettings.value( 411 rs = Preferences.Prefs.rsettings.value(recentNameTestNameHistory)
404 recentNameTestNameHistory)
405 if rs is not None: 412 if rs is not None:
406 recent = [n for n in Preferences.toList(rs) if n] 413 recent = [n for n in Preferences.toList(rs) if n]
407 self.__testNameHistory = recent[ 414 self.__testNameHistory = recent[: Preferences.getDebugger("RecentNumber")]
408 :Preferences.getDebugger("RecentNumber")] 415
409
410 def __saveRecent(self): 416 def __saveRecent(self):
411 """ 417 """
412 Private method to save the most recently used lists. 418 Private method to save the most recently used lists.
413 """ 419 """
414 Preferences.Prefs.rsettings.setValue( 420 Preferences.Prefs.rsettings.setValue(
415 recentNameTestEnvironment, self.__recentEnvironment) 421 recentNameTestEnvironment, self.__recentEnvironment
422 )
416 Preferences.Prefs.rsettings.setValue( 423 Preferences.Prefs.rsettings.setValue(
417 recentNameTestFramework, self.__recentFramework) 424 recentNameTestFramework, self.__recentFramework
425 )
418 Preferences.Prefs.rsettings.setValue( 426 Preferences.Prefs.rsettings.setValue(
419 recentNameTestDiscoverHistory, self.__discoverHistory) 427 recentNameTestDiscoverHistory, self.__discoverHistory
428 )
420 Preferences.Prefs.rsettings.setValue( 429 Preferences.Prefs.rsettings.setValue(
421 recentNameTestFileHistory, self.__fileHistory) 430 recentNameTestFileHistory, self.__fileHistory
431 )
422 Preferences.Prefs.rsettings.setValue( 432 Preferences.Prefs.rsettings.setValue(
423 recentNameTestNameHistory, self.__testNameHistory) 433 recentNameTestNameHistory, self.__testNameHistory
424 434 )
435
425 Preferences.Prefs.rsettings.sync() 436 Preferences.Prefs.rsettings.sync()
426 437
427 @pyqtSlot() 438 @pyqtSlot()
428 def clearRecent(self): 439 def clearRecent(self):
429 """ 440 """
430 Public slot to clear the recently used lists. 441 Public slot to clear the recently used lists.
431 """ 442 """
432 # clear histories 443 # clear histories
433 self.__discoverHistory = [] 444 self.__discoverHistory = []
434 self.__fileHistory = [] 445 self.__fileHistory = []
435 self.__testNameHistory = [] 446 self.__testNameHistory = []
436 447
437 # clear widgets with histories 448 # clear widgets with histories
438 self.discoveryPicker.clear() 449 self.discoveryPicker.clear()
439 self.testsuitePicker.clear() 450 self.testsuitePicker.clear()
440 self.testComboBox.clear() 451 self.testComboBox.clear()
441 452
442 # sync histories 453 # sync histories
443 self.__saveRecent() 454 self.__saveRecent()
444 455
445 @pyqtSlot() 456 @pyqtSlot()
446 def __resetResults(self): 457 def __resetResults(self):
447 """ 458 """
448 Private slot to reset the test results tab and data. 459 Private slot to reset the test results tab and data.
449 """ 460 """
450 self.__totalCount = 0 461 self.__totalCount = 0
451 self.__runCount = 0 462 self.__runCount = 0
452 463
453 self.progressCounterRunCount.setText("0") 464 self.progressCounterRunCount.setText("0")
454 self.progressCounterRemCount.setText("0") 465 self.progressCounterRemCount.setText("0")
455 self.progressProgressBar.setMaximum(100) 466 self.progressProgressBar.setMaximum(100)
456 self.progressProgressBar.setValue(0) 467 self.progressProgressBar.setValue(0)
457 468
458 self.statusLabel.clear() 469 self.statusLabel.clear()
459 470
460 self.__resultsModel.clear() 471 self.__resultsModel.clear()
461 self.__updateButtonBoxButtons() 472 self.__updateButtonBoxButtons()
462 473
463 @pyqtSlot() 474 @pyqtSlot()
464 def __updateButtonBoxButtons(self): 475 def __updateButtonBoxButtons(self):
465 """ 476 """
466 Private slot to update the state of the buttons of the button box. 477 Private slot to update the state of the buttons of the button box.
467 """ 478 """
468 failedAvailable = bool(self.__resultsModel.getFailedTests()) 479 failedAvailable = bool(self.__resultsModel.getFailedTests())
469 480
470 # Start button 481 # Start button
471 if self.__mode in ( 482 if self.__mode in (TestingWidgetModes.IDLE, TestingWidgetModes.STOPPED):
472 TestingWidgetModes.IDLE, TestingWidgetModes.STOPPED
473 ):
474 self.__startButton.setEnabled( 483 self.__startButton.setEnabled(
475 bool(self.venvComboBox.currentText()) and 484 bool(self.venvComboBox.currentText())
476 bool(self.frameworkComboBox.currentText()) and 485 and bool(self.frameworkComboBox.currentText())
477 ( 486 and (
478 (self.discoverCheckBox.isChecked() and 487 (
479 bool(self.discoveryPicker.currentText())) or 488 self.discoverCheckBox.isChecked()
480 bool(self.testsuitePicker.currentText()) 489 and bool(self.discoveryPicker.currentText())
490 )
491 or bool(self.testsuitePicker.currentText())
481 ) 492 )
482 ) 493 )
483 self.__startButton.setDefault( 494 self.__startButton.setDefault(
484 self.__mode == TestingWidgetModes.IDLE or 495 self.__mode == TestingWidgetModes.IDLE or not failedAvailable
485 not failedAvailable
486 ) 496 )
487 else: 497 else:
488 self.__startButton.setEnabled(False) 498 self.__startButton.setEnabled(False)
489 self.__startButton.setDefault(False) 499 self.__startButton.setDefault(False)
490 500
491 # Start Failed button 501 # Start Failed button
492 self.__startFailedButton.setEnabled( 502 self.__startFailedButton.setEnabled(
493 self.__mode == TestingWidgetModes.STOPPED and 503 self.__mode == TestingWidgetModes.STOPPED and failedAvailable
494 failedAvailable
495 ) 504 )
496 self.__startFailedButton.setDefault( 505 self.__startFailedButton.setDefault(
497 self.__mode == TestingWidgetModes.STOPPED and 506 self.__mode == TestingWidgetModes.STOPPED and failedAvailable
498 failedAvailable 507 )
499 ) 508
500
501 # Stop button 509 # Stop button
502 self.__stopButton.setEnabled( 510 self.__stopButton.setEnabled(self.__mode == TestingWidgetModes.RUNNING)
503 self.__mode == TestingWidgetModes.RUNNING) 511 self.__stopButton.setDefault(self.__mode == TestingWidgetModes.RUNNING)
504 self.__stopButton.setDefault( 512
505 self.__mode == TestingWidgetModes.RUNNING)
506
507 # Code coverage button 513 # Code coverage button
508 self.__showCoverageButton.setEnabled( 514 self.__showCoverageButton.setEnabled(
509 self.__mode == TestingWidgetModes.STOPPED and 515 self.__mode == TestingWidgetModes.STOPPED
510 bool(self.__coverageFile) and 516 and bool(self.__coverageFile)
511 ( 517 and (
512 (self.discoverCheckBox.isChecked() and 518 (
513 bool(self.discoveryPicker.currentText())) or 519 self.discoverCheckBox.isChecked()
514 bool(self.testsuitePicker.currentText()) 520 and bool(self.discoveryPicker.currentText())
515 ) 521 )
516 ) 522 or bool(self.testsuitePicker.currentText())
517 523 )
524 )
525
518 # Log output button 526 # Log output button
519 self.__showLogButton.setEnabled(bool(self.__recentLog)) 527 self.__showLogButton.setEnabled(bool(self.__recentLog))
520 528
521 # Close button 529 # Close button
522 self.buttonBox.button( 530 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(
523 QDialogButtonBox.StandardButton.Close 531 self.__mode in (TestingWidgetModes.IDLE, TestingWidgetModes.STOPPED)
524 ).setEnabled(self.__mode in ( 532 )
525 TestingWidgetModes.IDLE, TestingWidgetModes.STOPPED 533
526 ))
527
528 @pyqtSlot() 534 @pyqtSlot()
529 def __updateProgress(self): 535 def __updateProgress(self):
530 """ 536 """
531 Private slot update the progress indicators. 537 Private slot update the progress indicators.
532 """ 538 """
533 self.progressCounterRunCount.setText( 539 self.progressCounterRunCount.setText(str(self.__runCount))
534 str(self.__runCount)) 540 self.progressCounterRemCount.setText(str(self.__totalCount - self.__runCount))
535 self.progressCounterRemCount.setText(
536 str(self.__totalCount - self.__runCount))
537 self.progressProgressBar.setMaximum(self.__totalCount) 541 self.progressProgressBar.setMaximum(self.__totalCount)
538 self.progressProgressBar.setValue(self.__runCount) 542 self.progressProgressBar.setValue(self.__runCount)
539 543
540 @pyqtSlot() 544 @pyqtSlot()
541 def __setIdleMode(self): 545 def __setIdleMode(self):
542 """ 546 """
543 Private slot to switch the widget to idle mode. 547 Private slot to switch the widget to idle mode.
544 """ 548 """
545 self.__mode = TestingWidgetModes.IDLE 549 self.__mode = TestingWidgetModes.IDLE
546 self.__updateButtonBoxButtons() 550 self.__updateButtonBoxButtons()
547 self.progressGroupBox.hide() 551 self.progressGroupBox.hide()
548 self.tabWidget.setCurrentIndex(0) 552 self.tabWidget.setCurrentIndex(0)
549 553
550 @pyqtSlot() 554 @pyqtSlot()
551 def __setRunningMode(self): 555 def __setRunningMode(self):
552 """ 556 """
553 Private slot to switch the widget to running mode. 557 Private slot to switch the widget to running mode.
554 """ 558 """
555 self.__mode = TestingWidgetModes.RUNNING 559 self.__mode = TestingWidgetModes.RUNNING
556 560
557 self.__totalCount = 0 561 self.__totalCount = 0
558 self.__runCount = 0 562 self.__runCount = 0
559 563
560 self.__coverageFile = "" 564 self.__coverageFile = ""
561 565
562 self.sbLabel.setText(self.tr("Running")) 566 self.sbLabel.setText(self.tr("Running"))
563 self.tabWidget.setCurrentIndex(1) 567 self.tabWidget.setCurrentIndex(1)
564 self.__updateButtonBoxButtons() 568 self.__updateButtonBoxButtons()
565 self.__updateProgress() 569 self.__updateProgress()
566 570
567 self.progressGroupBox.show() 571 self.progressGroupBox.show()
568 572
569 @pyqtSlot() 573 @pyqtSlot()
570 def __setStoppedMode(self): 574 def __setStoppedMode(self):
571 """ 575 """
572 Private slot to switch the widget to stopped mode. 576 Private slot to switch the widget to stopped mode.
573 """ 577 """
574 self.__mode = TestingWidgetModes.STOPPED 578 self.__mode = TestingWidgetModes.STOPPED
575 if self.__totalCount == 0: 579 if self.__totalCount == 0:
576 self.progressProgressBar.setMaximum(100) 580 self.progressProgressBar.setMaximum(100)
577 581
578 self.progressGroupBox.hide() 582 self.progressGroupBox.hide()
579 583
580 self.__updateButtonBoxButtons() 584 self.__updateButtonBoxButtons()
581 585
582 self.testRunStopped.emit() 586 self.testRunStopped.emit()
583 587
584 self.raise_() 588 self.raise_()
585 self.activateWindow() 589 self.activateWindow()
586 590
587 @pyqtSlot(bool) 591 @pyqtSlot(bool)
588 def on_discoverCheckBox_toggled(self, checked): 592 def on_discoverCheckBox_toggled(self, checked):
589 """ 593 """
590 Private slot handling state changes of the 'discover' checkbox. 594 Private slot handling state changes of the 'discover' checkbox.
591 595
592 @param checked state of the checkbox 596 @param checked state of the checkbox
593 @type bool 597 @type bool
594 """ 598 """
595 if not bool(self.discoveryPicker.currentText()): 599 if not bool(self.discoveryPicker.currentText()):
596 if self.__project and self.__project.isOpen(): 600 if self.__project and self.__project.isOpen():
597 self.__insertDiscovery(self.__project.getProjectPath()) 601 self.__insertDiscovery(self.__project.getProjectPath())
598 else: 602 else:
599 self.__insertDiscovery( 603 self.__insertDiscovery(Preferences.getMultiProject("Workspace"))
600 Preferences.getMultiProject("Workspace")) 604
601
602 self.__resetResults() 605 self.__resetResults()
603 606
604 @pyqtSlot() 607 @pyqtSlot()
605 def on_testsuitePicker_aboutToShowPathPickerDialog(self): 608 def on_testsuitePicker_aboutToShowPathPickerDialog(self):
606 """ 609 """
607 Private slot called before the test file selection dialog is shown. 610 Private slot called before the test file selection dialog is shown.
608 """ 611 """
609 if self.__project: 612 if self.__project:
610 # we were called from within eric 613 # we were called from within eric
611 py3Extensions = ' '.join([ 614 py3Extensions = " ".join(
612 "*{0}".format(ext) 615 [
613 for ext in 616 "*{0}".format(ext)
614 ericApp().getObject("DebugServer").getExtensions('Python3') 617 for ext in ericApp()
615 ]) 618 .getObject("DebugServer")
616 fileFilter = self.tr( 619 .getExtensions("Python3")
617 "Python3 Files ({0});;All Files (*)" 620 ]
618 ).format(py3Extensions) 621 )
622 fileFilter = self.tr("Python3 Files ({0});;All Files (*)").format(
623 py3Extensions
624 )
619 else: 625 else:
620 # standalone application 626 # standalone application
621 fileFilter = self.tr("Python Files (*.py);;All Files (*)") 627 fileFilter = self.tr("Python Files (*.py);;All Files (*)")
622 self.testsuitePicker.setFilters(fileFilter) 628 self.testsuitePicker.setFilters(fileFilter)
623 629
624 defaultDirectory = ( 630 defaultDirectory = (
625 self.__project.getProjectPath() 631 self.__project.getProjectPath()
626 if self.__project and self.__project.isOpen() else 632 if self.__project and self.__project.isOpen()
627 Preferences.getMultiProject("Workspace") 633 else Preferences.getMultiProject("Workspace")
628 ) 634 )
629 if not defaultDirectory: 635 if not defaultDirectory:
630 defaultDirectory = os.path.expanduser("~") 636 defaultDirectory = os.path.expanduser("~")
631 self.testsuitePicker.setDefaultDirectory(defaultDirectory) 637 self.testsuitePicker.setDefaultDirectory(defaultDirectory)
632 638
633 @pyqtSlot(QAbstractButton) 639 @pyqtSlot(QAbstractButton)
634 def on_buttonBox_clicked(self, button): 640 def on_buttonBox_clicked(self, button):
635 """ 641 """
636 Private slot called by a button of the button box clicked. 642 Private slot called by a button of the button box clicked.
637 643
638 @param button button that was clicked 644 @param button button that was clicked
639 @type QAbstractButton 645 @type QAbstractButton
640 """ 646 """
641 if button == self.__startButton: 647 if button == self.__startButton:
642 self.startTests() 648 self.startTests()
647 self.startTests(failedOnly=True) 653 self.startTests(failedOnly=True)
648 elif button == self.__showCoverageButton: 654 elif button == self.__showCoverageButton:
649 self.__showCoverageDialog() 655 self.__showCoverageDialog()
650 elif button == self.__showLogButton: 656 elif button == self.__showLogButton:
651 self.__showLogOutput() 657 self.__showLogOutput()
652 658
653 @pyqtSlot(int) 659 @pyqtSlot(int)
654 def on_venvComboBox_currentIndexChanged(self, index): 660 def on_venvComboBox_currentIndexChanged(self, index):
655 """ 661 """
656 Private slot handling the selection of a virtual environment. 662 Private slot handling the selection of a virtual environment.
657 663
658 @param index index of the selected environment 664 @param index index of the selected environment
659 @type int 665 @type int
660 """ 666 """
661 self.__populateTestFrameworkComboBox() 667 self.__populateTestFrameworkComboBox()
662 self.__updateButtonBoxButtons() 668 self.__updateButtonBoxButtons()
663 669
664 self.versionsButton.setEnabled(bool(self.venvComboBox.currentText())) 670 self.versionsButton.setEnabled(bool(self.venvComboBox.currentText()))
665 671
666 self.__updateCoverage() 672 self.__updateCoverage()
667 673
668 @pyqtSlot(int) 674 @pyqtSlot(int)
669 def on_frameworkComboBox_currentIndexChanged(self, index): 675 def on_frameworkComboBox_currentIndexChanged(self, index):
670 """ 676 """
671 Private slot handling the selection of a test framework. 677 Private slot handling the selection of a test framework.
672 678
673 @param index index of the selected framework 679 @param index index of the selected framework
674 @type int 680 @type int
675 """ 681 """
676 self.__resetResults() 682 self.__resetResults()
677 self.__updateCoverage() 683 self.__updateCoverage()
678 684
679 @pyqtSlot() 685 @pyqtSlot()
680 def __updateCoverage(self): 686 def __updateCoverage(self):
681 """ 687 """
682 Private slot to update the state of the coverage checkbox depending on 688 Private slot to update the state of the coverage checkbox depending on
683 the selected framework's capabilities. 689 the selected framework's capabilities.
684 """ 690 """
685 hasCoverage = False 691 hasCoverage = False
686 692
687 venvName = self.venvComboBox.currentText() 693 venvName = self.venvComboBox.currentText()
688 if venvName: 694 if venvName:
689 framework = self.frameworkComboBox.currentText() 695 framework = self.frameworkComboBox.currentText()
690 if framework: 696 if framework:
691 interpreter = self.__venvManager.getVirtualenvInterpreter( 697 interpreter = self.__venvManager.getVirtualenvInterpreter(venvName)
692 venvName) 698 executor = self.__frameworkRegistry.createExecutor(framework, self)
693 executor = self.__frameworkRegistry.createExecutor(
694 framework, self)
695 hasCoverage = executor.hasCoverage(interpreter) 699 hasCoverage = executor.hasCoverage(interpreter)
696 700
697 self.coverageCheckBox.setEnabled(hasCoverage) 701 self.coverageCheckBox.setEnabled(hasCoverage)
698 if not hasCoverage: 702 if not hasCoverage:
699 self.coverageCheckBox.setChecked(False) 703 self.coverageCheckBox.setChecked(False)
700 704
701 @pyqtSlot() 705 @pyqtSlot()
702 def on_versionsButton_clicked(self): 706 def on_versionsButton_clicked(self):
703 """ 707 """
704 Private slot to show the versions of available plugins. 708 Private slot to show the versions of available plugins.
705 """ 709 """
706 venvName = self.venvComboBox.currentText() 710 venvName = self.venvComboBox.currentText()
707 if venvName: 711 if venvName:
708 headerText = self.tr("<h3>Versions of Frameworks and their" 712 headerText = self.tr("<h3>Versions of Frameworks and their" " Plugins</h3>")
709 " Plugins</h3>")
710 versionsText = "" 713 versionsText = ""
711 interpreter = self.__venvManager.getVirtualenvInterpreter(venvName) 714 interpreter = self.__venvManager.getVirtualenvInterpreter(venvName)
712 for framework in sorted( 715 for framework in sorted(self.__frameworkRegistry.getFrameworks().keys()):
713 self.__frameworkRegistry.getFrameworks().keys() 716 executor = self.__frameworkRegistry.createExecutor(framework, self)
714 ):
715 executor = self.__frameworkRegistry.createExecutor(
716 framework, self)
717 versions = executor.getVersions(interpreter) 717 versions = executor.getVersions(interpreter)
718 if versions: 718 if versions:
719 txt = "<p><strong>{0} {1}</strong>".format( 719 txt = "<p><strong>{0} {1}</strong>".format(
720 versions["name"], versions["version"]) 720 versions["name"], versions["version"]
721 721 )
722
722 if versions["plugins"]: 723 if versions["plugins"]:
723 txt += "<table>" 724 txt += "<table>"
724 for pluginVersion in versions["plugins"]: 725 for pluginVersion in versions["plugins"]:
725 txt += self.tr( 726 txt += self.tr("<tr><td>{0}</td><td>{1}</td></tr>").format(
726 "<tr><td>{0}</td><td>{1}</td></tr>"
727 ).format(
728 pluginVersion["name"], pluginVersion["version"] 727 pluginVersion["name"], pluginVersion["version"]
729 ) 728 )
730 txt += "</table>" 729 txt += "</table>"
731 txt += "</p>" 730 txt += "</p>"
732 731
733 versionsText += txt 732 versionsText += txt
734 733
735 if not versionsText: 734 if not versionsText:
736 versionsText = self.tr("No version information available.") 735 versionsText = self.tr("No version information available.")
737 736
738 EricMessageBox.information( 737 EricMessageBox.information(
739 self, 738 self, self.tr("Versions"), headerText + versionsText
740 self.tr("Versions"), 739 )
741 headerText + versionsText 740
742 )
743
744 @pyqtSlot() 741 @pyqtSlot()
745 def startTests(self, failedOnly=False): 742 def startTests(self, failedOnly=False):
746 """ 743 """
747 Public slot to start the test run. 744 Public slot to start the test run.
748 745
749 @param failedOnly flag indicating to run only failed tests 746 @param failedOnly flag indicating to run only failed tests
750 @type bool 747 @type bool
751 """ 748 """
752 if self.__mode == TestingWidgetModes.RUNNING: 749 if self.__mode == TestingWidgetModes.RUNNING:
753 return 750 return
754 751
755 self.__recentLog = "" 752 self.__recentLog = ""
756 753
757 self.__recentEnvironment = self.venvComboBox.currentText() 754 self.__recentEnvironment = self.venvComboBox.currentText()
758 self.__recentFramework = self.frameworkComboBox.currentText() 755 self.__recentFramework = self.frameworkComboBox.currentText()
759 756
760 self.__failedTests = ( 757 self.__failedTests = self.__resultsModel.getFailedTests() if failedOnly else []
761 self.__resultsModel.getFailedTests()
762 if failedOnly else
763 []
764 )
765 discover = self.discoverCheckBox.isChecked() 758 discover = self.discoverCheckBox.isChecked()
766 if discover: 759 if discover:
767 discoveryStart = self.discoveryPicker.currentText() 760 discoveryStart = self.discoveryPicker.currentText()
768 testFileName = "" 761 testFileName = ""
769 testName = "" 762 testName = ""
770 763
771 if discoveryStart: 764 if discoveryStart:
772 self.__insertDiscovery(discoveryStart) 765 self.__insertDiscovery(discoveryStart)
773 else: 766 else:
774 discoveryStart = "" 767 discoveryStart = ""
775 testFileName = self.testsuitePicker.currentText() 768 testFileName = self.testsuitePicker.currentText()
776 if testFileName: 769 if testFileName:
777 self.__insertTestFile(testFileName) 770 self.__insertTestFile(testFileName)
778 testName = self.testComboBox.currentText() 771 testName = self.testComboBox.currentText()
779 if testName: 772 if testName:
780 self.__insertTestName(testName) 773 self.__insertTestName(testName)
781 774
782 self.sbLabel.setText(self.tr("Preparing Testsuite")) 775 self.sbLabel.setText(self.tr("Preparing Testsuite"))
783 QCoreApplication.processEvents() 776 QCoreApplication.processEvents()
784 777
785 if self.__project: 778 if self.__project:
786 mainScript = self.__project.getMainScript(True) 779 mainScript = self.__project.getMainScript(True)
787 coverageFile = ( 780 coverageFile = (
788 os.path.splitext(mainScript)[0] + ".coverage" 781 os.path.splitext(mainScript)[0] + ".coverage" if mainScript else ""
789 if mainScript else
790 ""
791 ) 782 )
792 else: 783 else:
793 coverageFile = "" 784 coverageFile = ""
794 interpreter = self.__venvManager.getVirtualenvInterpreter( 785 interpreter = self.__venvManager.getVirtualenvInterpreter(
795 self.__recentEnvironment) 786 self.__recentEnvironment
787 )
796 config = TestConfig( 788 config = TestConfig(
797 interpreter=interpreter, 789 interpreter=interpreter,
798 discover=discover, 790 discover=discover,
799 discoveryStart=discoveryStart, 791 discoveryStart=discoveryStart,
800 testFilename=testFileName, 792 testFilename=testFileName,
803 failedOnly=failedOnly, 795 failedOnly=failedOnly,
804 collectCoverage=self.coverageCheckBox.isChecked(), 796 collectCoverage=self.coverageCheckBox.isChecked(),
805 eraseCoverage=self.coverageEraseCheckBox.isChecked(), 797 eraseCoverage=self.coverageEraseCheckBox.isChecked(),
806 coverageFile=coverageFile, 798 coverageFile=coverageFile,
807 ) 799 )
808 800
809 self.__testExecutor = self.__frameworkRegistry.createExecutor( 801 self.__testExecutor = self.__frameworkRegistry.createExecutor(
810 self.__recentFramework, self) 802 self.__recentFramework, self
803 )
811 self.__testExecutor.collected.connect(self.__testsCollected) 804 self.__testExecutor.collected.connect(self.__testsCollected)
812 self.__testExecutor.collectError.connect(self.__testsCollectError) 805 self.__testExecutor.collectError.connect(self.__testsCollectError)
813 self.__testExecutor.startTest.connect(self.__testStarted) 806 self.__testExecutor.startTest.connect(self.__testStarted)
814 self.__testExecutor.testResult.connect(self.__processTestResult) 807 self.__testExecutor.testResult.connect(self.__processTestResult)
815 self.__testExecutor.testFinished.connect(self.__testProcessFinished) 808 self.__testExecutor.testFinished.connect(self.__testProcessFinished)
816 self.__testExecutor.testRunFinished.connect(self.__testRunFinished) 809 self.__testExecutor.testRunFinished.connect(self.__testRunFinished)
817 self.__testExecutor.stop.connect(self.__testsStopped) 810 self.__testExecutor.stop.connect(self.__testsStopped)
818 self.__testExecutor.coverageDataSaved.connect(self.__coverageData) 811 self.__testExecutor.coverageDataSaved.connect(self.__coverageData)
819 self.__testExecutor.testRunAboutToBeStarted.connect( 812 self.__testExecutor.testRunAboutToBeStarted.connect(
820 self.__testRunAboutToBeStarted) 813 self.__testRunAboutToBeStarted
821 814 )
815
822 self.__setRunningMode() 816 self.__setRunningMode()
823 self.__testExecutor.start(config, []) 817 self.__testExecutor.start(config, [])
824 818
825 @pyqtSlot() 819 @pyqtSlot()
826 def __stopTests(self): 820 def __stopTests(self):
827 """ 821 """
828 Private slot to stop the current test run. 822 Private slot to stop the current test run.
829 """ 823 """
830 self.__testExecutor.stopIfRunning() 824 self.__testExecutor.stopIfRunning()
831 825
832 @pyqtSlot(list) 826 @pyqtSlot(list)
833 def __testsCollected(self, testNames): 827 def __testsCollected(self, testNames):
834 """ 828 """
835 Private slot handling the 'collected' signal of the executor. 829 Private slot handling the 'collected' signal of the executor.
836 830
837 @param testNames list of tuples containing the test id, the test name 831 @param testNames list of tuples containing the test id, the test name
838 and a description of collected tests 832 and a description of collected tests
839 @type list of tuple of (str, str, str) 833 @type list of tuple of (str, str, str)
840 """ 834 """
841 testResults = [ 835 testResults = [
843 category=TestResultCategory.PENDING, 837 category=TestResultCategory.PENDING,
844 status=self.tr("pending"), 838 status=self.tr("pending"),
845 name=name, 839 name=name,
846 id=id, 840 id=id,
847 message=desc, 841 message=desc,
848 ) for id, name, desc in testNames 842 )
843 for id, name, desc in testNames
849 ] 844 ]
850 self.__resultsModel.addTestResults(testResults) 845 self.__resultsModel.addTestResults(testResults)
851 846
852 self.__totalCount += len(testResults) 847 self.__totalCount += len(testResults)
853 self.__updateProgress() 848 self.__updateProgress()
854 849
855 @pyqtSlot(list) 850 @pyqtSlot(list)
856 def __testsCollectError(self, errors): 851 def __testsCollectError(self, errors):
857 """ 852 """
858 Private slot handling the 'collectError' signal of the executor. 853 Private slot handling the 'collectError' signal of the executor.
859 854
860 @param errors list of tuples containing the test name and a description 855 @param errors list of tuples containing the test name and a description
861 of the error 856 of the error
862 @type list of tuple of (str, str) 857 @type list of tuple of (str, str)
863 """ 858 """
864 testResults = [] 859 testResults = []
865 860
866 for testFile, error in errors: 861 for testFile, error in errors:
867 if testFile: 862 if testFile:
868 testResults.append(TestResult( 863 testResults.append(
869 category=TestResultCategory.FAIL, 864 TestResult(
870 status=self.tr("Failure"), 865 category=TestResultCategory.FAIL,
871 name=testFile, 866 status=self.tr("Failure"),
872 id=testFile, 867 name=testFile,
873 message=self.tr("Collection Error"), 868 id=testFile,
874 extra=error.splitlines() 869 message=self.tr("Collection Error"),
875 )) 870 extra=error.splitlines(),
871 )
872 )
876 else: 873 else:
877 EricMessageBox.critical( 874 EricMessageBox.critical(
878 self, 875 self,
879 self.tr("Collection Error"), 876 self.tr("Collection Error"),
880 self.tr( 877 self.tr(
881 "<p>There was an error while collecting tests." 878 "<p>There was an error while collecting tests." "</p><p>{0}</p>"
882 "</p><p>{0}</p>" 879 ).format("<br/>".join(error.splitlines())),
883 ).format("<br/>".join(error.splitlines()))
884 ) 880 )
885 881
886 if testResults: 882 if testResults:
887 self.__resultsModel.addTestResults(testResults) 883 self.__resultsModel.addTestResults(testResults)
888 884
889 @pyqtSlot(tuple) 885 @pyqtSlot(tuple)
890 def __testStarted(self, test): 886 def __testStarted(self, test):
891 """ 887 """
892 Private slot handling the 'startTest' signal of the executor. 888 Private slot handling the 'startTest' signal of the executor.
893 889
894 @param test tuple containing the id, name and short description of the 890 @param test tuple containing the id, name and short description of the
895 tests about to be run 891 tests about to be run
896 @type tuple of (str, str, str) 892 @type tuple of (str, str, str)
897 """ 893 """
898 self.__resultsModel.updateTestResults([ 894 self.__resultsModel.updateTestResults(
899 TestResult( 895 [
900 category=TestResultCategory.RUNNING, 896 TestResult(
901 status=self.tr("running"), 897 category=TestResultCategory.RUNNING,
902 id=test[0], 898 status=self.tr("running"),
903 name=test[1], 899 id=test[0],
904 message="" if test[2] is None else test[2], 900 name=test[1],
905 ) 901 message="" if test[2] is None else test[2],
906 ]) 902 )
907 903 ]
904 )
905
908 @pyqtSlot(TestResult) 906 @pyqtSlot(TestResult)
909 def __processTestResult(self, result): 907 def __processTestResult(self, result):
910 """ 908 """
911 Private slot to handle the receipt of a test result object. 909 Private slot to handle the receipt of a test result object.
912 910
913 @param result test result object 911 @param result test result object
914 @type TestResult 912 @type TestResult
915 """ 913 """
916 if not result.subtestResult: 914 if not result.subtestResult:
917 self.__runCount += 1 915 self.__runCount += 1
918 self.__updateProgress() 916 self.__updateProgress()
919 917
920 self.__resultsModel.updateTestResults([result]) 918 self.__resultsModel.updateTestResults([result])
921 919
922 @pyqtSlot(list, str) 920 @pyqtSlot(list, str)
923 def __testProcessFinished(self, results, output): 921 def __testProcessFinished(self, results, output):
924 """ 922 """
925 Private slot to handle the 'testFinished' signal of the executor. 923 Private slot to handle the 'testFinished' signal of the executor.
926 924
927 @param results list of test result objects (if not sent via the 925 @param results list of test result objects (if not sent via the
928 'testResult' signal 926 'testResult' signal
929 @type list of TestResult 927 @type list of TestResult
930 @param output string containing the test process output (if any) 928 @param output string containing the test process output (if any)
931 @type str 929 @type str
932 """ 930 """
933 self.__recentLog = output 931 self.__recentLog = output
934 932
935 self.__setStoppedMode() 933 self.__setStoppedMode()
936 self.__testExecutor = None 934 self.__testExecutor = None
937 935
938 self.__adjustPendingState() 936 self.__adjustPendingState()
939 937
940 @pyqtSlot(int, float) 938 @pyqtSlot(int, float)
941 def __testRunFinished(self, noTests, duration): 939 def __testRunFinished(self, noTests, duration):
942 """ 940 """
943 Private slot to handle the 'testRunFinished' signal of the executor. 941 Private slot to handle the 'testRunFinished' signal of the executor.
944 942
945 @param noTests number of tests run by the executor 943 @param noTests number of tests run by the executor
946 @type int 944 @type int
947 @param duration time needed in seconds to run the tests 945 @param duration time needed in seconds to run the tests
948 @type float 946 @type float
949 """ 947 """
950 self.sbLabel.setText( 948 self.sbLabel.setText(
951 self.tr("Ran %n test(s) in {0}s", "", noTests).format( 949 self.tr("Ran %n test(s) in {0}s", "", noTests).format(
952 locale.format_string("%.3f", duration, grouping=True) 950 locale.format_string("%.3f", duration, grouping=True)
953 ) 951 )
954 ) 952 )
955 953
956 self.__setStoppedMode() 954 self.__setStoppedMode()
957 955
958 @pyqtSlot() 956 @pyqtSlot()
959 def __testsStopped(self): 957 def __testsStopped(self):
960 """ 958 """
961 Private slot to handle the 'stop' signal of the executor. 959 Private slot to handle the 'stop' signal of the executor.
962 """ 960 """
963 self.sbLabel.setText(self.tr("Ran %n test(s)", "", self.__runCount)) 961 self.sbLabel.setText(self.tr("Ran %n test(s)", "", self.__runCount))
964 962
965 self.__setStoppedMode() 963 self.__setStoppedMode()
966 964
967 @pyqtSlot() 965 @pyqtSlot()
968 def __testRunAboutToBeStarted(self): 966 def __testRunAboutToBeStarted(self):
969 """ 967 """
970 Private slot to handle the 'testRunAboutToBeStarted' signal of the 968 Private slot to handle the 'testRunAboutToBeStarted' signal of the
971 executor. 969 executor.
972 """ 970 """
973 self.__resultsModel.clear() 971 self.__resultsModel.clear()
974 972
975 def __adjustPendingState(self): 973 def __adjustPendingState(self):
976 """ 974 """
977 Private method to change the status indicator of all still pending 975 Private method to change the status indicator of all still pending
978 tests to "not run". 976 tests to "not run".
979 """ 977 """
981 for result in self.__resultsModel.getTestResults(): 979 for result in self.__resultsModel.getTestResults():
982 if result.category == TestResultCategory.PENDING: 980 if result.category == TestResultCategory.PENDING:
983 result.category = TestResultCategory.SKIP 981 result.category = TestResultCategory.SKIP
984 result.status = self.tr("not run") 982 result.status = self.tr("not run")
985 newResults.append(result) 983 newResults.append(result)
986 984
987 if newResults: 985 if newResults:
988 self.__resultsModel.updateTestResults(newResults) 986 self.__resultsModel.updateTestResults(newResults)
989 987
990 @pyqtSlot(str) 988 @pyqtSlot(str)
991 def __coverageData(self, coverageFile): 989 def __coverageData(self, coverageFile):
992 """ 990 """
993 Private slot to handle the 'coverageData' signal of the executor. 991 Private slot to handle the 'coverageData' signal of the executor.
994 992
995 @param coverageFile file containing the coverage data 993 @param coverageFile file containing the coverage data
996 @type str 994 @type str
997 """ 995 """
998 self.__coverageFile = coverageFile 996 self.__coverageFile = coverageFile
999 997
1000 @pyqtSlot() 998 @pyqtSlot()
1001 def __showCoverageDialog(self): 999 def __showCoverageDialog(self):
1002 """ 1000 """
1003 Private slot to show a code coverage dialog for the most recent test 1001 Private slot to show a code coverage dialog for the most recent test
1004 run. 1002 run.
1005 """ 1003 """
1006 if self.__coverageDialog is None: 1004 if self.__coverageDialog is None:
1007 from DataViews.PyCoverageDialog import PyCoverageDialog 1005 from DataViews.PyCoverageDialog import PyCoverageDialog
1006
1008 self.__coverageDialog = PyCoverageDialog(self) 1007 self.__coverageDialog = PyCoverageDialog(self)
1009 self.__coverageDialog.openFile.connect(self.__openEditor) 1008 self.__coverageDialog.openFile.connect(self.__openEditor)
1010 1009
1011 testDir = ( 1010 testDir = (
1012 self.discoveryPicker.currentText() 1011 self.discoveryPicker.currentText()
1013 if self.discoverCheckBox.isChecked() else 1012 if self.discoverCheckBox.isChecked()
1014 os.path.dirname(self.testsuitePicker.currentText()) 1013 else os.path.dirname(self.testsuitePicker.currentText())
1015 ) 1014 )
1016 if testDir: 1015 if testDir:
1017 self.__coverageDialog.show() 1016 self.__coverageDialog.show()
1018 self.__coverageDialog.start(self.__coverageFile, testDir) 1017 self.__coverageDialog.start(self.__coverageFile, testDir)
1019 1018
1020 @pyqtSlot() 1019 @pyqtSlot()
1021 def __showLogOutput(self): 1020 def __showLogOutput(self):
1022 """ 1021 """
1023 Private slot to show the output of the most recent test run. 1022 Private slot to show the output of the most recent test run.
1024 """ 1023 """
1025 from EricWidgets.EricPlainTextDialog import EricPlainTextDialog 1024 from EricWidgets.EricPlainTextDialog import EricPlainTextDialog
1025
1026 dlg = EricPlainTextDialog( 1026 dlg = EricPlainTextDialog(
1027 title=self.tr("Test Run Output"), 1027 title=self.tr("Test Run Output"), text=self.__recentLog
1028 text=self.__recentLog
1029 ) 1028 )
1030 dlg.exec() 1029 dlg.exec()
1031 1030
1032 @pyqtSlot(str) 1031 @pyqtSlot(str)
1033 def __setStatusLabel(self, statusText): 1032 def __setStatusLabel(self, statusText):
1034 """ 1033 """
1035 Private slot to set the status label to the text sent by the model. 1034 Private slot to set the status label to the text sent by the model.
1036 1035
1037 @param statusText text to be shown 1036 @param statusText text to be shown
1038 @type str 1037 @type str
1039 """ 1038 """
1040 self.statusLabel.setText(f"<b>{statusText}</b>") 1039 self.statusLabel.setText(f"<b>{statusText}</b>")
1041 1040
1042 @pyqtSlot() 1041 @pyqtSlot()
1043 def __projectOpened(self): 1042 def __projectOpened(self):
1044 """ 1043 """
1045 Private slot to handle a project being opened. 1044 Private slot to handle a project being opened.
1046 """ 1045 """
1047 self.venvComboBox.setCurrentText(self.__project.getProjectVenv()) 1046 self.venvComboBox.setCurrentText(self.__project.getProjectVenv())
1048 self.frameworkComboBox.setCurrentText( 1047 self.frameworkComboBox.setCurrentText(
1049 self.__project.getProjectTestingFramework()) 1048 self.__project.getProjectTestingFramework()
1049 )
1050 self.__insertDiscovery(self.__project.getProjectPath()) 1050 self.__insertDiscovery(self.__project.getProjectPath())
1051 1051
1052 @pyqtSlot() 1052 @pyqtSlot()
1053 def __projectClosed(self): 1053 def __projectClosed(self):
1054 """ 1054 """
1055 Private slot to handle a project being closed. 1055 Private slot to handle a project being closed.
1056 """ 1056 """
1057 self.venvComboBox.setCurrentText("") 1057 self.venvComboBox.setCurrentText("")
1058 self.frameworkComboBox.setCurrentText("") 1058 self.frameworkComboBox.setCurrentText("")
1059 self.__insertDiscovery("") 1059 self.__insertDiscovery("")
1060 1060
1061 @pyqtSlot(str, int) 1061 @pyqtSlot(str, int)
1062 def __showSource(self, filename, lineno): 1062 def __showSource(self, filename, lineno):
1063 """ 1063 """
1064 Private slot to show the source of a traceback in an editor. 1064 Private slot to show the source of a traceback in an editor.
1065 1065
1066 @param filename file name of the file to be shown 1066 @param filename file name of the file to be shown
1067 @type str 1067 @type str
1068 @param lineno line number to go to in the file 1068 @param lineno line number to go to in the file
1069 @type int 1069 @type int
1070 """ 1070 """
1071 if self.__project: 1071 if self.__project:
1072 # running as part of eric IDE 1072 # running as part of eric IDE
1073 self.testFile.emit(filename, lineno, True) 1073 self.testFile.emit(filename, lineno, True)
1074 else: 1074 else:
1075 self.__openEditor(filename, lineno) 1075 self.__openEditor(filename, lineno)
1076 1076
1077 def __openEditor(self, filename, linenumber=1): 1077 def __openEditor(self, filename, linenumber=1):
1078 """ 1078 """
1079 Private method to open an editor window for the given file. 1079 Private method to open an editor window for the given file.
1080 1080
1081 Note: This method opens an editor window when the testing dialog 1081 Note: This method opens an editor window when the testing dialog
1082 is called as a standalone application. 1082 is called as a standalone application.
1083 1083
1084 @param filename path of the file to be opened 1084 @param filename path of the file to be opened
1085 @type str 1085 @type str
1086 @param linenumber line number to place the cursor at (defaults to 1) 1086 @param linenumber line number to place the cursor at (defaults to 1)
1087 @type int (optional) 1087 @type int (optional)
1088 """ 1088 """
1089 from QScintilla.MiniEditor import MiniEditor 1089 from QScintilla.MiniEditor import MiniEditor
1090
1090 editor = MiniEditor(filename, "Python3", self) 1091 editor = MiniEditor(filename, "Python3", self)
1091 editor.gotoLine(linenumber) 1092 editor.gotoLine(linenumber)
1092 editor.show() 1093 editor.show()
1093 1094
1094 self.__editors.append(editor) 1095 self.__editors.append(editor)
1095 1096
1096 def closeEvent(self, event): 1097 def closeEvent(self, event):
1097 """ 1098 """
1098 Protected method to handle the close event. 1099 Protected method to handle the close event.
1099 1100
1100 @param event close event 1101 @param event close event
1101 @type QCloseEvent 1102 @type QCloseEvent
1102 """ 1103 """
1103 event.accept() 1104 event.accept()
1104 1105
1105 for editor in self.__editors: 1106 for editor in self.__editors:
1106 with contextlib.suppress(Exception): 1107 with contextlib.suppress(Exception):
1107 editor.close() 1108 editor.close()
1108 1109
1109 1110
1110 class TestingWindow(EricMainWindow): 1111 class TestingWindow(EricMainWindow):
1111 """ 1112 """
1112 Main window class for the standalone dialog. 1113 Main window class for the standalone dialog.
1113 """ 1114 """
1115
1114 def __init__(self, testfile=None, parent=None): 1116 def __init__(self, testfile=None, parent=None):
1115 """ 1117 """
1116 Constructor 1118 Constructor
1117 1119
1118 @param testfile file name of the test script to open 1120 @param testfile file name of the test script to open
1119 @type str 1121 @type str
1120 @param parent reference to the parent widget 1122 @param parent reference to the parent widget
1121 @type QWidget 1123 @type QWidget
1122 """ 1124 """
1124 self.__cw = TestingWidget(testfile=testfile, parent=self) 1126 self.__cw = TestingWidget(testfile=testfile, parent=self)
1125 self.__cw.installEventFilter(self) 1127 self.__cw.installEventFilter(self)
1126 size = self.__cw.size() 1128 size = self.__cw.size()
1127 self.setCentralWidget(self.__cw) 1129 self.setCentralWidget(self.__cw)
1128 self.resize(size) 1130 self.resize(size)
1129 1131
1130 self.setStyle(Preferences.getUI("Style"), 1132 self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet"))
1131 Preferences.getUI("StyleSheet")) 1133
1132
1133 self.__cw.buttonBox.accepted.connect(self.close) 1134 self.__cw.buttonBox.accepted.connect(self.close)
1134 self.__cw.buttonBox.rejected.connect(self.close) 1135 self.__cw.buttonBox.rejected.connect(self.close)
1135 1136
1136 def eventFilter(self, obj, event): 1137 def eventFilter(self, obj, event):
1137 """ 1138 """
1138 Public method to filter events. 1139 Public method to filter events.
1139 1140
1140 @param obj reference to the object the event is meant for (QObject) 1141 @param obj reference to the object the event is meant for (QObject)
1141 @param event reference to the event object (QEvent) 1142 @param event reference to the event object (QEvent)
1142 @return flag indicating, whether the event was handled (boolean) 1143 @return flag indicating, whether the event was handled (boolean)
1143 """ 1144 """
1144 if event.type() == QEvent.Type.Close: 1145 if event.type() == QEvent.Type.Close:
1145 QCoreApplication.exit(0) 1146 QCoreApplication.exit(0)
1146 return True 1147 return True
1147 1148
1148 return False 1149 return False
1149 1150
1150 1151
1151 def clearSavedHistories(self): 1152 def clearSavedHistories(self):
1152 """ 1153 """
1153 Function to clear the saved history lists. 1154 Function to clear the saved history lists.
1154 """ 1155 """
1155 Preferences.Prefs.rsettings.setValue( 1156 Preferences.Prefs.rsettings.setValue(recentNameTestDiscoverHistory, [])
1156 recentNameTestDiscoverHistory, []) 1157 Preferences.Prefs.rsettings.setValue(recentNameTestFileHistory, [])
1157 Preferences.Prefs.rsettings.setValue( 1158 Preferences.Prefs.rsettings.setValue(recentNameTestNameHistory, [])
1158 recentNameTestFileHistory, []) 1159
1159 Preferences.Prefs.rsettings.setValue(
1160 recentNameTestNameHistory, [])
1161
1162 Preferences.Prefs.rsettings.sync() 1160 Preferences.Prefs.rsettings.sync()

eric ide

mercurial