|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2002 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the UI to the pyunit package. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 import unittest |
|
13 import sys |
|
14 import time |
|
15 import re |
|
16 import os |
|
17 |
|
18 from PyQt5.QtCore import pyqtSignal, QEvent, Qt, pyqtSlot |
|
19 from PyQt5.QtGui import QColor |
|
20 from PyQt5.QtWidgets import QWidget, QDialog, QApplication, QDialogButtonBox, \ |
|
21 QListWidgetItem, QComboBox, QTreeWidgetItem |
|
22 |
|
23 from E5Gui.E5Application import e5App |
|
24 from E5Gui import E5MessageBox |
|
25 from E5Gui.E5MainWindow import E5MainWindow |
|
26 from E5Gui.E5PathPicker import E5PathPickerModes |
|
27 |
|
28 from .Ui_UnittestDialog import Ui_UnittestDialog |
|
29 |
|
30 import UI.PixmapCache |
|
31 |
|
32 import Utilities |
|
33 import Preferences |
|
34 |
|
35 |
|
36 class UnittestDialog(QWidget, Ui_UnittestDialog): |
|
37 """ |
|
38 Class implementing the UI to the pyunit package. |
|
39 |
|
40 @signal unittestFile(str, int, bool) emitted to show the source of a |
|
41 unittest file |
|
42 @signal unittestStopped() emitted after a unit test was run |
|
43 """ |
|
44 unittestFile = pyqtSignal(str, int, bool) |
|
45 unittestStopped = pyqtSignal() |
|
46 |
|
47 TestCaseNameRole = Qt.UserRole |
|
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): |
|
53 """ |
|
54 Constructor |
|
55 |
|
56 @param prog filename of the program to open |
|
57 @type str |
|
58 @param dbs reference to the debug server object. It is an indication |
|
59 whether we were called from within the eric6 IDE. |
|
60 @type DebugServer |
|
61 @param ui reference to the UI object |
|
62 @type UserInterface |
|
63 @param parent parent widget of this dialog |
|
64 @type QWidget |
|
65 @param name name of this dialog |
|
66 @type str |
|
67 """ |
|
68 super(UnittestDialog, self).__init__(parent) |
|
69 if name: |
|
70 self.setObjectName(name) |
|
71 self.setupUi(self) |
|
72 |
|
73 self.testsuitePicker.setMode(E5PathPickerModes.OpenFileMode) |
|
74 self.testsuitePicker.setInsertPolicy(QComboBox.InsertAtTop) |
|
75 self.testsuitePicker.setSizeAdjustPolicy( |
|
76 QComboBox.AdjustToMinimumContentsLength) |
|
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>""")) |
|
90 self.startButton = self.buttonBox.addButton( |
|
91 self.tr("Start"), QDialogButtonBox.ActionRole) |
|
92 self.startButton.setToolTip(self.tr( |
|
93 "Start the selected testsuite")) |
|
94 self.startButton.setWhatsThis(self.tr( |
|
95 """<b>Start Test</b>""" |
|
96 """<p>This button starts the selected testsuite.</p>""")) |
|
97 self.startFailedButton = self.buttonBox.addButton( |
|
98 self.tr("Rerun Failed"), QDialogButtonBox.ActionRole) |
|
99 self.startFailedButton.setToolTip( |
|
100 self.tr("Reruns failed tests of the selected testsuite")) |
|
101 self.startFailedButton.setWhatsThis(self.tr( |
|
102 """<b>Rerun Failed</b>""" |
|
103 """<p>This button reruns all failed tests of the selected""" |
|
104 """ testsuite.</p>""")) |
|
105 self.stopButton = self.buttonBox.addButton( |
|
106 self.tr("Stop"), QDialogButtonBox.ActionRole) |
|
107 self.stopButton.setToolTip(self.tr("Stop the running unittest")) |
|
108 self.stopButton.setWhatsThis(self.tr( |
|
109 """<b>Stop Test</b>""" |
|
110 """<p>This button stops a running unittest.</p>""")) |
|
111 self.discoverButton.setEnabled(False) |
|
112 self.stopButton.setEnabled(False) |
|
113 self.startButton.setDefault(True) |
|
114 self.startFailedButton.setEnabled(False) |
|
115 |
|
116 self.__dbs = dbs |
|
117 self.__forProject = False |
|
118 |
|
119 self.setWindowFlags( |
|
120 self.windowFlags() | Qt.WindowFlags( |
|
121 Qt.WindowContextHelpButtonHint)) |
|
122 self.setWindowIcon(UI.PixmapCache.getIcon("eric.png")) |
|
123 self.setWindowTitle(self.tr("Unittest")) |
|
124 if dbs: |
|
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) |
|
139 else: |
|
140 self.__venvManager = None |
|
141 self.debuggerCheckBox.setVisible(False) |
|
142 self.venvComboBox.setVisible(bool(self.__venvManager)) |
|
143 self.venvLabel.setVisible(bool(self.__venvManager)) |
|
144 |
|
145 self.__setProgressColor("green") |
|
146 self.progressLed.setDarkFactor(150) |
|
147 self.progressLed.off() |
|
148 |
|
149 self.discoverHistory = [] |
|
150 self.fileHistory = [] |
|
151 self.testNameHistory = [] |
|
152 self.running = False |
|
153 self.savedModulelist = None |
|
154 self.savedSysPath = sys.path |
|
155 self.savedCwd = os.getcwd() |
|
156 if prog: |
|
157 self.insertProg(prog) |
|
158 |
|
159 self.rxPatterns = [ |
|
160 self.tr("^Failure: "), |
|
161 self.tr("^Error: "), |
|
162 # These are for untranslated/partially translated situations |
|
163 "^Failure: ", |
|
164 "^Error: ", |
|
165 ] |
|
166 |
|
167 self.__failedTests = [] |
|
168 |
|
169 # now connect the debug server signals if called from the eric6 IDE |
|
170 if self.__dbs: |
|
171 self.__dbs.utDiscovered.connect(self.__UTDiscovered) |
|
172 self.__dbs.utPrepared.connect(self.__UTPrepared) |
|
173 self.__dbs.utFinished.connect(self.__setStoppedMode) |
|
174 self.__dbs.utStartTest.connect(self.testStarted) |
|
175 self.__dbs.utStopTest.connect(self.testFinished) |
|
176 self.__dbs.utTestFailed.connect(self.testFailed) |
|
177 self.__dbs.utTestErrored.connect(self.testErrored) |
|
178 self.__dbs.utTestSkipped.connect(self.testSkipped) |
|
179 self.__dbs.utTestFailedExpected.connect(self.testFailedExpected) |
|
180 self.__dbs.utTestSucceededUnexpected.connect( |
|
181 self.testSucceededUnexpected) |
|
182 |
|
183 self.__editors = [] |
|
184 |
|
185 def keyPressEvent(self, evt): |
|
186 """ |
|
187 Protected slot to handle key press events. |
|
188 |
|
189 @param evt key press event to handle (QKeyEvent) |
|
190 """ |
|
191 if evt.key() == Qt.Key_Escape and self.__dbs: |
|
192 self.close() |
|
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 |
|
208 def __setProgressColor(self, color): |
|
209 """ |
|
210 Private methode to set the color of the progress color label. |
|
211 |
|
212 @param color colour to be shown (string) |
|
213 """ |
|
214 self.progressLed.setColor(QColor(color)) |
|
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 |
|
253 def insertProg(self, prog): |
|
254 """ |
|
255 Public slot to insert the filename prog into the testsuitePicker |
|
256 object. |
|
257 |
|
258 @param prog filename to be inserted (string) |
|
259 """ |
|
260 # prepend the selected file to the testsuite picker |
|
261 if prog is None: |
|
262 prog = "" |
|
263 if prog in self.fileHistory: |
|
264 self.fileHistory.remove(prog) |
|
265 self.fileHistory.insert(0, prog) |
|
266 self.testsuitePicker.clear() |
|
267 self.testsuitePicker.addItems(self.fileHistory) |
|
268 |
|
269 def insertTestName(self, testName): |
|
270 """ |
|
271 Public slot to insert a test name into the testComboBox object. |
|
272 |
|
273 @param testName name of the test to be inserted (string) |
|
274 """ |
|
275 # prepend the selected file to the testsuite combobox |
|
276 if testName is None: |
|
277 testName = "" |
|
278 if testName in self.testNameHistory: |
|
279 self.testNameHistory.remove(testName) |
|
280 self.testNameHistory.insert(0, testName) |
|
281 self.testComboBox.clear() |
|
282 self.testComboBox.addItems(self.testNameHistory) |
|
283 |
|
284 @pyqtSlot() |
|
285 def on_testsuitePicker_aboutToShowPathPickerDialog(self): |
|
286 """ |
|
287 Private slot called before the test suite selection dialog is shown. |
|
288 """ |
|
289 if self.__dbs: |
|
290 py2Extensions = \ |
|
291 ' '.join(["*{0}".format(ext) |
|
292 for ext in self.__dbs.getExtensions('Python2')]) |
|
293 py3Extensions = \ |
|
294 ' '.join(["*{0}".format(ext) |
|
295 for ext in self.__dbs.getExtensions('Python3')]) |
|
296 fileFilter = self.tr( |
|
297 "Python3 Files ({1});;Python2 Files ({0});;All Files (*)")\ |
|
298 .format(py2Extensions, py3Extensions) |
|
299 else: |
|
300 fileFilter = self.tr("Python Files (*.py);;All Files (*)") |
|
301 self.testsuitePicker.setFilters(fileFilter) |
|
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 |
|
312 @pyqtSlot(str) |
|
313 def on_testsuitePicker_pathSelected(self, suite): |
|
314 """ |
|
315 Private slot called after a test suite has been selected. |
|
316 |
|
317 @param suite file name of the test suite |
|
318 @type str |
|
319 """ |
|
320 self.insertProg(suite) |
|
321 |
|
322 @pyqtSlot(str) |
|
323 def on_testsuitePicker_editTextChanged(self, path): |
|
324 """ |
|
325 Private slot handling changes of the test suite path. |
|
326 |
|
327 @param path path of the test suite file |
|
328 @type str |
|
329 """ |
|
330 self.startFailedButton.setEnabled(False) |
|
331 |
|
332 @pyqtSlot(bool) |
|
333 def on_discoverCheckBox_toggled(self, checked): |
|
334 """ |
|
335 Private slot handling state changes of the 'discover' checkbox. |
|
336 |
|
337 @param checked state of the checkbox |
|
338 @type bool |
|
339 """ |
|
340 self.discoverButton.setEnabled(checked) |
|
341 self.discoveryList.clear() |
|
342 |
|
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 |
|
352 def on_buttonBox_clicked(self, button): |
|
353 """ |
|
354 Private slot called by a button of the button box clicked. |
|
355 |
|
356 @param button button that was clicked (QAbstractButton) |
|
357 """ |
|
358 if button == self.discoverButton: |
|
359 self.__discover() |
|
360 elif button == self.startButton: |
|
361 self.startTests() |
|
362 elif button == self.stopButton: |
|
363 self.__stopTests() |
|
364 elif button == self.startFailedButton: |
|
365 self.startTests(failedOnly=True) |
|
366 |
|
367 @pyqtSlot() |
|
368 def __discover(self): |
|
369 """ |
|
370 Private slot to discover unit test but don't run them. |
|
371 """ |
|
372 if self.running: |
|
373 return |
|
374 |
|
375 self.discoveryList.clear() |
|
376 |
|
377 discoveryStart = self.discoveryPicker.currentText() |
|
378 self.sbLabel.setText(self.tr("Discovering Tests")) |
|
379 QApplication.processEvents() |
|
380 |
|
381 self.testName = self.tr("Unittest with auto-discovery") |
|
382 if self.__dbs: |
|
383 venvName = self.venvComboBox.currentText() |
|
384 |
|
385 # we are cooperating with the eric6 IDE |
|
386 project = e5App().getObject("Project") |
|
387 if self.__forProject: |
|
388 mainScript = os.path.abspath(project.getMainScript(True)) |
|
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 |
|
397 else: |
|
398 if not discoveryStart: |
|
399 E5MessageBox.critical( |
|
400 self, |
|
401 self.tr("Unittest"), |
|
402 self.tr("You must enter a start directory for" |
|
403 " auto-discovery.")) |
|
404 return |
|
405 |
|
406 workdir = "" |
|
407 clientType = \ |
|
408 self.__venvManager.getVirtualenvVariant(venvName) |
|
409 if not clientType: |
|
410 # assume Python 3 |
|
411 clientType = "Python3" |
|
412 sysPath = [] |
|
413 self.__dbs.remoteUTDiscover(clientType, self.__forProject, |
|
414 workdir, venvName, sysPath, |
|
415 discoveryStart) |
|
416 else: |
|
417 # we are running as an application |
|
418 if not discoveryStart: |
|
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 |
|
429 |
|
430 # clean up list of imported modules to force a reimport upon |
|
431 # running the test |
|
432 if self.savedModulelist: |
|
433 for modname in list(sys.modules.keys()): |
|
434 if modname not in self.savedModulelist: |
|
435 # delete it |
|
436 del(sys.modules[modname]) |
|
437 self.savedModulelist = sys.modules.copy() |
|
438 |
|
439 # now try to discover the testsuite |
|
440 os.chdir(discoveryStart) |
|
441 try: |
|
442 testLoader = unittest.TestLoader() |
|
443 test = testLoader.discover(discoveryStart) |
|
444 if hasattr(testLoader, "errors") and \ |
|
445 bool(testLoader.errors): |
|
446 E5MessageBox.critical( |
|
447 self, |
|
448 self.tr("Unittest"), |
|
449 self.tr( |
|
450 "<p>Unable to discover tests.</p>" |
|
451 "<p>{0}</p>" |
|
452 ).format("<br/>".join(testLoader.errors) |
|
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) |
|
464 except Exception: |
|
465 exc_type, exc_value, exc_tb = sys.exc_info() |
|
466 E5MessageBox.critical( |
|
467 self, |
|
468 self.tr("Unittest"), |
|
469 self.tr( |
|
470 "<p>Unable to discover tests.</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>") |
|
847 .format(self.testName, str(exc_type), |
|
848 str(exc_value).replace("\n", "<br/>")) |
|
849 ) |
|
850 return |
|
851 |
|
852 # now set up the coverage stuff |
|
853 if self.coverageCheckBox.isChecked(): |
|
854 if discover: |
|
855 covname = os.path.join(discoveryStart, "unittest") |
|
856 elif testFileName: |
|
857 covname = \ |
|
858 os.path.splitext(os.path.abspath(testFileName))[0] |
|
859 else: |
|
860 covname = "unittest" |
|
861 |
|
862 from DebugClients.Python.coverage import coverage |
|
863 cover = coverage(data_file="{0}.coverage".format(covname)) |
|
864 if self.coverageEraseCheckBox.isChecked(): |
|
865 cover.erase() |
|
866 else: |
|
867 cover = None |
|
868 |
|
869 self.testResult = QtTestResult( |
|
870 self, self.failfastCheckBox.isChecked()) |
|
871 self.totalTests = test.countTestCases() |
|
872 self.__failedTests = [] |
|
873 self.__setRunningMode() |
|
874 if cover: |
|
875 cover.start() |
|
876 test.run(self.testResult) |
|
877 if cover: |
|
878 cover.stop() |
|
879 cover.save() |
|
880 self.__setStoppedMode() |
|
881 sys.path = self.savedSysPath |
|
882 |
|
883 def __UTPrepared(self, nrTests, exc_type, exc_value): |
|
884 """ |
|
885 Private slot to handle the utPrepared signal. |
|
886 |
|
887 If the unittest suite was loaded successfully, we ask the |
|
888 client to run the test suite. |
|
889 |
|
890 @param nrTests number of tests contained in the test suite (integer) |
|
891 @param exc_type type of exception occured during preparation (string) |
|
892 @param exc_value value of exception occured during preparation (string) |
|
893 """ |
|
894 if nrTests == 0: |
|
895 E5MessageBox.critical( |
|
896 self, |
|
897 self.tr("Unittest"), |
|
898 self.tr( |
|
899 "<p>Unable to run test <b>{0}</b>.</p>" |
|
900 "<p>{1}<br/>{2}</p>") |
|
901 .format(self.testName, exc_type, |
|
902 exc_value.replace("\n", "<br/>")) |
|
903 ) |
|
904 return |
|
905 |
|
906 self.totalTests = nrTests |
|
907 self.__setRunningMode() |
|
908 self.__dbs.remoteUTRun(debug=self.debuggerCheckBox.isChecked(), |
|
909 failfast=self.failfastCheckBox.isChecked()) |
|
910 |
|
911 @pyqtSlot() |
|
912 def __stopTests(self): |
|
913 """ |
|
914 Private slot to stop the test. |
|
915 """ |
|
916 if self.__dbs: |
|
917 self.__dbs.remoteUTStop() |
|
918 elif self.testResult: |
|
919 self.testResult.stop() |
|
920 |
|
921 def on_errorsListWidget_currentTextChanged(self, text): |
|
922 """ |
|
923 Private slot to handle the highlighted signal. |
|
924 |
|
925 @param text current text (string) |
|
926 """ |
|
927 if text: |
|
928 for pattern in self.rxPatterns: |
|
929 text = re.sub(pattern, "", text) |
|
930 |
|
931 foundItems = self.testsListWidget.findItems( |
|
932 text, Qt.MatchFlags(Qt.MatchExactly)) |
|
933 if len(foundItems) > 0: |
|
934 itm = foundItems[0] |
|
935 self.testsListWidget.setCurrentItem(itm) |
|
936 self.testsListWidget.scrollToItem(itm) |
|
937 |
|
938 def __setRunningMode(self): |
|
939 """ |
|
940 Private method to set the GUI in running mode. |
|
941 """ |
|
942 self.running = True |
|
943 self.tabWidget.setCurrentIndex(1) |
|
944 |
|
945 # reset counters and error infos |
|
946 self.runCount = 0 |
|
947 self.failCount = 0 |
|
948 self.errorCount = 0 |
|
949 self.skippedCount = 0 |
|
950 self.expectedFailureCount = 0 |
|
951 self.unexpectedSuccessCount = 0 |
|
952 self.remainingCount = self.totalTests |
|
953 |
|
954 # reset the GUI |
|
955 self.progressCounterRunCount.setText(str(self.runCount)) |
|
956 self.progressCounterRemCount.setText(str(self.remainingCount)) |
|
957 self.progressCounterFailureCount.setText(str(self.failCount)) |
|
958 self.progressCounterErrorCount.setText(str(self.errorCount)) |
|
959 self.progressCounterSkippedCount.setText(str(self.skippedCount)) |
|
960 self.progressCounterExpectedFailureCount.setText( |
|
961 str(self.expectedFailureCount)) |
|
962 self.progressCounterUnexpectedSuccessCount.setText( |
|
963 str(self.unexpectedSuccessCount)) |
|
964 |
|
965 self.errorsListWidget.clear() |
|
966 self.testsListWidget.clear() |
|
967 |
|
968 self.progressProgressBar.setRange(0, self.totalTests) |
|
969 self.__setProgressColor("green") |
|
970 self.progressProgressBar.reset() |
|
971 |
|
972 self.stopButton.setEnabled(True) |
|
973 self.startButton.setEnabled(False) |
|
974 self.startFailedButton.setEnabled(False) |
|
975 self.stopButton.setDefault(True) |
|
976 |
|
977 self.sbLabel.setText(self.tr("Running")) |
|
978 self.progressLed.on() |
|
979 QApplication.processEvents() |
|
980 |
|
981 self.startTime = time.time() |
|
982 |
|
983 def __setStoppedMode(self): |
|
984 """ |
|
985 Private method to set the GUI in stopped mode. |
|
986 """ |
|
987 self.stopTime = time.time() |
|
988 self.timeTaken = float(self.stopTime - self.startTime) |
|
989 self.running = False |
|
990 |
|
991 failedAvailable = bool(self.__failedTests) |
|
992 self.startButton.setEnabled(True) |
|
993 self.startFailedButton.setEnabled(failedAvailable) |
|
994 self.stopButton.setEnabled(False) |
|
995 if failedAvailable: |
|
996 self.startFailedButton.setDefault(True) |
|
997 self.startButton.setDefault(False) |
|
998 else: |
|
999 self.startFailedButton.setDefault(False) |
|
1000 self.startButton.setDefault(True) |
|
1001 self.sbLabel.setText( |
|
1002 self.tr("Ran %n test(s) in {0:.3f}s", "", self.runCount) |
|
1003 .format(self.timeTaken)) |
|
1004 self.progressLed.off() |
|
1005 |
|
1006 self.unittestStopped.emit() |
|
1007 |
|
1008 self.raise_() |
|
1009 self.activateWindow() |
|
1010 |
|
1011 def testFailed(self, test, exc, testId): |
|
1012 """ |
|
1013 Public method called if a test fails. |
|
1014 |
|
1015 @param test name of the test (string) |
|
1016 @param exc string representation of the exception (string) |
|
1017 @param testId id of the test (string) |
|
1018 """ |
|
1019 self.failCount += 1 |
|
1020 self.progressCounterFailureCount.setText(str(self.failCount)) |
|
1021 itm = QListWidgetItem(self.tr("Failure: {0}").format(test)) |
|
1022 itm.setData(UnittestDialog.ErrorsInfoRole, (test, exc)) |
|
1023 self.errorsListWidget.insertItem(0, itm) |
|
1024 self.__failedTests.append(testId) |
|
1025 |
|
1026 def testErrored(self, test, exc, testId): |
|
1027 """ |
|
1028 Public method called if a test errors. |
|
1029 |
|
1030 @param test name of the test (string) |
|
1031 @param exc string representation of the exception (string) |
|
1032 @param testId id of the test (string) |
|
1033 """ |
|
1034 self.errorCount += 1 |
|
1035 self.progressCounterErrorCount.setText(str(self.errorCount)) |
|
1036 itm = QListWidgetItem(self.tr("Error: {0}").format(test)) |
|
1037 itm.setData(UnittestDialog.ErrorsInfoRole, (test, exc)) |
|
1038 self.errorsListWidget.insertItem(0, itm) |
|
1039 self.__failedTests.append(testId) |
|
1040 |
|
1041 def testSkipped(self, test, reason, testId): |
|
1042 """ |
|
1043 Public method called if a test was skipped. |
|
1044 |
|
1045 @param test name of the test (string) |
|
1046 @param reason reason for skipping the test (string) |
|
1047 @param testId id of the test (string) |
|
1048 """ |
|
1049 self.skippedCount += 1 |
|
1050 self.progressCounterSkippedCount.setText(str(self.skippedCount)) |
|
1051 itm = QListWidgetItem(self.tr(" Skipped: {0}").format(reason)) |
|
1052 itm.setForeground(Qt.blue) |
|
1053 self.testsListWidget.insertItem(1, itm) |
|
1054 |
|
1055 def testFailedExpected(self, test, exc, testId): |
|
1056 """ |
|
1057 Public method called if a test fails expectedly. |
|
1058 |
|
1059 @param test name of the test (string) |
|
1060 @param exc string representation of the exception (string) |
|
1061 @param testId id of the test (string) |
|
1062 """ |
|
1063 self.expectedFailureCount += 1 |
|
1064 self.progressCounterExpectedFailureCount.setText( |
|
1065 str(self.expectedFailureCount)) |
|
1066 itm = QListWidgetItem(self.tr(" Expected Failure")) |
|
1067 itm.setForeground(Qt.blue) |
|
1068 self.testsListWidget.insertItem(1, itm) |
|
1069 |
|
1070 def testSucceededUnexpected(self, test, testId): |
|
1071 """ |
|
1072 Public method called if a test succeeds unexpectedly. |
|
1073 |
|
1074 @param test name of the test (string) |
|
1075 @param testId id of the test (string) |
|
1076 """ |
|
1077 self.unexpectedSuccessCount += 1 |
|
1078 self.progressCounterUnexpectedSuccessCount.setText( |
|
1079 str(self.unexpectedSuccessCount)) |
|
1080 itm = QListWidgetItem(self.tr(" Unexpected Success")) |
|
1081 itm.setForeground(Qt.red) |
|
1082 self.testsListWidget.insertItem(1, itm) |
|
1083 |
|
1084 def testStarted(self, test, doc): |
|
1085 """ |
|
1086 Public method called if a test is about to be run. |
|
1087 |
|
1088 @param test name of the started test (string) |
|
1089 @param doc documentation of the started test (string) |
|
1090 """ |
|
1091 if doc: |
|
1092 self.testsListWidget.insertItem(0, " {0}".format(doc)) |
|
1093 self.testsListWidget.insertItem(0, test) |
|
1094 if self.__dbs is None: |
|
1095 QApplication.processEvents() |
|
1096 |
|
1097 def testFinished(self): |
|
1098 """ |
|
1099 Public method called if a test has finished. |
|
1100 |
|
1101 <b>Note</b>: It is also called if it has already failed or errored. |
|
1102 """ |
|
1103 # update the counters |
|
1104 self.remainingCount -= 1 |
|
1105 self.runCount += 1 |
|
1106 self.progressCounterRunCount.setText(str(self.runCount)) |
|
1107 self.progressCounterRemCount.setText(str(self.remainingCount)) |
|
1108 |
|
1109 # update the progressbar |
|
1110 if self.errorCount: |
|
1111 self.__setProgressColor("red") |
|
1112 elif self.failCount: |
|
1113 self.__setProgressColor("orange") |
|
1114 self.progressProgressBar.setValue(self.runCount) |
|
1115 |
|
1116 def on_errorsListWidget_itemDoubleClicked(self, lbitem): |
|
1117 """ |
|
1118 Private slot called by doubleclicking an errorlist entry. |
|
1119 |
|
1120 It will popup a dialog showing the stacktrace. |
|
1121 If called from eric, an additional button is displayed |
|
1122 to show the python source in an eric source viewer (in |
|
1123 erics main window. |
|
1124 |
|
1125 @param lbitem the listbox item that was double clicked |
|
1126 """ |
|
1127 self.errListIndex = self.errorsListWidget.row(lbitem) |
|
1128 text = lbitem.text() |
|
1129 self.on_errorsListWidget_currentTextChanged(text) |
|
1130 |
|
1131 # get the error info |
|
1132 test, tracebackText = lbitem.data(UnittestDialog.ErrorsInfoRole) |
|
1133 |
|
1134 # now build the dialog |
|
1135 from .Ui_UnittestStacktraceDialog import Ui_UnittestStacktraceDialog |
|
1136 self.dlg = QDialog(self) |
|
1137 ui = Ui_UnittestStacktraceDialog() |
|
1138 ui.setupUi(self.dlg) |
|
1139 self.dlg.traceback = ui.traceback |
|
1140 |
|
1141 ui.showButton = ui.buttonBox.addButton( |
|
1142 self.tr("Show Source"), QDialogButtonBox.ActionRole) |
|
1143 ui.showButton.clicked.connect(self.__showSource) |
|
1144 |
|
1145 ui.buttonBox.button(QDialogButtonBox.Close).setDefault(True) |
|
1146 |
|
1147 self.dlg.setWindowTitle(text) |
|
1148 ui.testLabel.setText(test) |
|
1149 ui.traceback.setPlainText(tracebackText) |
|
1150 |
|
1151 # and now fire it up |
|
1152 self.dlg.show() |
|
1153 self.dlg.exec_() |
|
1154 |
|
1155 def __showSource(self): |
|
1156 """ |
|
1157 Private slot to show the source of a traceback in an eric6 editor. |
|
1158 """ |
|
1159 # get the error info |
|
1160 tracebackLines = self.dlg.traceback.toPlainText().splitlines() |
|
1161 # find the last entry matching the pattern |
|
1162 for index in range(len(tracebackLines) - 1, -1, -1): |
|
1163 fmatch = re.search(r'File "(.*?)", line (\d*?),.*', |
|
1164 tracebackLines[index]) |
|
1165 if fmatch: |
|
1166 break |
|
1167 if fmatch: |
|
1168 fn, ln = fmatch.group(1, 2) |
|
1169 if self.__dbs: |
|
1170 # running as part of eric IDE |
|
1171 self.unittestFile.emit(fn, int(ln), True) |
|
1172 else: |
|
1173 self.__openEditor(fn, int(ln)) |
|
1174 |
|
1175 def hasFailedTests(self): |
|
1176 """ |
|
1177 Public method to check, if there are failed tests from the last run. |
|
1178 |
|
1179 @return flag indicating the presence of failed tests (boolean) |
|
1180 """ |
|
1181 return bool(self.__failedTests) |
|
1182 |
|
1183 def __openEditor(self, filename, linenumber): |
|
1184 """ |
|
1185 Private method to open an editor window for the given file. |
|
1186 |
|
1187 Note: This method opens an editor window when the unittest dialog |
|
1188 is called as a standalone application. |
|
1189 |
|
1190 @param filename path of the file to be opened |
|
1191 @type str |
|
1192 @param linenumber line number to place the cursor at |
|
1193 @type int |
|
1194 """ |
|
1195 from QScintilla.MiniEditor import MiniEditor |
|
1196 editor = MiniEditor(filename, "Python3", self) |
|
1197 editor.gotoLine(linenumber) |
|
1198 editor.show() |
|
1199 |
|
1200 self.__editors.append(editor) |
|
1201 |
|
1202 def closeEvent(self, event): |
|
1203 """ |
|
1204 Protected method to handle the close event. |
|
1205 |
|
1206 @param event close event |
|
1207 @type QCloseEvent |
|
1208 """ |
|
1209 event.accept() |
|
1210 |
|
1211 for editor in self.__editors: |
|
1212 try: |
|
1213 editor.close() |
|
1214 except Exception: |
|
1215 # ignore all exceptions |
|
1216 pass |
|
1217 |
|
1218 |
|
1219 class QtTestResult(unittest.TestResult): |
|
1220 """ |
|
1221 A TestResult derivative to work with a graphical GUI. |
|
1222 |
|
1223 For more details see pyunit.py of the standard Python distribution. |
|
1224 """ |
|
1225 def __init__(self, parent, failfast): |
|
1226 """ |
|
1227 Constructor |
|
1228 |
|
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 |
|
1233 """ |
|
1234 super(QtTestResult, self).__init__() |
|
1235 self.parent = parent |
|
1236 self.failfast = failfast |
|
1237 |
|
1238 def addFailure(self, test, err): |
|
1239 """ |
|
1240 Public method called if a test failed. |
|
1241 |
|
1242 @param test reference to the test object |
|
1243 @param err error traceback |
|
1244 """ |
|
1245 super(QtTestResult, self).addFailure(test, err) |
|
1246 tracebackLines = self._exc_info_to_string(err, test) |
|
1247 self.parent.testFailed(str(test), tracebackLines, test.id()) |
|
1248 |
|
1249 def addError(self, test, err): |
|
1250 """ |
|
1251 Public method called if a test errored. |
|
1252 |
|
1253 @param test reference to the test object |
|
1254 @param err error traceback |
|
1255 """ |
|
1256 super(QtTestResult, self).addError(test, err) |
|
1257 tracebackLines = self._exc_info_to_string(err, test) |
|
1258 self.parent.testErrored(str(test), tracebackLines, test.id()) |
|
1259 |
|
1260 def addSkip(self, test, reason): |
|
1261 """ |
|
1262 Public method called if a test was skipped. |
|
1263 |
|
1264 @param test reference to the test object |
|
1265 @param reason reason for skipping the test (string) |
|
1266 """ |
|
1267 super(QtTestResult, self).addSkip(test, reason) |
|
1268 self.parent.testSkipped(str(test), reason, test.id()) |
|
1269 |
|
1270 def addExpectedFailure(self, test, err): |
|
1271 """ |
|
1272 Public method called if a test failed expected. |
|
1273 |
|
1274 @param test reference to the test object |
|
1275 @param err error traceback |
|
1276 """ |
|
1277 super(QtTestResult, self).addExpectedFailure(test, err) |
|
1278 tracebackLines = self._exc_info_to_string(err, test) |
|
1279 self.parent.testFailedExpected(str(test), tracebackLines, test.id()) |
|
1280 |
|
1281 def addUnexpectedSuccess(self, test): |
|
1282 """ |
|
1283 Public method called if a test succeeded expectedly. |
|
1284 |
|
1285 @param test reference to the test object |
|
1286 """ |
|
1287 super(QtTestResult, self).addUnexpectedSuccess(test) |
|
1288 self.parent.testSucceededUnexpected(str(test), test.id()) |
|
1289 |
|
1290 def startTest(self, test): |
|
1291 """ |
|
1292 Public method called at the start of a test. |
|
1293 |
|
1294 @param test Reference to the test object |
|
1295 """ |
|
1296 super(QtTestResult, self).startTest(test) |
|
1297 self.parent.testStarted(str(test), test.shortDescription()) |
|
1298 |
|
1299 def stopTest(self, test): |
|
1300 """ |
|
1301 Public method called at the end of a test. |
|
1302 |
|
1303 @param test Reference to the test object |
|
1304 """ |
|
1305 super(QtTestResult, self).stopTest(test) |
|
1306 self.parent.testFinished() |
|
1307 |
|
1308 |
|
1309 class UnittestWindow(E5MainWindow): |
|
1310 """ |
|
1311 Main window class for the standalone dialog. |
|
1312 """ |
|
1313 def __init__(self, prog=None, parent=None): |
|
1314 """ |
|
1315 Constructor |
|
1316 |
|
1317 @param prog filename of the program to open |
|
1318 @param parent reference to the parent widget (QWidget) |
|
1319 """ |
|
1320 super(UnittestWindow, self).__init__(parent) |
|
1321 self.cw = UnittestDialog(prog, parent=self) |
|
1322 self.cw.installEventFilter(self) |
|
1323 size = self.cw.size() |
|
1324 self.setCentralWidget(self.cw) |
|
1325 self.resize(size) |
|
1326 |
|
1327 self.setStyle(Preferences.getUI("Style"), |
|
1328 Preferences.getUI("StyleSheet")) |
|
1329 |
|
1330 self.cw.buttonBox.accepted.connect(self.close) |
|
1331 self.cw.buttonBox.rejected.connect(self.close) |
|
1332 |
|
1333 def eventFilter(self, obj, event): |
|
1334 """ |
|
1335 Public method to filter events. |
|
1336 |
|
1337 @param obj reference to the object the event is meant for (QObject) |
|
1338 @param event reference to the event object (QEvent) |
|
1339 @return flag indicating, whether the event was handled (boolean) |
|
1340 """ |
|
1341 if event.type() == QEvent.Close: |
|
1342 QApplication.exit() |
|
1343 return True |
|
1344 |
|
1345 return False |