PyUnit/UnittestDialog.py

branch
maintenance
changeset 6923
d062df8f1d9f
parent 6908
a56b500d7d2d
equal deleted inserted replaced
6827:14680839ad7a 6923:d062df8f1d9f
16 import os 16 import os
17 17
18 from PyQt5.QtCore import pyqtSignal, QEvent, Qt, pyqtSlot 18 from PyQt5.QtCore import pyqtSignal, QEvent, Qt, pyqtSlot
19 from PyQt5.QtGui import QColor 19 from PyQt5.QtGui import QColor
20 from PyQt5.QtWidgets import QWidget, QDialog, QApplication, QDialogButtonBox, \ 20 from PyQt5.QtWidgets import QWidget, QDialog, QApplication, QDialogButtonBox, \
21 QListWidgetItem, QComboBox 21 QListWidgetItem, QComboBox, QTreeWidgetItem
22 22
23 from E5Gui.E5Application import e5App 23 from E5Gui.E5Application import e5App
24 from E5Gui import E5MessageBox 24 from E5Gui import E5MessageBox
25 from E5Gui.E5MainWindow import E5MainWindow 25 from E5Gui.E5MainWindow import E5MainWindow
26 from E5Gui.E5PathPicker import E5PathPickerModes 26 from E5Gui.E5PathPicker import E5PathPickerModes
35 35
36 class UnittestDialog(QWidget, Ui_UnittestDialog): 36 class UnittestDialog(QWidget, Ui_UnittestDialog):
37 """ 37 """
38 Class implementing the UI to the pyunit package. 38 Class implementing the UI to the pyunit package.
39 39
40 @signal unittestFile(str, int, int) emitted to show the source of a 40 @signal unittestFile(str, int, bool) emitted to show the source of a
41 unittest file 41 unittest file
42 @signal unittestStopped() emitted after a unit test was run 42 @signal unittestStopped() emitted after a unit test was run
43 """ 43 """
44 unittestFile = pyqtSignal(str, int, int) 44 unittestFile = pyqtSignal(str, int, bool)
45 unittestStopped = pyqtSignal() 45 unittestStopped = pyqtSignal()
46 46
47 def __init__(self, prog=None, dbs=None, ui=None, fromEric=False, 47 TestCaseNameRole = Qt.UserRole
48 parent=None, name=None): 48 TestCaseFileRole = Qt.UserRole + 1
49
50 ErrorsInfoRole = Qt.UserRole
51
52 def __init__(self, prog=None, dbs=None, ui=None, parent=None, name=None):
49 """ 53 """
50 Constructor 54 Constructor
51 55
52 @param prog filename of the program to open 56 @param prog filename of the program to open
57 @type str
53 @param dbs reference to the debug server object. It is an indication 58 @param dbs reference to the debug server object. It is an indication
54 whether we were called from within the eric6 IDE 59 whether we were called from within the eric6 IDE.
60 @type DebugServer
55 @param ui reference to the UI object 61 @param ui reference to the UI object
56 @param fromEric flag indicating an instantiation from within the 62 @type UserInterface
57 eric IDE (boolean) 63 @param parent parent widget of this dialog
58 @param parent parent widget of this dialog (QWidget) 64 @type QWidget
59 @param name name of this dialog (string) 65 @param name name of this dialog
66 @type str
60 """ 67 """
61 super(UnittestDialog, self).__init__(parent) 68 super(UnittestDialog, self).__init__(parent)
62 if name: 69 if name:
63 self.setObjectName(name) 70 self.setObjectName(name)
64 self.setupUi(self) 71 self.setupUi(self)
66 self.testsuitePicker.setMode(E5PathPickerModes.OpenFileMode) 73 self.testsuitePicker.setMode(E5PathPickerModes.OpenFileMode)
67 self.testsuitePicker.setInsertPolicy(QComboBox.InsertAtTop) 74 self.testsuitePicker.setInsertPolicy(QComboBox.InsertAtTop)
68 self.testsuitePicker.setSizeAdjustPolicy( 75 self.testsuitePicker.setSizeAdjustPolicy(
69 QComboBox.AdjustToMinimumContentsLength) 76 QComboBox.AdjustToMinimumContentsLength)
70 77
78 self.discoveryPicker.setMode(E5PathPickerModes.DirectoryMode)
79 self.discoveryPicker.setInsertPolicy(QComboBox.InsertAtTop)
80 self.discoveryPicker.setSizeAdjustPolicy(
81 QComboBox.AdjustToMinimumContentsLength)
82
83 self.discoverButton = self.buttonBox.addButton(
84 self.tr("Discover"), QDialogButtonBox.ActionRole)
85 self.discoverButton.setToolTip(self.tr(
86 "Discover tests"))
87 self.discoverButton.setWhatsThis(self.tr(
88 """<b>Discover</b>"""
89 """<p>This button starts a discovery of available tests.</p>"""))
71 self.startButton = self.buttonBox.addButton( 90 self.startButton = self.buttonBox.addButton(
72 self.tr("Start"), QDialogButtonBox.ActionRole) 91 self.tr("Start"), QDialogButtonBox.ActionRole)
73 self.startButton.setToolTip(self.tr( 92 self.startButton.setToolTip(self.tr(
74 "Start the selected testsuite")) 93 "Start the selected testsuite"))
75 self.startButton.setWhatsThis(self.tr( 94 self.startButton.setWhatsThis(self.tr(
87 self.tr("Stop"), QDialogButtonBox.ActionRole) 106 self.tr("Stop"), QDialogButtonBox.ActionRole)
88 self.stopButton.setToolTip(self.tr("Stop the running unittest")) 107 self.stopButton.setToolTip(self.tr("Stop the running unittest"))
89 self.stopButton.setWhatsThis(self.tr( 108 self.stopButton.setWhatsThis(self.tr(
90 """<b>Stop Test</b>""" 109 """<b>Stop Test</b>"""
91 """<p>This button stops a running unittest.</p>""")) 110 """<p>This button stops a running unittest.</p>"""))
111 self.discoverButton.setEnabled(False)
92 self.stopButton.setEnabled(False) 112 self.stopButton.setEnabled(False)
93 self.startButton.setDefault(True) 113 self.startButton.setDefault(True)
94 self.startFailedButton.setEnabled(False) 114 self.startFailedButton.setEnabled(False)
95 115
96 self.dbs = dbs 116 self.__dbs = dbs
97 self.__fromEric = fromEric 117 self.__forProject = False
98 118
99 self.setWindowFlags( 119 self.setWindowFlags(
100 self.windowFlags() | Qt.WindowFlags( 120 self.windowFlags() | Qt.WindowFlags(
101 Qt.WindowContextHelpButtonHint)) 121 Qt.WindowContextHelpButtonHint))
102 self.setWindowIcon(UI.PixmapCache.getIcon("eric.png")) 122 self.setWindowIcon(UI.PixmapCache.getIcon("eric.png"))
103 self.setWindowTitle(self.tr("Unittest")) 123 self.setWindowTitle(self.tr("Unittest"))
104 if dbs: 124 if dbs:
105 self.ui = ui 125 self.ui = ui
126
127 self.debuggerCheckBox.setChecked(True)
128
129 # virtual environment manager is only used in the integrated
130 # variant
131 self.__venvManager = e5App().getObject("VirtualEnvManager")
132 self.__populateVenvComboBox()
133 self.__venvManager.virtualEnvironmentAdded.connect(
134 self.__populateVenvComboBox)
135 self.__venvManager.virtualEnvironmentRemoved.connect(
136 self.__populateVenvComboBox)
137 self.__venvManager.virtualEnvironmentChanged.connect(
138 self.__populateVenvComboBox)
106 else: 139 else:
107 self.localCheckBox.hide() 140 self.__venvManager = None
141 self.debuggerCheckBox.setVisible(False)
142 self.venvComboBox.setVisible(bool(self.__venvManager))
143 self.venvLabel.setVisible(bool(self.__venvManager))
144
108 self.__setProgressColor("green") 145 self.__setProgressColor("green")
109 self.progressLed.setDarkFactor(150) 146 self.progressLed.setDarkFactor(150)
110 self.progressLed.off() 147 self.progressLed.off()
111 148
149 self.discoverHistory = []
112 self.fileHistory = [] 150 self.fileHistory = []
113 self.testNameHistory = [] 151 self.testNameHistory = []
114 self.running = False 152 self.running = False
115 self.savedModulelist = None 153 self.savedModulelist = None
116 self.savedSysPath = sys.path 154 self.savedSysPath = sys.path
155 self.savedCwd = os.getcwd()
117 if prog: 156 if prog:
118 self.insertProg(prog) 157 self.insertProg(prog)
119 158
120 self.rxPatterns = [ 159 self.rxPatterns = [
121 self.tr("^Failure: "), 160 self.tr("^Failure: "),
126 ] 165 ]
127 166
128 self.__failedTests = [] 167 self.__failedTests = []
129 168
130 # now connect the debug server signals if called from the eric6 IDE 169 # now connect the debug server signals if called from the eric6 IDE
131 if self.dbs: 170 if self.__dbs:
132 self.dbs.utPrepared.connect(self.__UTPrepared) 171 self.__dbs.utDiscovered.connect(self.__UTDiscovered)
133 self.dbs.utFinished.connect(self.__setStoppedMode) 172 self.__dbs.utPrepared.connect(self.__UTPrepared)
134 self.dbs.utStartTest.connect(self.testStarted) 173 self.__dbs.utFinished.connect(self.__setStoppedMode)
135 self.dbs.utStopTest.connect(self.testFinished) 174 self.__dbs.utStartTest.connect(self.testStarted)
136 self.dbs.utTestFailed.connect(self.testFailed) 175 self.__dbs.utStopTest.connect(self.testFinished)
137 self.dbs.utTestErrored.connect(self.testErrored) 176 self.__dbs.utTestFailed.connect(self.testFailed)
138 self.dbs.utTestSkipped.connect(self.testSkipped) 177 self.__dbs.utTestErrored.connect(self.testErrored)
139 self.dbs.utTestFailedExpected.connect(self.testFailedExpected) 178 self.__dbs.utTestSkipped.connect(self.testSkipped)
140 self.dbs.utTestSucceededUnexpected.connect( 179 self.__dbs.utTestFailedExpected.connect(self.testFailedExpected)
180 self.__dbs.utTestSucceededUnexpected.connect(
141 self.testSucceededUnexpected) 181 self.testSucceededUnexpected)
142 182
143 self.__editors = [] 183 self.__editors = []
144 184
145 def keyPressEvent(self, evt): 185 def keyPressEvent(self, evt):
146 """ 186 """
147 Protected slot to handle key press events. 187 Protected slot to handle key press events.
148 188
149 @param evt key press event to handle (QKeyEvent) 189 @param evt key press event to handle (QKeyEvent)
150 """ 190 """
151 if evt.key() == Qt.Key_Escape and self.__fromEric: 191 if evt.key() == Qt.Key_Escape and self.__dbs:
152 self.close() 192 self.close()
153 193
194 def __populateVenvComboBox(self):
195 """
196 Private method to (re-)populate the virtual environments selector.
197 """
198 currentText = self.venvComboBox.currentText()
199 self.venvComboBox.clear()
200 self.venvComboBox.addItem("")
201 self.venvComboBox.addItems(
202 sorted(self.__venvManager.getVirtualenvNames()))
203 index = self.venvComboBox.findText(currentText)
204 if index < 0:
205 index = 0
206 self.venvComboBox.setCurrentIndex(index)
207
154 def __setProgressColor(self, color): 208 def __setProgressColor(self, color):
155 """ 209 """
156 Private methode to set the color of the progress color label. 210 Private methode to set the color of the progress color label.
157 211
158 @param color colour to be shown (string) 212 @param color colour to be shown (string)
159 """ 213 """
160 self.progressLed.setColor(QColor(color)) 214 self.progressLed.setColor(QColor(color))
161 215
216 def setProjectMode(self, forProject):
217 """
218 Public method to set the project mode of the dialog.
219
220 @param forProject flag indicating to run for the open project
221 @type bool
222 """
223 self.__forProject = forProject
224 if forProject:
225 project = e5App().getObject("Project")
226 if project.isOpen():
227 self.insertDiscovery(project.getProjectPath())
228 else:
229 self.insertDiscovery("")
230 else:
231 self.insertDiscovery("")
232
233 self.discoveryList.clear()
234 self.tabWidget.setCurrentIndex(0)
235
236 def insertDiscovery(self, start):
237 """
238 Public slot to insert the discovery start directory into the
239 discoveryPicker object.
240
241 @param start start directory name to be inserted
242 @type str
243 """
244 # prepend the given directory to the discovery picker
245 if start is None:
246 start = ""
247 if start in self.discoverHistory:
248 self.discoverHistory.remove(start)
249 self.discoverHistory.insert(0, start)
250 self.discoveryPicker.clear()
251 self.discoveryPicker.addItems(self.discoverHistory)
252
162 def insertProg(self, prog): 253 def insertProg(self, prog):
163 """ 254 """
164 Public slot to insert the filename prog into the testsuitePicker 255 Public slot to insert the filename prog into the testsuitePicker
165 object. 256 object.
166 257
172 if prog in self.fileHistory: 263 if prog in self.fileHistory:
173 self.fileHistory.remove(prog) 264 self.fileHistory.remove(prog)
174 self.fileHistory.insert(0, prog) 265 self.fileHistory.insert(0, prog)
175 self.testsuitePicker.clear() 266 self.testsuitePicker.clear()
176 self.testsuitePicker.addItems(self.fileHistory) 267 self.testsuitePicker.addItems(self.fileHistory)
177 268
178 def insertTestName(self, testName): 269 def insertTestName(self, testName):
179 """ 270 """
180 Public slot to insert a test name into the testComboBox object. 271 Public slot to insert a test name into the testComboBox object.
181 272
182 @param testName name of the test to be inserted (string) 273 @param testName name of the test to be inserted (string)
187 if testName in self.testNameHistory: 278 if testName in self.testNameHistory:
188 self.testNameHistory.remove(testName) 279 self.testNameHistory.remove(testName)
189 self.testNameHistory.insert(0, testName) 280 self.testNameHistory.insert(0, testName)
190 self.testComboBox.clear() 281 self.testComboBox.clear()
191 self.testComboBox.addItems(self.testNameHistory) 282 self.testComboBox.addItems(self.testNameHistory)
192 283
193 @pyqtSlot() 284 @pyqtSlot()
194 def on_testsuitePicker_aboutToShowPathPickerDialog(self): 285 def on_testsuitePicker_aboutToShowPathPickerDialog(self):
195 """ 286 """
196 Private slot called before the test suite selection dialog is shown. 287 Private slot called before the test suite selection dialog is shown.
197 """ 288 """
198 if self.dbs: 289 if self.__dbs:
199 py2Extensions = \ 290 py2Extensions = \
200 ' '.join(["*{0}".format(ext) 291 ' '.join(["*{0}".format(ext)
201 for ext in self.dbs.getExtensions('Python2')]) 292 for ext in self.__dbs.getExtensions('Python2')])
202 py3Extensions = \ 293 py3Extensions = \
203 ' '.join(["*{0}".format(ext) 294 ' '.join(["*{0}".format(ext)
204 for ext in self.dbs.getExtensions('Python3')]) 295 for ext in self.__dbs.getExtensions('Python3')])
205 fileFilter = self.tr( 296 fileFilter = self.tr(
206 "Python3 Files ({1});;Python2 Files ({0});;All Files (*)")\ 297 "Python3 Files ({1});;Python2 Files ({0});;All Files (*)")\
207 .format(py2Extensions, py3Extensions) 298 .format(py2Extensions, py3Extensions)
208 else: 299 else:
209 fileFilter = self.tr("Python Files (*.py);;All Files (*)") 300 fileFilter = self.tr("Python Files (*.py);;All Files (*)")
210 self.testsuitePicker.setFilters(fileFilter) 301 self.testsuitePicker.setFilters(fileFilter)
211 302
303 defaultDirectory = Preferences.getMultiProject("Workspace")
304 if not defaultDirectory:
305 defaultDirectory = os.path.expanduser("~")
306 if self.__dbs:
307 project = e5App().getObject("Project")
308 if self.__forProject and project.isOpen():
309 defaultDirectory = project.getProjectPath()
310 self.testsuitePicker.setDefaultDirectory(defaultDirectory)
311
212 @pyqtSlot(str) 312 @pyqtSlot(str)
213 def on_testsuitePicker_pathSelected(self, suite): 313 def on_testsuitePicker_pathSelected(self, suite):
214 """ 314 """
215 Private slot called after a test suite has been selected. 315 Private slot called after a test suite has been selected.
216 316
217 @param suite file name of the test suite 317 @param suite file name of the test suite
218 @type str 318 @type str
219 """ 319 """
220 self.insertProg(suite) 320 self.insertProg(suite)
221 321
222 @pyqtSlot(str) 322 @pyqtSlot(str)
223 def on_testsuitePicker_editTextChanged(self, txt): 323 def on_testsuitePicker_editTextChanged(self, path):
224 """ 324 """
225 Private slot to handle changes of the test file name. 325 Private slot handling changes of the test suite path.
226 326
227 @param txt name of the test file (string) 327 @param path path of the test suite file
228 """ 328 @type str
229 if self.dbs: 329 """
230 exts = self.dbs.getExtensions("Python2") 330 self.startFailedButton.setEnabled(False)
231 flags = Utilities.extractFlagsFromFile(txt) 331
232 if txt.endswith(exts) or \ 332 @pyqtSlot(bool)
233 ("FileType" in flags and 333 def on_discoverCheckBox_toggled(self, checked):
234 flags["FileType"] in ["Python", "Python2"]): 334 """
235 self.coverageCheckBox.setChecked(False) 335 Private slot handling state changes of the 'discover' checkbox.
236 self.coverageCheckBox.setEnabled(False) 336
237 self.localCheckBox.setChecked(False) 337 @param checked state of the checkbox
238 self.localCheckBox.setEnabled(False) 338 @type bool
239 return 339 """
240 340 self.discoverButton.setEnabled(checked)
241 self.coverageCheckBox.setEnabled(True) 341 self.discoveryList.clear()
242 self.localCheckBox.setEnabled(True) 342
243 343 if not bool(self.discoveryPicker.currentText()):
344 if self.__forProject:
345 project = e5App().getObject("Project")
346 if project.isOpen():
347 self.insertDiscovery(project.getProjectPath())
348 return
349
350 self.insertDiscovery(Preferences.getMultiProject("Workspace"))
351
244 def on_buttonBox_clicked(self, button): 352 def on_buttonBox_clicked(self, button):
245 """ 353 """
246 Private slot called by a button of the button box clicked. 354 Private slot called by a button of the button box clicked.
247 355
248 @param button button that was clicked (QAbstractButton) 356 @param button button that was clicked (QAbstractButton)
249 """ 357 """
250 if button == self.startButton: 358 if button == self.discoverButton:
251 self.on_startButton_clicked() 359 self.__discover()
360 elif button == self.startButton:
361 self.startTests()
252 elif button == self.stopButton: 362 elif button == self.stopButton:
253 self.on_stopButton_clicked() 363 self.__stopTests()
254 elif button == self.startFailedButton: 364 elif button == self.startFailedButton:
255 self.on_startButton_clicked(failedOnly=True) 365 self.startTests(failedOnly=True)
256 366
257 @pyqtSlot() 367 @pyqtSlot()
258 def on_startButton_clicked(self, failedOnly=False): 368 def __discover(self):
259 """ 369 """
260 Private slot to start the test. 370 Private slot to discover unit test but don't run them.
261
262 @keyparam failedOnly flag indicating to run only failed tests (boolean)
263 """ 371 """
264 if self.running: 372 if self.running:
265 return 373 return
266 374
267 prog = self.testsuitePicker.currentText() 375 self.discoveryList.clear()
268 if not prog: 376
269 E5MessageBox.critical( 377 discoveryStart = self.discoveryPicker.currentText()
270 self, 378 self.sbLabel.setText(self.tr("Discovering Tests"))
271 self.tr("Unittest"),
272 self.tr("You must enter a test suite file."))
273 return
274
275 # prepend the selected file to the testsuite combobox
276 self.insertProg(prog)
277 self.sbLabel.setText(self.tr("Preparing Testsuite"))
278 QApplication.processEvents() 379 QApplication.processEvents()
279 380
280 testFunctionName = self.testComboBox.currentText() 381 self.testName = self.tr("Unittest with auto-discovery")
281 if testFunctionName: 382 if self.__dbs:
282 self.insertTestName(testFunctionName) 383 venvName = self.venvComboBox.currentText()
283 else: 384
284 testFunctionName = "suite"
285
286 # build the module name from the filename without extension
287 self.testName = os.path.splitext(os.path.basename(prog))[0]
288
289 if self.dbs and not self.localCheckBox.isChecked():
290 # we are cooperating with the eric6 IDE 385 # we are cooperating with the eric6 IDE
291 project = e5App().getObject("Project") 386 project = e5App().getObject("Project")
292 if project.isOpen() and project.isProjectSource(prog): 387 if self.__forProject:
293 mainScript = project.getMainScript(True) 388 mainScript = os.path.abspath(project.getMainScript(True))
294 clientType = project.getProjectLanguage() 389 clientType = project.getProjectLanguage()
390 if mainScript:
391 workdir = os.path.dirname(mainScript)
392 else:
393 workdir = project.getProjectPath()
394 sysPath = [workdir]
395 if not discoveryStart:
396 discoveryStart = workdir
295 else: 397 else:
296 mainScript = os.path.abspath(prog) 398 if not discoveryStart:
297 flags = Utilities.extractFlagsFromFile(mainScript) 399 E5MessageBox.critical(
298 if mainScript.endswith( 400 self,
299 tuple(Preferences.getPython("PythonExtensions"))) or \ 401 self.tr("Unittest"),
300 ("FileType" in flags and 402 self.tr("You must enter a start directory for"
301 flags["FileType"] in ["Python", "Python2"]): 403 " auto-discovery."))
302 clientType = "Python2" 404 return
303 else: 405
304 clientType = "" 406 workdir = ""
305 if failedOnly and self.__failedTests: 407 clientType = \
306 failed = [t.split(".", 1)[1] for t in self.__failedTests] 408 self.__venvManager.getVirtualenvVariant(venvName)
307 else: 409 if not clientType:
308 failed = [] 410 # assume Python 3
309 self.__failedTests = [] 411 clientType = "Python3"
310 self.dbs.remoteUTPrepare( 412 sysPath = []
311 prog, self.testName, testFunctionName, failed, 413 self.__dbs.remoteUTDiscover(clientType, self.__forProject,
312 self.coverageCheckBox.isChecked(), mainScript, 414 workdir, venvName, sysPath,
313 self.coverageEraseCheckBox.isChecked(), clientType=clientType) 415 discoveryStart)
314 else: 416 else:
315 # we are running as an application or in local mode 417 # we are running as an application
316 sys.path = [os.path.dirname(os.path.abspath(prog))] + \ 418 if not discoveryStart:
317 self.savedSysPath 419 E5MessageBox.critical(
420 self,
421 self.tr("Unittest"),
422 self.tr("You must enter a start directory for"
423 " auto-discovery."))
424 return
425
426 if discoveryStart:
427 sys.path = [os.path.abspath(discoveryStart)] + \
428 self.savedSysPath
318 429
319 # clean up list of imported modules to force a reimport upon 430 # clean up list of imported modules to force a reimport upon
320 # running the test 431 # running the test
321 if self.savedModulelist: 432 if self.savedModulelist:
322 for modname in list(sys.modules.keys()): 433 for modname in list(sys.modules.keys()):
323 if modname not in self.savedModulelist: 434 if modname not in self.savedModulelist:
324 # delete it 435 # delete it
325 del(sys.modules[modname]) 436 del(sys.modules[modname])
326 self.savedModulelist = sys.modules.copy() 437 self.savedModulelist = sys.modules.copy()
327 438
328 # now try to generate the testsuite 439 # now try to discover the testsuite
440 os.chdir(discoveryStart)
329 try: 441 try:
330 module = __import__(self.testName) 442 testLoader = unittest.TestLoader()
331 try: 443 test = testLoader.discover(discoveryStart)
332 if failedOnly and self.__failedTests: 444 if hasattr(testLoader, "errors") and \
333 test = unittest.defaultTestLoader.loadTestsFromNames( 445 bool(testLoader.errors):
334 [t.split(".", 1)[1] for t in self.__failedTests], 446 E5MessageBox.critical(
335 module) 447 self,
336 else: 448 self.tr("Unittest"),
337 test = unittest.defaultTestLoader.loadTestsFromName( 449 self.tr(
338 testFunctionName, module) 450 "<p>Unable to discover tests.</p>"
339 except AttributeError: 451 "<p>{0}</p>"
340 test = unittest.defaultTestLoader.loadTestsFromModule( 452 ).format("<br/>".join(testLoader.errors)
341 module) 453 .replace("\n", "<br/>"))
454 )
455 self.sbLabel.clear()
456 else:
457 testsList = self.__assembleTestCasesList(
458 test, discoveryStart)
459 self.__populateDiscoveryResults(testsList)
460 self.sbLabel.setText(
461 self.tr("Discovered %n Test(s)", "",
462 len(testsList)))
463 self.tabWidget.setCurrentIndex(0)
342 except Exception: 464 except Exception:
343 exc_type, exc_value, exc_tb = sys.exc_info() 465 exc_type, exc_value, exc_tb = sys.exc_info()
344 E5MessageBox.critical( 466 E5MessageBox.critical(
345 self, 467 self,
346 self.tr("Unittest"), 468 self.tr("Unittest"),
347 self.tr( 469 self.tr(
348 "<p>Unable to run test <b>{0}</b>.<br>" 470 "<p>Unable to discover tests.</p>"
349 "{1}<br>{2}</p>") 471 "<p>{0}<br/>{1}</p>")
472 .format(str(exc_type),
473 str(exc_value).replace("\n", "<br/>"))
474 )
475 self.sbLabel.clear()
476
477 sys.path = self.savedSysPath
478
479 def __assembleTestCasesList(self, suite, start):
480 """
481 Private method to assemble a list of test cases included in a test
482 suite.
483
484 @param suite test suite to be inspected
485 @type unittest.TestSuite
486 @param start name of directory discovery was started at
487 @type str
488 @return list of tuples containing the test case ID, a short description
489 and the path of the test file name
490 @rtype list of tuples of (str, str, str)
491 """
492 testCases = []
493 for test in suite:
494 if isinstance(test, unittest.TestSuite):
495 testCases.extend(self.__assembleTestCasesList(test, start))
496 else:
497 testId = test.id()
498 if "ModuleImportFailure" not in testId and \
499 "LoadTestsFailure" not in testId and \
500 "_FailedTest" not in testId:
501 filename = os.path.join(
502 start,
503 test.__module__.replace(".", os.sep) + ".py")
504 testCases.append(
505 (test.id(), test.shortDescription(), filename)
506 )
507 return testCases
508
509 def __findDiscoveryItem(self, modulePath):
510 """
511 Private method to find an item given the module path.
512
513 @param modulePath path of the module in dotted notation
514 @type str
515 @return reference to the item or None
516 @rtype QTreeWidgetItem or None
517 """
518 itm = self.discoveryList.topLevelItem(0)
519 while itm is not None:
520 if itm.data(0, UnittestDialog.TestCaseNameRole) == modulePath:
521 return itm
522 itm = self.discoveryList.itemBelow(itm)
523
524 return None
525
526 def __populateDiscoveryResults(self, tests):
527 """
528 Private method to populate the test discovery results list.
529
530 @param tests list of tuples containing the discovery results
531 @type list of tuples of (str, str, str)
532 """
533 for test, _testDescription, filename in tests:
534 testPath = test.split(".")
535 pitm = None
536 for index in range(1, len(testPath) + 1):
537 modulePath = ".".join(testPath[:index])
538 itm = self.__findDiscoveryItem(modulePath)
539 if itm is not None:
540 pitm = itm
541 else:
542 if pitm is None:
543 itm = QTreeWidgetItem(self.discoveryList,
544 [testPath[index - 1]])
545 else:
546 itm = QTreeWidgetItem(pitm,
547 [testPath[index - 1]])
548 pitm.setExpanded(True)
549 itm.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
550 itm.setCheckState(0, Qt.Unchecked)
551 itm.setData(0, UnittestDialog.TestCaseNameRole, modulePath)
552 if os.path.splitext(os.path.basename(filename))[0] == \
553 itm.text(0):
554 itm.setData(0, UnittestDialog.TestCaseFileRole,
555 filename)
556 elif pitm:
557 fn = pitm.data(0, UnittestDialog.TestCaseFileRole)
558 if fn:
559 itm.setData(0, UnittestDialog.TestCaseFileRole, fn)
560 pitm = itm
561
562 def __selectedTestCases(self, parent=None):
563 """
564 Private method to assemble the list of selected test cases and suites.
565
566 @param parent reference to the parent item
567 @type QTreeWidgetItem
568 @return list of selected test cases
569 @rtype list of str
570 """
571 selectedTests = []
572 if parent is None:
573 # top level
574 for index in range(self.discoveryList.topLevelItemCount()):
575 itm = self.discoveryList.topLevelItem(index)
576 if itm.checkState(0) == Qt.Checked:
577 selectedTests.append(
578 itm.data(0, UnittestDialog.TestCaseNameRole))
579 # ignore children because they are included implicitly
580 elif itm.childCount():
581 # recursively check children
582 selectedTests.extend(self.__selectedTestCases(itm))
583
584 else:
585 # parent item with children
586 for index in range(parent.childCount()):
587 itm = parent.child(index)
588 if itm.checkState(0) == Qt.Checked:
589 selectedTests.append(
590 itm.data(0, UnittestDialog.TestCaseNameRole))
591 # ignore children because they are included implicitly
592 elif itm.childCount():
593 # recursively check children
594 selectedTests.extend(self.__selectedTestCases(itm))
595
596 return selectedTests
597
598 def __UTDiscovered(self, testCases, exc_type, exc_value):
599 """
600 Private slot to handle the utDiscovered signal.
601
602 If the unittest suite was loaded successfully, we ask the
603 client to run the test suite.
604
605 @param testCases list of detected test cases
606 @type str
607 @param exc_type exception type occured during discovery
608 @type str
609 @param exc_value value of exception occured during discovery
610 @type str
611 """
612 if testCases:
613 self.__populateDiscoveryResults(testCases)
614 self.sbLabel.setText(
615 self.tr("Discovered %n Test(s)", "",
616 len(testCases)))
617 self.tabWidget.setCurrentIndex(0)
618 else:
619 E5MessageBox.critical(
620 self,
621 self.tr("Unittest"),
622 self.tr("<p>Unable to discover tests.</p>"
623 "<p>{0}<br/>{1}</p>")
624 .format(exc_type, exc_value.replace("\n", "<br/>"))
625 )
626
627 @pyqtSlot(QTreeWidgetItem, int)
628 def on_discoveryList_itemDoubleClicked(self, item, column):
629 """
630 Private slot handling the user double clicking an item.
631
632 @param item reference to the item
633 @type QTreeWidgetItem
634 @param column column of the double click
635 @type int
636 """
637 if item:
638 filename = item.data(0, UnittestDialog.TestCaseFileRole)
639 if filename:
640 if self.__dbs:
641 # running as part of eric IDE
642 self.unittestFile.emit(filename, 1, False)
643 else:
644 self.__openEditor(filename, 1)
645
646 @pyqtSlot()
647 def startTests(self, failedOnly=False):
648 """
649 Public slot to start the test.
650
651 @keyparam failedOnly flag indicating to run only failed tests (boolean)
652 """
653 if self.running:
654 return
655
656 discover = self.discoverCheckBox.isChecked()
657 if discover:
658 discoveryStart = self.discoveryPicker.currentText()
659 testFileName = ""
660 testName = ""
661 else:
662 discoveryStart = ""
663 testFileName = self.testsuitePicker.currentText()
664 testName = self.testComboBox.currentText()
665 if testName:
666 self.insertTestName(testName)
667 if testFileName and not testName:
668 testName = "suite"
669
670 if not discover and not testFileName and not testName:
671 E5MessageBox.critical(
672 self,
673 self.tr("Unittest"),
674 self.tr("You must select auto-discovery or enter a test suite"
675 " file or a dotted test name."))
676 return
677
678 # prepend the selected file to the testsuite combobox
679 self.insertProg(testFileName)
680 self.sbLabel.setText(self.tr("Preparing Testsuite"))
681 QApplication.processEvents()
682
683 if discover:
684 self.testName = self.tr("Unittest with auto-discovery")
685 else:
686 # build the module name from the filename without extension
687 if testFileName:
688 self.testName = os.path.splitext(
689 os.path.basename(testFileName))[0]
690 elif testName:
691 self.testName = testName
692 else:
693 self.testName = self.tr("<Unnamed Test>")
694
695 if failedOnly:
696 testCases = []
697 else:
698 testCases = self.__selectedTestCases()
699
700 if not testCases and self.discoveryList.topLevelItemCount():
701 ok = E5MessageBox.yesNo(
702 self,
703 self.tr("Unittest"),
704 self.tr("""No test case has been selected. Shall all"""
705 """ test cases be run?"""))
706 if not ok:
707 return
708
709 if self.__dbs:
710 venvName = self.venvComboBox.currentText()
711
712 # we are cooperating with the eric6 IDE
713 project = e5App().getObject("Project")
714 if self.__forProject:
715 mainScript = os.path.abspath(project.getMainScript(True))
716 clientType = project.getProjectLanguage()
717 if mainScript:
718 workdir = os.path.dirname(mainScript)
719 else:
720 workdir = project.getProjectPath()
721 sysPath = [workdir]
722 coverageFile = os.path.splitext(mainScript)[0]
723 if discover and not discoveryStart:
724 discoveryStart = workdir
725 else:
726 if discover:
727 if not discoveryStart:
728 E5MessageBox.critical(
729 self,
730 self.tr("Unittest"),
731 self.tr("You must enter a start directory for"
732 " auto-discovery."))
733 return
734
735 coverageFile = os.path.join(discoveryStart, "unittest")
736 workdir = ""
737 clientType = \
738 self.__venvManager.getVirtualenvVariant(venvName)
739 if not clientType:
740 # assume Python 3
741 clientType = "Python3"
742 elif testFileName:
743 mainScript = os.path.abspath(testFileName)
744 flags = Utilities.extractFlagsFromFile(mainScript)
745 workdir = os.path.dirname(mainScript)
746 if mainScript.endswith(
747 tuple(Preferences.getPython("PythonExtensions"))) or \
748 ("FileType" in flags and
749 flags["FileType"] in ["Python", "Python2"]):
750 clientType = "Python2"
751 else:
752 # if it is not Python2 it must be Python3!
753 clientType = "Python3"
754 coverageFile = os.path.splitext(mainScript)[0]
755 else:
756 coverageFile = os.path.abspath("unittest")
757 workdir = ""
758 clientType = \
759 self.__venvManager.getVirtualenvVariant(venvName)
760 if not clientType:
761 # assume Python 3
762 clientType = "Python3"
763 sysPath = []
764 if failedOnly and self.__failedTests:
765 failed = self.__failedTests[:]
766 if discover:
767 workdir = discoveryStart
768 discover = False
769 else:
770 failed = []
771 self.__failedTests = []
772 self.__dbs.remoteUTPrepare(
773 testFileName, self.testName, testName, failed,
774 self.coverageCheckBox.isChecked(), coverageFile,
775 self.coverageEraseCheckBox.isChecked(), clientType=clientType,
776 forProject=self.__forProject, workdir=workdir,
777 venvName=venvName, syspath=sysPath,
778 discover=discover, discoveryStart=discoveryStart,
779 testCases=testCases, debug=self.debuggerCheckBox.isChecked())
780 else:
781 # we are running as an application
782 if discover and not discoveryStart:
783 E5MessageBox.critical(
784 self,
785 self.tr("Unittest"),
786 self.tr("You must enter a start directory for"
787 " auto-discovery."))
788 return
789
790 if testFileName:
791 sys.path = [os.path.dirname(os.path.abspath(testFileName))] + \
792 self.savedSysPath
793 elif discoveryStart:
794 sys.path = [os.path.abspath(discoveryStart)] + \
795 self.savedSysPath
796
797 # clean up list of imported modules to force a reimport upon
798 # running the test
799 if self.savedModulelist:
800 for modname in list(sys.modules.keys()):
801 if modname not in self.savedModulelist:
802 # delete it
803 del(sys.modules[modname])
804 self.savedModulelist = sys.modules.copy()
805
806 os.chdir(self.savedCwd)
807
808 # now try to generate the testsuite
809 try:
810 testLoader = unittest.TestLoader()
811 if failedOnly and self.__failedTests:
812 failed = self.__failedTests[:]
813 if discover:
814 os.chdir(discoveryStart)
815 discover = False
816 else:
817 failed = []
818 if discover:
819 if testCases:
820 test = testLoader.loadTestsFromNames(testCases)
821 else:
822 test = testLoader.discover(discoveryStart)
823 else:
824 if testFileName:
825 module = __import__(self.testName)
826 else:
827 module = None
828 if failedOnly and self.__failedTests:
829 if module:
830 failed = [t.split(".", 1)[1]
831 for t in self.__failedTests]
832 else:
833 failed = self.__failedTests[:]
834 test = testLoader.loadTestsFromNames(
835 failed, module)
836 else:
837 test = testLoader.loadTestsFromName(
838 testName, module)
839 except Exception:
840 exc_type, exc_value, exc_tb = sys.exc_info()
841 E5MessageBox.critical(
842 self,
843 self.tr("Unittest"),
844 self.tr(
845 "<p>Unable to run test <b>{0}</b>.</p>"
846 "<p>{1}<br/>{2}</p>")
350 .format(self.testName, str(exc_type), 847 .format(self.testName, str(exc_type),
351 str(exc_value))) 848 str(exc_value).replace("\n", "<br/>"))
849 )
352 return 850 return
353 851
354 # now set up the coverage stuff 852 # now set up the coverage stuff
355 if self.coverageCheckBox.isChecked(): 853 if self.coverageCheckBox.isChecked():
356 if self.dbs: 854 if discover:
357 # we are cooperating with the eric6 IDE 855 covname = os.path.join(discoveryStart, "unittest")
358 project = e5App().getObject("Project") 856 elif testFileName:
359 if project.isOpen() and project.isProjectSource(prog): 857 covname = \
360 mainScript = project.getMainScript(True) 858 os.path.splitext(os.path.abspath(testFileName))[0]
361 if not mainScript:
362 mainScript = os.path.abspath(prog)
363 else:
364 mainScript = os.path.abspath(prog)
365 else: 859 else:
366 mainScript = os.path.abspath(prog) 860 covname = "unittest"
367 861
368 from DebugClients.Python.coverage import coverage 862 from DebugClients.Python.coverage import coverage
369 cover = coverage( 863 cover = coverage(data_file="{0}.coverage".format(covname))
370 data_file="{0}.coverage".format(
371 os.path.splitext(mainScript)[0]))
372 if self.coverageEraseCheckBox.isChecked(): 864 if self.coverageEraseCheckBox.isChecked():
373 cover.erase() 865 cover.erase()
374 else: 866 else:
375 cover = None 867 cover = None
376 868
377 self.testResult = QtTestResult(self) 869 self.testResult = QtTestResult(
870 self, self.failfastCheckBox.isChecked())
378 self.totalTests = test.countTestCases() 871 self.totalTests = test.countTestCases()
379 self.__failedTests = [] 872 self.__failedTests = []
380 self.__setRunningMode() 873 self.__setRunningMode()
381 if cover: 874 if cover:
382 cover.start() 875 cover.start()
384 if cover: 877 if cover:
385 cover.stop() 878 cover.stop()
386 cover.save() 879 cover.save()
387 self.__setStoppedMode() 880 self.__setStoppedMode()
388 sys.path = self.savedSysPath 881 sys.path = self.savedSysPath
389 882
390 def __UTPrepared(self, nrTests, exc_type, exc_value): 883 def __UTPrepared(self, nrTests, exc_type, exc_value):
391 """ 884 """
392 Private slot to handle the utPrepared signal. 885 Private slot to handle the utPrepared signal.
393 886
394 If the unittest suite was loaded successfully, we ask the 887 If the unittest suite was loaded successfully, we ask the
401 if nrTests == 0: 894 if nrTests == 0:
402 E5MessageBox.critical( 895 E5MessageBox.critical(
403 self, 896 self,
404 self.tr("Unittest"), 897 self.tr("Unittest"),
405 self.tr( 898 self.tr(
406 "<p>Unable to run test <b>{0}</b>.<br>{1}<br>{2}</p>") 899 "<p>Unable to run test <b>{0}</b>.</p>"
407 .format(self.testName, exc_type, exc_value)) 900 "<p>{1}<br/>{2}</p>")
901 .format(self.testName, exc_type,
902 exc_value.replace("\n", "<br/>"))
903 )
408 return 904 return
409 905
410 self.totalTests = nrTests 906 self.totalTests = nrTests
411 self.__setRunningMode() 907 self.__setRunningMode()
412 self.dbs.remoteUTRun() 908 self.__dbs.remoteUTRun(debug=self.debuggerCheckBox.isChecked(),
413 909 failfast=self.failfastCheckBox.isChecked())
910
414 @pyqtSlot() 911 @pyqtSlot()
415 def on_stopButton_clicked(self): 912 def __stopTests(self):
416 """ 913 """
417 Private slot to stop the test. 914 Private slot to stop the test.
418 """ 915 """
419 if self.dbs and not self.localCheckBox.isChecked(): 916 if self.__dbs:
420 self.dbs.remoteUTStop() 917 self.__dbs.remoteUTStop()
421 elif self.testResult: 918 elif self.testResult:
422 self.testResult.stop() 919 self.testResult.stop()
423 920
424 def on_errorsListWidget_currentTextChanged(self, text): 921 def on_errorsListWidget_currentTextChanged(self, text):
425 """ 922 """
426 Private slot to handle the highlighted signal. 923 Private slot to handle the highlighted signal.
427 924
428 @param text current text (string) 925 @param text current text (string)
435 text, Qt.MatchFlags(Qt.MatchExactly)) 932 text, Qt.MatchFlags(Qt.MatchExactly))
436 if len(foundItems) > 0: 933 if len(foundItems) > 0:
437 itm = foundItems[0] 934 itm = foundItems[0]
438 self.testsListWidget.setCurrentItem(itm) 935 self.testsListWidget.setCurrentItem(itm)
439 self.testsListWidget.scrollToItem(itm) 936 self.testsListWidget.scrollToItem(itm)
440 937
441 def __setRunningMode(self): 938 def __setRunningMode(self):
442 """ 939 """
443 Private method to set the GUI in running mode. 940 Private method to set the GUI in running mode.
444 """ 941 """
445 self.running = True 942 self.running = True
943 self.tabWidget.setCurrentIndex(1)
446 944
447 # reset counters and error infos 945 # reset counters and error infos
448 self.runCount = 0 946 self.runCount = 0
449 self.failCount = 0 947 self.failCount = 0
450 self.errorCount = 0 948 self.errorCount = 0
451 self.skippedCount = 0 949 self.skippedCount = 0
452 self.expectedFailureCount = 0 950 self.expectedFailureCount = 0
453 self.unexpectedSuccessCount = 0 951 self.unexpectedSuccessCount = 0
454 self.remainingCount = self.totalTests 952 self.remainingCount = self.totalTests
455 953
456 # reset the GUI 954 # reset the GUI
457 self.progressCounterRunCount.setText(str(self.runCount)) 955 self.progressCounterRunCount.setText(str(self.runCount))
458 self.progressCounterRemCount.setText(str(self.remainingCount)) 956 self.progressCounterRemCount.setText(str(self.remainingCount))
459 self.progressCounterFailureCount.setText(str(self.failCount)) 957 self.progressCounterFailureCount.setText(str(self.failCount))
460 self.progressCounterErrorCount.setText(str(self.errorCount)) 958 self.progressCounterErrorCount.setText(str(self.errorCount))
461 self.progressCounterSkippedCount.setText(str(self.skippedCount)) 959 self.progressCounterSkippedCount.setText(str(self.skippedCount))
462 self.progressCounterExpectedFailureCount.setText( 960 self.progressCounterExpectedFailureCount.setText(
463 str(self.expectedFailureCount)) 961 str(self.expectedFailureCount))
464 self.progressCounterUnexpectedSuccessCount.setText( 962 self.progressCounterUnexpectedSuccessCount.setText(
465 str(self.unexpectedSuccessCount)) 963 str(self.unexpectedSuccessCount))
964
466 self.errorsListWidget.clear() 965 self.errorsListWidget.clear()
467 self.testsListWidget.clear() 966 self.testsListWidget.clear()
967
468 self.progressProgressBar.setRange(0, self.totalTests) 968 self.progressProgressBar.setRange(0, self.totalTests)
469 self.__setProgressColor("green") 969 self.__setProgressColor("green")
470 self.progressProgressBar.reset() 970 self.progressProgressBar.reset()
971
471 self.stopButton.setEnabled(True) 972 self.stopButton.setEnabled(True)
472 self.startButton.setEnabled(False) 973 self.startButton.setEnabled(False)
974 self.startFailedButton.setEnabled(False)
473 self.stopButton.setDefault(True) 975 self.stopButton.setDefault(True)
976
474 self.sbLabel.setText(self.tr("Running")) 977 self.sbLabel.setText(self.tr("Running"))
475 self.progressLed.on() 978 self.progressLed.on()
476 QApplication.processEvents() 979 QApplication.processEvents()
477 980
478 self.startTime = time.time() 981 self.startTime = time.time()
479 982
480 def __setStoppedMode(self): 983 def __setStoppedMode(self):
481 """ 984 """
482 Private method to set the GUI in stopped mode. 985 Private method to set the GUI in stopped mode.
483 """ 986 """
484 self.stopTime = time.time() 987 self.stopTime = time.time()
485 self.timeTaken = float(self.stopTime - self.startTime) 988 self.timeTaken = float(self.stopTime - self.startTime)
486 self.running = False 989 self.running = False
487 990
991 failedAvailable = bool(self.__failedTests)
488 self.startButton.setEnabled(True) 992 self.startButton.setEnabled(True)
489 self.startFailedButton.setEnabled(bool(self.__failedTests)) 993 self.startFailedButton.setEnabled(failedAvailable)
490 self.stopButton.setEnabled(False) 994 self.stopButton.setEnabled(False)
491 if self.__failedTests: 995 if failedAvailable:
492 self.startFailedButton.setDefault(True) 996 self.startFailedButton.setDefault(True)
493 self.startButton.setDefault(False) 997 self.startButton.setDefault(False)
494 else: 998 else:
495 self.startFailedButton.setDefault(False) 999 self.startFailedButton.setDefault(False)
496 self.startButton.setDefault(True) 1000 self.startButton.setDefault(True)
497 if self.runCount == 1: 1001 self.sbLabel.setText(
498 self.sbLabel.setText( 1002 self.tr("Ran %n test(s) in {0:.3f}s", "", self.runCount)
499 self.tr("Ran {0} test in {1:.3f}s") 1003 .format(self.timeTaken))
500 .format(self.runCount, self.timeTaken))
501 else:
502 self.sbLabel.setText(
503 self.tr("Ran {0} tests in {1:.3f}s")
504 .format(self.runCount, self.timeTaken))
505 self.progressLed.off() 1004 self.progressLed.off()
506 1005
507 self.unittestStopped.emit() 1006 self.unittestStopped.emit()
508 1007
1008 self.raise_()
1009 self.activateWindow()
1010
509 def testFailed(self, test, exc, testId): 1011 def testFailed(self, test, exc, testId):
510 """ 1012 """
511 Public method called if a test fails. 1013 Public method called if a test fails.
512 1014
513 @param test name of the test (string) 1015 @param test name of the test (string)
515 @param testId id of the test (string) 1017 @param testId id of the test (string)
516 """ 1018 """
517 self.failCount += 1 1019 self.failCount += 1
518 self.progressCounterFailureCount.setText(str(self.failCount)) 1020 self.progressCounterFailureCount.setText(str(self.failCount))
519 itm = QListWidgetItem(self.tr("Failure: {0}").format(test)) 1021 itm = QListWidgetItem(self.tr("Failure: {0}").format(test))
520 itm.setData(Qt.UserRole, (test, exc)) 1022 itm.setData(UnittestDialog.ErrorsInfoRole, (test, exc))
521 self.errorsListWidget.insertItem(0, itm) 1023 self.errorsListWidget.insertItem(0, itm)
522 self.__failedTests.append(testId) 1024 self.__failedTests.append(testId)
523 1025
524 def testErrored(self, test, exc, testId): 1026 def testErrored(self, test, exc, testId):
525 """ 1027 """
526 Public method called if a test errors. 1028 Public method called if a test errors.
527 1029
528 @param test name of the test (string) 1030 @param test name of the test (string)
530 @param testId id of the test (string) 1032 @param testId id of the test (string)
531 """ 1033 """
532 self.errorCount += 1 1034 self.errorCount += 1
533 self.progressCounterErrorCount.setText(str(self.errorCount)) 1035 self.progressCounterErrorCount.setText(str(self.errorCount))
534 itm = QListWidgetItem(self.tr("Error: {0}").format(test)) 1036 itm = QListWidgetItem(self.tr("Error: {0}").format(test))
535 itm.setData(Qt.UserRole, (test, exc)) 1037 itm.setData(UnittestDialog.ErrorsInfoRole, (test, exc))
536 self.errorsListWidget.insertItem(0, itm) 1038 self.errorsListWidget.insertItem(0, itm)
537 self.__failedTests.append(testId) 1039 self.__failedTests.append(testId)
538 1040
539 def testSkipped(self, test, reason, testId): 1041 def testSkipped(self, test, reason, testId):
540 """ 1042 """
541 Public method called if a test was skipped. 1043 Public method called if a test was skipped.
542 1044
543 @param test name of the test (string) 1045 @param test name of the test (string)
547 self.skippedCount += 1 1049 self.skippedCount += 1
548 self.progressCounterSkippedCount.setText(str(self.skippedCount)) 1050 self.progressCounterSkippedCount.setText(str(self.skippedCount))
549 itm = QListWidgetItem(self.tr(" Skipped: {0}").format(reason)) 1051 itm = QListWidgetItem(self.tr(" Skipped: {0}").format(reason))
550 itm.setForeground(Qt.blue) 1052 itm.setForeground(Qt.blue)
551 self.testsListWidget.insertItem(1, itm) 1053 self.testsListWidget.insertItem(1, itm)
552 1054
553 def testFailedExpected(self, test, exc, testId): 1055 def testFailedExpected(self, test, exc, testId):
554 """ 1056 """
555 Public method called if a test fails expectedly. 1057 Public method called if a test fails expectedly.
556 1058
557 @param test name of the test (string) 1059 @param test name of the test (string)
562 self.progressCounterExpectedFailureCount.setText( 1064 self.progressCounterExpectedFailureCount.setText(
563 str(self.expectedFailureCount)) 1065 str(self.expectedFailureCount))
564 itm = QListWidgetItem(self.tr(" Expected Failure")) 1066 itm = QListWidgetItem(self.tr(" Expected Failure"))
565 itm.setForeground(Qt.blue) 1067 itm.setForeground(Qt.blue)
566 self.testsListWidget.insertItem(1, itm) 1068 self.testsListWidget.insertItem(1, itm)
567 1069
568 def testSucceededUnexpected(self, test, testId): 1070 def testSucceededUnexpected(self, test, testId):
569 """ 1071 """
570 Public method called if a test succeeds unexpectedly. 1072 Public method called if a test succeeds unexpectedly.
571 1073
572 @param test name of the test (string) 1074 @param test name of the test (string)
576 self.progressCounterUnexpectedSuccessCount.setText( 1078 self.progressCounterUnexpectedSuccessCount.setText(
577 str(self.unexpectedSuccessCount)) 1079 str(self.unexpectedSuccessCount))
578 itm = QListWidgetItem(self.tr(" Unexpected Success")) 1080 itm = QListWidgetItem(self.tr(" Unexpected Success"))
579 itm.setForeground(Qt.red) 1081 itm.setForeground(Qt.red)
580 self.testsListWidget.insertItem(1, itm) 1082 self.testsListWidget.insertItem(1, itm)
581 1083
582 def testStarted(self, test, doc): 1084 def testStarted(self, test, doc):
583 """ 1085 """
584 Public method called if a test is about to be run. 1086 Public method called if a test is about to be run.
585 1087
586 @param test name of the started test (string) 1088 @param test name of the started test (string)
587 @param doc documentation of the started test (string) 1089 @param doc documentation of the started test (string)
588 """ 1090 """
589 if doc: 1091 if doc:
590 self.testsListWidget.insertItem(0, " {0}".format(doc)) 1092 self.testsListWidget.insertItem(0, " {0}".format(doc))
591 self.testsListWidget.insertItem(0, test) 1093 self.testsListWidget.insertItem(0, test)
592 if self.dbs is None or self.localCheckBox.isChecked(): 1094 if self.__dbs is None:
593 QApplication.processEvents() 1095 QApplication.processEvents()
594 1096
595 def testFinished(self): 1097 def testFinished(self):
596 """ 1098 """
597 Public method called if a test has finished. 1099 Public method called if a test has finished.
598 1100
599 <b>Note</b>: It is also called if it has already failed or errored. 1101 <b>Note</b>: It is also called if it has already failed or errored.
608 if self.errorCount: 1110 if self.errorCount:
609 self.__setProgressColor("red") 1111 self.__setProgressColor("red")
610 elif self.failCount: 1112 elif self.failCount:
611 self.__setProgressColor("orange") 1113 self.__setProgressColor("orange")
612 self.progressProgressBar.setValue(self.runCount) 1114 self.progressProgressBar.setValue(self.runCount)
613 1115
614 def on_errorsListWidget_itemDoubleClicked(self, lbitem): 1116 def on_errorsListWidget_itemDoubleClicked(self, lbitem):
615 """ 1117 """
616 Private slot called by doubleclicking an errorlist entry. 1118 Private slot called by doubleclicking an errorlist entry.
617 1119
618 It will popup a dialog showing the stacktrace. 1120 It will popup a dialog showing the stacktrace.
623 @param lbitem the listbox item that was double clicked 1125 @param lbitem the listbox item that was double clicked
624 """ 1126 """
625 self.errListIndex = self.errorsListWidget.row(lbitem) 1127 self.errListIndex = self.errorsListWidget.row(lbitem)
626 text = lbitem.text() 1128 text = lbitem.text()
627 self.on_errorsListWidget_currentTextChanged(text) 1129 self.on_errorsListWidget_currentTextChanged(text)
628 1130
629 # get the error info 1131 # get the error info
630 test, tracebackText = lbitem.data(Qt.UserRole) 1132 test, tracebackText = lbitem.data(UnittestDialog.ErrorsInfoRole)
631 1133
632 # now build the dialog 1134 # now build the dialog
633 from .Ui_UnittestStacktraceDialog import Ui_UnittestStacktraceDialog 1135 from .Ui_UnittestStacktraceDialog import Ui_UnittestStacktraceDialog
634 self.dlg = QDialog(self) 1136 self.dlg = QDialog(self)
635 ui = Ui_UnittestStacktraceDialog() 1137 ui = Ui_UnittestStacktraceDialog()
636 ui.setupUi(self.dlg) 1138 ui.setupUi(self.dlg)
647 ui.traceback.setPlainText(tracebackText) 1149 ui.traceback.setPlainText(tracebackText)
648 1150
649 # and now fire it up 1151 # and now fire it up
650 self.dlg.show() 1152 self.dlg.show()
651 self.dlg.exec_() 1153 self.dlg.exec_()
652 1154
653 def __showSource(self): 1155 def __showSource(self):
654 """ 1156 """
655 Private slot to show the source of a traceback in an eric6 editor. 1157 Private slot to show the source of a traceback in an eric6 editor.
656 """ 1158 """
657 # get the error info 1159 # get the error info
662 tracebackLines[index]) 1164 tracebackLines[index])
663 if fmatch: 1165 if fmatch:
664 break 1166 break
665 if fmatch: 1167 if fmatch:
666 fn, ln = fmatch.group(1, 2) 1168 fn, ln = fmatch.group(1, 2)
667 if self.dbs: 1169 if self.__dbs:
668 # running as part of eric IDE 1170 # running as part of eric IDE
669 self.unittestFile.emit(fn, int(ln), 1) 1171 self.unittestFile.emit(fn, int(ln), True)
670 else: 1172 else:
671 self.__openEditor(fn, int(ln)) 1173 self.__openEditor(fn, int(ln))
672 1174
673 def hasFailedTests(self): 1175 def hasFailedTests(self):
674 """ 1176 """
718 """ 1220 """
719 A TestResult derivative to work with a graphical GUI. 1221 A TestResult derivative to work with a graphical GUI.
720 1222
721 For more details see pyunit.py of the standard Python distribution. 1223 For more details see pyunit.py of the standard Python distribution.
722 """ 1224 """
723 def __init__(self, parent): 1225 def __init__(self, parent, failfast):
724 """ 1226 """
725 Constructor 1227 Constructor
726 1228
727 @param parent The parent widget. 1229 @param parent reference to the parent widget
1230 @type UnittestDialog
1231 @param failfast flag indicating to stop at the first error
1232 @type bool
728 """ 1233 """
729 super(QtTestResult, self).__init__() 1234 super(QtTestResult, self).__init__()
730 self.parent = parent 1235 self.parent = parent
731 1236 self.failfast = failfast
1237
732 def addFailure(self, test, err): 1238 def addFailure(self, test, err):
733 """ 1239 """
734 Public method called if a test failed. 1240 Public method called if a test failed.
735 1241
736 @param test reference to the test object 1242 @param test reference to the test object
737 @param err error traceback 1243 @param err error traceback
738 """ 1244 """
739 super(QtTestResult, self).addFailure(test, err) 1245 super(QtTestResult, self).addFailure(test, err)
740 tracebackLines = self._exc_info_to_string(err, test) 1246 tracebackLines = self._exc_info_to_string(err, test)
741 self.parent.testFailed(str(test), tracebackLines, test.id()) 1247 self.parent.testFailed(str(test), tracebackLines, test.id())
742 1248
743 def addError(self, test, err): 1249 def addError(self, test, err):
744 """ 1250 """
745 Public method called if a test errored. 1251 Public method called if a test errored.
746 1252
747 @param test reference to the test object 1253 @param test reference to the test object
748 @param err error traceback 1254 @param err error traceback
749 """ 1255 """
750 super(QtTestResult, self).addError(test, err) 1256 super(QtTestResult, self).addError(test, err)
751 tracebackLines = self._exc_info_to_string(err, test) 1257 tracebackLines = self._exc_info_to_string(err, test)
752 self.parent.testErrored(str(test), tracebackLines, test.id()) 1258 self.parent.testErrored(str(test), tracebackLines, test.id())
753 1259
754 def addSkip(self, test, reason): 1260 def addSkip(self, test, reason):
755 """ 1261 """
756 Public method called if a test was skipped. 1262 Public method called if a test was skipped.
757 1263
758 @param test reference to the test object 1264 @param test reference to the test object
759 @param reason reason for skipping the test (string) 1265 @param reason reason for skipping the test (string)
760 """ 1266 """
761 super(QtTestResult, self).addSkip(test, reason) 1267 super(QtTestResult, self).addSkip(test, reason)
762 self.parent.testSkipped(str(test), reason, test.id()) 1268 self.parent.testSkipped(str(test), reason, test.id())
763 1269
764 def addExpectedFailure(self, test, err): 1270 def addExpectedFailure(self, test, err):
765 """ 1271 """
766 Public method called if a test failed expected. 1272 Public method called if a test failed expected.
767 1273
768 @param test reference to the test object 1274 @param test reference to the test object
769 @param err error traceback 1275 @param err error traceback
770 """ 1276 """
771 super(QtTestResult, self).addExpectedFailure(test, err) 1277 super(QtTestResult, self).addExpectedFailure(test, err)
772 tracebackLines = self._exc_info_to_string(err, test) 1278 tracebackLines = self._exc_info_to_string(err, test)
773 self.parent.testFailedExpected(str(test), tracebackLines, test.id()) 1279 self.parent.testFailedExpected(str(test), tracebackLines, test.id())
774 1280
775 def addUnexpectedSuccess(self, test): 1281 def addUnexpectedSuccess(self, test):
776 """ 1282 """
777 Public method called if a test succeeded expectedly. 1283 Public method called if a test succeeded expectedly.
778 1284
779 @param test reference to the test object 1285 @param test reference to the test object
780 """ 1286 """
781 super(QtTestResult, self).addUnexpectedSuccess(test) 1287 super(QtTestResult, self).addUnexpectedSuccess(test)
782 self.parent.testSucceededUnexpected(str(test), test.id()) 1288 self.parent.testSucceededUnexpected(str(test), test.id())
783 1289
784 def startTest(self, test): 1290 def startTest(self, test):
785 """ 1291 """
786 Public method called at the start of a test. 1292 Public method called at the start of a test.
787 1293
788 @param test Reference to the test object 1294 @param test Reference to the test object
810 1316
811 @param prog filename of the program to open 1317 @param prog filename of the program to open
812 @param parent reference to the parent widget (QWidget) 1318 @param parent reference to the parent widget (QWidget)
813 """ 1319 """
814 super(UnittestWindow, self).__init__(parent) 1320 super(UnittestWindow, self).__init__(parent)
815 self.cw = UnittestDialog(prog=prog, parent=self) 1321 self.cw = UnittestDialog(prog, parent=self)
816 self.cw.installEventFilter(self) 1322 self.cw.installEventFilter(self)
817 size = self.cw.size() 1323 size = self.cw.size()
818 self.setCentralWidget(self.cw) 1324 self.setCentralWidget(self.cw)
819 self.resize(size) 1325 self.resize(size)
820 1326
821 self.setStyle(Preferences.getUI("Style"), 1327 self.setStyle(Preferences.getUI("Style"),
822 Preferences.getUI("StyleSheet")) 1328 Preferences.getUI("StyleSheet"))
1329
1330 self.cw.buttonBox.accepted.connect(self.close)
1331 self.cw.buttonBox.rejected.connect(self.close)
823 1332
824 def eventFilter(self, obj, event): 1333 def eventFilter(self, obj, event):
825 """ 1334 """
826 Public method to filter events. 1335 Public method to filter events.
827 1336

eric ide

mercurial