PyUnit/UnittestDialog.py

changeset 1499
b4d0457afb15
parent 1491
985c5abc8226
child 1509
c0b5e693b0eb
equal deleted inserted replaced
1497:94f27ede4186 1499:b4d0457afb15
35 class UnittestDialog(QWidget, Ui_UnittestDialog): 35 class UnittestDialog(QWidget, Ui_UnittestDialog):
36 """ 36 """
37 Class implementing the UI to the pyunit package. 37 Class implementing the UI to the pyunit package.
38 38
39 @signal unittestFile(str, int, int) emitted to show the source of a unittest file 39 @signal unittestFile(str, int, int) emitted to show the source of a unittest file
40 @signal unittestStopped() emitted after a unit test was run
40 """ 41 """
41 unittestFile = pyqtSignal(str, int, int) 42 unittestFile = pyqtSignal(str, int, int)
43 unittestStopped = pyqtSignal()
42 44
43 def __init__(self, prog=None, dbs=None, ui=None, fromEric=False, parent=None, 45 def __init__(self, prog=None, dbs=None, ui=None, fromEric=False, parent=None,
44 name=None): 46 name=None):
45 """ 47 """
46 Constructor 48 Constructor
63 self.trUtf8("Start"), QDialogButtonBox.ActionRole) 65 self.trUtf8("Start"), QDialogButtonBox.ActionRole)
64 self.startButton.setToolTip(self.trUtf8("Start the selected testsuite")) 66 self.startButton.setToolTip(self.trUtf8("Start the selected testsuite"))
65 self.startButton.setWhatsThis(self.trUtf8( 67 self.startButton.setWhatsThis(self.trUtf8(
66 """<b>Start Test</b>""" 68 """<b>Start Test</b>"""
67 """<p>This button starts the selected testsuite.</p>""")) 69 """<p>This button starts the selected testsuite.</p>"""))
70 self.startFailedButton = self.buttonBox.addButton(
71 self.trUtf8("Rerun Failed"), QDialogButtonBox.ActionRole)
72 self.startFailedButton.setToolTip(
73 self.trUtf8("Reruns failed tests of the selected testsuite"))
74 self.startFailedButton.setWhatsThis(self.trUtf8(
75 """<b>Rerun Failed</b>"""
76 """<p>This button reruns all failed tests of the selected testsuite.</p>"""))
68 self.stopButton = self.buttonBox.addButton( 77 self.stopButton = self.buttonBox.addButton(
69 self.trUtf8("Stop"), QDialogButtonBox.ActionRole) 78 self.trUtf8("Stop"), QDialogButtonBox.ActionRole)
70 self.stopButton.setToolTip(self.trUtf8("Stop the running unittest")) 79 self.stopButton.setToolTip(self.trUtf8("Stop the running unittest"))
71 self.stopButton.setWhatsThis(self.trUtf8( 80 self.stopButton.setWhatsThis(self.trUtf8(
72 """<b>Stop Test</b>""" 81 """<b>Stop Test</b>"""
73 """<p>This button stops a running unittest.</p>""")) 82 """<p>This button stops a running unittest.</p>"""))
74 self.stopButton.setEnabled(False) 83 self.stopButton.setEnabled(False)
75 self.startButton.setDefault(True) 84 self.startButton.setDefault(True)
85 self.startFailedButton.setEnabled(False)
76 86
77 self.dbs = dbs 87 self.dbs = dbs
78 self.__fromEric = fromEric 88 self.__fromEric = fromEric
79 89
80 self.setWindowFlags( 90 self.setWindowFlags(
101 111
102 self.rxPatterns = [ 112 self.rxPatterns = [
103 self.trUtf8("^Failure: "), 113 self.trUtf8("^Failure: "),
104 self.trUtf8("^Error: "), 114 self.trUtf8("^Error: "),
105 ] 115 ]
116
117 self.__failedTests = []
106 118
107 # now connect the debug server signals if called from the eric5 IDE 119 # now connect the debug server signals if called from the eric5 IDE
108 if self.dbs: 120 if self.dbs:
109 self.dbs.utPrepared.connect(self.__UTPrepared) 121 self.dbs.utPrepared.connect(self.__UTPrepared)
110 self.dbs.utFinished.connect(self.__setStoppedMode) 122 self.dbs.utFinished.connect(self.__setStoppedMode)
198 210
199 @param txt name of the test file (string) 211 @param txt name of the test file (string)
200 """ 212 """
201 if self.dbs: 213 if self.dbs:
202 exts = self.dbs.getExtensions("Python2") 214 exts = self.dbs.getExtensions("Python2")
203 if txt.endswith(exts): 215 flags = Utilities.extractFlagsFromFile(txt)
216 if txt.endswith(exts) or \
217 ("FileType" in flags and
218 flags["FileType"] in ["Python", "Python2"]):
204 self.coverageCheckBox.setChecked(False) 219 self.coverageCheckBox.setChecked(False)
205 self.coverageCheckBox.setEnabled(False) 220 self.coverageCheckBox.setEnabled(False)
206 self.localCheckBox.setChecked(False) 221 self.localCheckBox.setChecked(False)
207 self.localCheckBox.setEnabled(False) 222 self.localCheckBox.setEnabled(False)
208 return 223 return
218 """ 233 """
219 if button == self.startButton: 234 if button == self.startButton:
220 self.on_startButton_clicked() 235 self.on_startButton_clicked()
221 elif button == self.stopButton: 236 elif button == self.stopButton:
222 self.on_stopButton_clicked() 237 self.on_stopButton_clicked()
238 elif button == self.startFailedButton:
239 self.on_startButton_clicked(failedOnly=True)
223 240
224 @pyqtSlot() 241 @pyqtSlot()
225 def on_startButton_clicked(self): 242 def on_startButton_clicked(self, failedOnly=False):
226 """ 243 """
227 Public slot to start the test. 244 Public slot to start the test.
245
246 @keyparam failedOnly flag indicating to run only failed tests (boolean)
228 """ 247 """
229 if self.running: 248 if self.running:
230 return 249 return
231 250
232 prog = self.testsuiteComboBox.currentText() 251 prog = self.testsuiteComboBox.currentText()
264 ("FileType" in flags and 283 ("FileType" in flags and
265 flags["FileType"] in ["Python", "Python2"]): 284 flags["FileType"] in ["Python", "Python2"]):
266 clientType = "Python2" 285 clientType = "Python2"
267 else: 286 else:
268 clientType = "" 287 clientType = ""
288 if failedOnly and self.__failedTests:
289 failed = [t.split(".", 1)[1] for t in self.__failedTests]
290 else:
291 failed = []
292 self.__failedTests = []
269 self.dbs.remoteUTPrepare(prog, self.testName, testFunctionName, 293 self.dbs.remoteUTPrepare(prog, self.testName, testFunctionName,
294 failed,
270 self.coverageCheckBox.isChecked(), mainScript, 295 self.coverageCheckBox.isChecked(), mainScript,
271 self.coverageEraseCheckBox.isChecked(), clientType=clientType) 296 self.coverageEraseCheckBox.isChecked(), clientType=clientType)
272 else: 297 else:
273 # we are running as an application or in local mode 298 # we are running as an application or in local mode
274 sys.path = [os.path.dirname(os.path.abspath(prog))] + self.savedSysPath 299 sys.path = [os.path.dirname(os.path.abspath(prog))] + self.savedSysPath
283 308
284 # now try to generate the testsuite 309 # now try to generate the testsuite
285 try: 310 try:
286 module = __import__(self.testName) 311 module = __import__(self.testName)
287 try: 312 try:
288 test = unittest.defaultTestLoader.loadTestsFromName( 313 if failedOnly and self.__failedTests:
289 testFunctionName, module) 314 test = unittest.defaultTestLoader.loadTestsFromNames(
315 [t.split(".", 1)[1] for t in self.__failedTests],
316 module)
317 else:
318 test = unittest.defaultTestLoader.loadTestsFromName(
319 testFunctionName, module)
290 except AttributeError: 320 except AttributeError:
291 test = unittest.defaultTestLoader.loadTestsFromModule(module) 321 test = unittest.defaultTestLoader.loadTestsFromModule(module)
292 except: 322 except:
293 exc_type, exc_value, exc_tb = sys.exc_info() 323 exc_type, exc_value, exc_tb = sys.exc_info()
294 E5MessageBox.critical(self, 324 E5MessageBox.critical(self,
316 else: 346 else:
317 cover = None 347 cover = None
318 348
319 self.testResult = QtTestResult(self) 349 self.testResult = QtTestResult(self)
320 self.totalTests = test.countTestCases() 350 self.totalTests = test.countTestCases()
351 self.__failedTests = []
321 self.__setRunningMode() 352 self.__setRunningMode()
322 if cover: 353 if cover:
323 cover.start() 354 cover.start()
324 test.run(self.testResult) 355 test.run(self.testResult)
325 if cover: 356 if cover:
418 self.stopTime = time.time() 449 self.stopTime = time.time()
419 self.timeTaken = float(self.stopTime - self.startTime) 450 self.timeTaken = float(self.stopTime - self.startTime)
420 self.running = False 451 self.running = False
421 452
422 self.startButton.setEnabled(True) 453 self.startButton.setEnabled(True)
454 self.startFailedButton.setEnabled(bool(self.__failedTests))
423 self.stopButton.setEnabled(False) 455 self.stopButton.setEnabled(False)
424 self.startButton.setDefault(True) 456 if self.__failedTests:
457 self.startFailedButton.setDefault(True)
458 self.startButton.setDefault(False)
459 else:
460 self.startFailedButton.setDefault(False)
461 self.startButton.setDefault(True)
425 if self.runCount == 1: 462 if self.runCount == 1:
426 self.sbLabel.setText(self.trUtf8("Ran {0} test in {1:.3f}s") 463 self.sbLabel.setText(self.trUtf8("Ran {0} test in {1:.3f}s")
427 .format(self.runCount, self.timeTaken)) 464 .format(self.runCount, self.timeTaken))
428 else: 465 else:
429 self.sbLabel.setText(self.trUtf8("Ran {0} tests in {1:.3f}s") 466 self.sbLabel.setText(self.trUtf8("Ran {0} tests in {1:.3f}s")
430 .format(self.runCount, self.timeTaken)) 467 .format(self.runCount, self.timeTaken))
431 self.progressLed.off() 468 self.progressLed.off()
432 469
433 def testFailed(self, test, exc): 470 self.unittestStopped.emit()
471
472 def testFailed(self, test, exc, id):
434 """ 473 """
435 Public method called if a test fails. 474 Public method called if a test fails.
436 475
437 @param test name of the failed test (string) 476 @param test name of the test (string)
438 @param exc string representation of the exception (string) 477 @param exc string representation of the exception (string)
478 @param id id of the test (string)
439 """ 479 """
440 self.failCount += 1 480 self.failCount += 1
441 self.progressCounterFailureCount.setText(str(self.failCount)) 481 self.progressCounterFailureCount.setText(str(self.failCount))
442 itm = QListWidgetItem(self.trUtf8("Failure: {0}").format(test)) 482 itm = QListWidgetItem(self.trUtf8("Failure: {0}").format(test))
443 itm.setData(Qt.UserRole, (test, exc)) 483 itm.setData(Qt.UserRole, (test, exc))
444 self.errorsListWidget.insertItem(0, itm) 484 self.errorsListWidget.insertItem(0, itm)
445 485 self.__failedTests.append(id)
446 def testErrored(self, test, exc): 486
487 def testErrored(self, test, exc, id):
447 """ 488 """
448 Public method called if a test errors. 489 Public method called if a test errors.
449 490
450 @param test name of the failed test (string) 491 @param test name of the test (string)
451 @param exc string representation of the exception (string) 492 @param exc string representation of the exception (string)
493 @param id id of the test (string)
452 """ 494 """
453 self.errorCount += 1 495 self.errorCount += 1
454 self.progressCounterErrorCount.setText(str(self.errorCount)) 496 self.progressCounterErrorCount.setText(str(self.errorCount))
455 itm = QListWidgetItem(self.trUtf8("Error: {0}").format(test)) 497 itm = QListWidgetItem(self.trUtf8("Error: {0}").format(test))
456 itm.setData(Qt.UserRole, (test, exc)) 498 itm.setData(Qt.UserRole, (test, exc))
457 self.errorsListWidget.insertItem(0, itm) 499 self.errorsListWidget.insertItem(0, itm)
458 500 self.__failedTests.append(id)
459 def testSkipped(self, test, reason): 501
502 def testSkipped(self, test, reason, id):
460 """ 503 """
461 Public method called if a test was skipped. 504 Public method called if a test was skipped.
462 505
463 @param test name of the failed test (string) 506 @param test name of the test (string)
464 @param reason reason for skipping the test (string) 507 @param reason reason for skipping the test (string)
508 @param id id of the test (string)
465 """ 509 """
466 self.skippedCount += 1 510 self.skippedCount += 1
467 self.progressCounterSkippedCount.setText(str(self.skippedCount)) 511 self.progressCounterSkippedCount.setText(str(self.skippedCount))
468 itm = QListWidgetItem(self.trUtf8(" Skipped: {0}").format(reason)) 512 itm = QListWidgetItem(self.trUtf8(" Skipped: {0}").format(reason))
469 itm.setForeground(Qt.blue) 513 itm.setForeground(Qt.blue)
470 self.testsListWidget.insertItem(1, itm) 514 self.testsListWidget.insertItem(1, itm)
471 515
472 def testFailedExpected(self, test, exc): 516 def testFailedExpected(self, test, exc, id):
473 """ 517 """
474 Public method called if a test fails expected. 518 Public method called if a test fails expectedly.
475 519
476 @param test name of the failed test (string) 520 @param test name of the test (string)
477 @param exc string representation of the exception (string) 521 @param exc string representation of the exception (string)
522 @param id id of the test (string)
478 """ 523 """
479 self.expectedFailureCount += 1 524 self.expectedFailureCount += 1
480 self.progressCounterExpectedFailureCount.setText(str(self.expectedFailureCount)) 525 self.progressCounterExpectedFailureCount.setText(str(self.expectedFailureCount))
481 itm = QListWidgetItem(self.trUtf8(" Expected Failure")) 526 itm = QListWidgetItem(self.trUtf8(" Expected Failure"))
482 itm.setForeground(Qt.blue) 527 itm.setForeground(Qt.blue)
483 self.testsListWidget.insertItem(1, itm) 528 self.testsListWidget.insertItem(1, itm)
484 529
485 def testSucceededUnexpected(self, test): 530 def testSucceededUnexpected(self, test, id):
486 """ 531 """
487 Public method called if a test succeeds unexpectedly. 532 Public method called if a test succeeds unexpectedly.
488 533
489 @param test name of the failed test (string) 534 @param test name of the test (string)
535 @param id id of the test (string)
490 """ 536 """
491 self.unexpectedSuccessCount += 1 537 self.unexpectedSuccessCount += 1
492 self.progressCounterUnexpectedSuccessCount.setText( 538 self.progressCounterUnexpectedSuccessCount.setText(
493 str(self.unexpectedSuccessCount)) 539 str(self.unexpectedSuccessCount))
494 itm = QListWidgetItem(self.trUtf8(" Unexpected Success")) 540 itm = QListWidgetItem(self.trUtf8(" Unexpected Success"))
584 if fmatch: 630 if fmatch:
585 break 631 break
586 if fmatch: 632 if fmatch:
587 fn, ln = fmatch.group(1, 2) 633 fn, ln = fmatch.group(1, 2)
588 self.unittestFile.emit(fn, int(ln), 1) 634 self.unittestFile.emit(fn, int(ln), 1)
635
636 def hasFailedTests(self):
637 """
638 Public method to check, if there are failed tests from the last run.
639
640 @return flag indicating the presence of failed tests (boolean)
641 """
642 return bool(self.__failedTests)
589 643
590 644
591 class QtTestResult(unittest.TestResult): 645 class QtTestResult(unittest.TestResult):
592 """ 646 """
593 A TestResult derivative to work with a graphical GUI. 647 A TestResult derivative to work with a graphical GUI.
610 @param test reference to the test object 664 @param test reference to the test object
611 @param err error traceback 665 @param err error traceback
612 """ 666 """
613 super().addFailure(test, err) 667 super().addFailure(test, err)
614 tracebackLines = self._exc_info_to_string(err, test) 668 tracebackLines = self._exc_info_to_string(err, test)
615 self.parent.testFailed(str(test), tracebackLines) 669 self.parent.testFailed(str(test), tracebackLines, test.id())
616 670
617 def addError(self, test, err): 671 def addError(self, test, err):
618 """ 672 """
619 Method called if a test errored. 673 Method called if a test errored.
620 674
621 @param test reference to the test object 675 @param test reference to the test object
622 @param err error traceback 676 @param err error traceback
623 """ 677 """
624 super().addError(test, err) 678 super().addError(test, err)
625 tracebackLines = self._exc_info_to_string(err, test) 679 tracebackLines = self._exc_info_to_string(err, test)
626 self.parent.testErrored(str(test), tracebackLines) 680 self.parent.testErrored(str(test), tracebackLines, test.id())
627 681
628 def addSkip(self, test, reason): 682 def addSkip(self, test, reason):
629 """ 683 """
630 Method called if a test was skipped. 684 Method called if a test was skipped.
631 685
632 @param test reference to the test object 686 @param test reference to the test object
633 @param reason reason for skipping the test (string) 687 @param reason reason for skipping the test (string)
634 """ 688 """
635 super().addSkip(test, reason) 689 super().addSkip(test, reason)
636 self.parent.testSkipped(str(test), reason) 690 self.parent.testSkipped(str(test), reason, test.id())
637 691
638 def addExpectedFailure(self, test, err): 692 def addExpectedFailure(self, test, err):
639 """ 693 """
640 Method called if a test failed expected. 694 Method called if a test failed expected.
641 695
642 @param test reference to the test object 696 @param test reference to the test object
643 @param err error traceback 697 @param err error traceback
644 """ 698 """
645 super().addExpectedFailure(test, err) 699 super().addExpectedFailure(test, err)
646 tracebackLines = self._exc_info_to_string(err, test) 700 tracebackLines = self._exc_info_to_string(err, test)
647 self.parent.testFailedExpected(str(test), tracebackLines) 701 self.parent.testFailedExpected(str(test), tracebackLines, test.id())
648 702
649 def addUnexpectedSuccess(self, test): 703 def addUnexpectedSuccess(self, test):
650 """ 704 """
651 Method called if a test succeeded expectedly. 705 Method called if a test succeeded expectedly.
652 706
653 @param test reference to the test object 707 @param test reference to the test object
654 """ 708 """
655 super().addUnexpectedSuccess(test) 709 super().addUnexpectedSuccess(test)
656 self.parent.testSucceededUnexpected(str(test)) 710 self.parent.testSucceededUnexpected(str(test), test.id())
657 711
658 def startTest(self, test): 712 def startTest(self, test):
659 """ 713 """
660 Method called at the start of a test. 714 Method called at the start of a test.
661 715

eric ide

mercurial