7 Module implementing the UI to the pyunit package. |
7 Module implementing the UI to the pyunit package. |
8 """ |
8 """ |
9 |
9 |
10 import unittest |
10 import unittest |
11 import sys |
11 import sys |
12 import traceback |
|
13 import time |
12 import time |
14 import re |
13 import re |
15 import os |
14 import os |
16 |
15 |
17 from PyQt4.QtCore import pyqtSignal, QEvent, Qt, pyqtSlot |
16 from PyQt4.QtCore import pyqtSignal, QEvent, Qt, pyqtSlot |
18 from PyQt4.QtGui import QWidget, QColor, QDialog, QApplication, QDialogButtonBox, \ |
17 from PyQt4.QtGui import QWidget, QColor, QDialog, QApplication, QDialogButtonBox, \ |
19 QMainWindow |
18 QMainWindow, QListWidgetItem |
20 |
19 |
21 from E5Gui.E5Application import e5App |
20 from E5Gui.E5Application import e5App |
22 from E5Gui.E5Completers import E5FileCompleter |
21 from E5Gui.E5Completers import E5FileCompleter |
23 from E5Gui import E5MessageBox, E5FileDialog |
22 from E5Gui import E5MessageBox, E5FileDialog |
24 |
23 |
93 self.savedModulelist = None |
93 self.savedModulelist = None |
94 self.savedSysPath = sys.path |
94 self.savedSysPath = sys.path |
95 if prog: |
95 if prog: |
96 self.insertProg(prog) |
96 self.insertProg(prog) |
97 |
97 |
98 self.rx1 = self.trUtf8("^Failure: ") |
98 self.rxPatterns = [ |
99 self.rx2 = self.trUtf8("^Error: ") |
99 self.trUtf8("^Failure: "), |
|
100 self.trUtf8("^Error: "), |
|
101 ] |
100 |
102 |
101 # now connect the debug server signals if called from the eric5 IDE |
103 # now connect the debug server signals if called from the eric5 IDE |
102 if self.dbs: |
104 if self.dbs: |
103 self.dbs.utPrepared.connect(self.__UTPrepared) |
105 self.dbs.utPrepared.connect(self.__UTPrepared) |
104 self.dbs.utFinished.connect(self.__setStoppedMode) |
106 self.dbs.utFinished.connect(self.__setStoppedMode) |
105 self.dbs.utStartTest.connect(self.testStarted) |
107 self.dbs.utStartTest.connect(self.testStarted) |
106 self.dbs.utStopTest.connect(self.testFinished) |
108 self.dbs.utStopTest.connect(self.testFinished) |
107 self.dbs.utTestFailed.connect(self.testFailed) |
109 self.dbs.utTestFailed.connect(self.testFailed) |
108 self.dbs.utTestErrored.connect(self.testErrored) |
110 self.dbs.utTestErrored.connect(self.testErrored) |
|
111 self.dbs.utTestSkipped.connect(self.testSkipped) |
|
112 self.dbs.utTestFailedExpected.connect(self.testFailedExpected) |
|
113 self.dbs.utTestSucceededUnexpected.connect(self.testSucceededUnexpected) |
109 |
114 |
110 def __setProgressColor(self, color): |
115 def __setProgressColor(self, color): |
111 """ |
116 """ |
112 Private methode to set the color of the progress color label. |
117 Private methode to set the color of the progress color label. |
113 |
118 |
235 if self.dbs and not self.localCheckBox.isChecked(): |
240 if self.dbs and not self.localCheckBox.isChecked(): |
236 # we are cooperating with the eric5 IDE |
241 # we are cooperating with the eric5 IDE |
237 project = e5App().getObject("Project") |
242 project = e5App().getObject("Project") |
238 if project.isOpen() and project.isProjectSource(prog): |
243 if project.isOpen() and project.isProjectSource(prog): |
239 mainScript = project.getMainScript(True) |
244 mainScript = project.getMainScript(True) |
|
245 clientType = self.project.getProjectLanguage() |
240 else: |
246 else: |
241 mainScript = os.path.abspath(prog) |
247 mainScript = os.path.abspath(prog) |
|
248 flags = Utilities.extractFlagsFromFile(mainScript) |
|
249 if mainScript.endswith( |
|
250 tuple(Preferences.getPython("PythonExtensions"))) or \ |
|
251 ("FileType" in flags and |
|
252 flags["FileType"] in ["Python", "Python2"]): |
|
253 clientType = "Python2" |
|
254 else: |
|
255 clientType = "" |
242 self.dbs.remoteUTPrepare(prog, self.testName, testFunctionName, |
256 self.dbs.remoteUTPrepare(prog, self.testName, testFunctionName, |
243 self.coverageCheckBox.isChecked(), mainScript, |
257 self.coverageCheckBox.isChecked(), mainScript, |
244 self.coverageEraseCheckBox.isChecked()) |
258 self.coverageEraseCheckBox.isChecked(), clientType=clientType) |
245 else: |
259 else: |
246 # we are running as an application or in local mode |
260 # we are running as an application or in local mode |
247 sys.path = [os.path.dirname(os.path.abspath(prog))] + self.savedSysPath |
261 sys.path = [os.path.dirname(os.path.abspath(prog))] + self.savedSysPath |
248 |
262 |
249 # clean up list of imported modules to force a reimport upon running the test |
263 # clean up list of imported modules to force a reimport upon running the test |
338 Private slot to handle the highlighted signal. |
352 Private slot to handle the highlighted signal. |
339 |
353 |
340 @param txt current text (string) |
354 @param txt current text (string) |
341 """ |
355 """ |
342 if text: |
356 if text: |
343 text = re.sub(self.rx1, "", text) |
357 for pattern in self.rxPatterns: |
344 text = re.sub(self.rx2, "", text) |
358 text = re.sub(pattern, "", text) |
345 itm = self.testsListWidget.findItems(text, Qt.MatchFlags(Qt.MatchExactly))[0] |
359 itm = self.testsListWidget.findItems(text, Qt.MatchFlags(Qt.MatchExactly))[0] |
346 self.testsListWidget.setCurrentItem(itm) |
360 self.testsListWidget.setCurrentItem(itm) |
347 self.testsListWidget.scrollToItem(itm) |
361 self.testsListWidget.scrollToItem(itm) |
348 |
362 |
349 def __setRunningMode(self): |
363 def __setRunningMode(self): |
354 |
368 |
355 # reset counters and error infos |
369 # reset counters and error infos |
356 self.runCount = 0 |
370 self.runCount = 0 |
357 self.failCount = 0 |
371 self.failCount = 0 |
358 self.errorCount = 0 |
372 self.errorCount = 0 |
|
373 self.skippedCount = 0 |
|
374 self.expectedFailureCount = 0 |
|
375 self.unexpectedSuccessCount = 0 |
359 self.remainingCount = self.totalTests |
376 self.remainingCount = self.totalTests |
360 self.errorInfo = [] |
|
361 |
377 |
362 # reset the GUI |
378 # reset the GUI |
363 self.progressCounterRunCount.setText(str(self.runCount)) |
379 self.progressCounterRunCount.setText(str(self.runCount)) |
|
380 self.progressCounterRemCount.setText(str(self.remainingCount)) |
364 self.progressCounterFailureCount.setText(str(self.failCount)) |
381 self.progressCounterFailureCount.setText(str(self.failCount)) |
365 self.progressCounterErrorCount.setText(str(self.errorCount)) |
382 self.progressCounterErrorCount.setText(str(self.errorCount)) |
366 self.progressCounterRemCount.setText(str(self.remainingCount)) |
383 self.progressCounterSkippedCount.setText(str(self.skippedCount)) |
|
384 self.progressCounterExpectedFailureCount.setText(str(self.expectedFailureCount)) |
|
385 self.progressCounterUnexpectedSuccessCount.setText( |
|
386 str(self.unexpectedSuccessCount)) |
367 self.errorsListWidget.clear() |
387 self.errorsListWidget.clear() |
368 self.testsListWidget.clear() |
388 self.testsListWidget.clear() |
369 self.progressProgressBar.setRange(0, self.totalTests) |
389 self.progressProgressBar.setRange(0, self.totalTests) |
370 self.__setProgressColor("green") |
390 self.__setProgressColor("green") |
371 self.progressProgressBar.reset() |
391 self.progressProgressBar.reset() |
400 def testFailed(self, test, exc): |
420 def testFailed(self, test, exc): |
401 """ |
421 """ |
402 Public method called if a test fails. |
422 Public method called if a test fails. |
403 |
423 |
404 @param test name of the failed test (string) |
424 @param test name of the failed test (string) |
405 @param exc string representation of the exception (list of strings) |
425 @param exc string representation of the exception (string) |
406 """ |
426 """ |
407 self.failCount += 1 |
427 self.failCount += 1 |
408 self.progressCounterFailureCount.setText(str(self.failCount)) |
428 self.progressCounterFailureCount.setText(str(self.failCount)) |
409 self.errorsListWidget.insertItem(0, self.trUtf8("Failure: {0}").format(test)) |
429 itm = QListWidgetItem(self.trUtf8("Failure: {0}").format(test)) |
410 self.errorInfo.insert(0, (test, exc)) |
430 itm.setData(Qt.UserRole, (test, exc)) |
|
431 self.errorsListWidget.insertItem(0, itm) |
411 |
432 |
412 def testErrored(self, test, exc): |
433 def testErrored(self, test, exc): |
413 """ |
434 """ |
414 Public method called if a test errors. |
435 Public method called if a test errors. |
415 |
436 |
416 @param test name of the failed test (string) |
437 @param test name of the failed test (string) |
417 @param exc string representation of the exception (list of strings) |
438 @param exc string representation of the exception (string) |
418 """ |
439 """ |
419 self.errorCount += 1 |
440 self.errorCount += 1 |
420 self.progressCounterErrorCount.setText(str(self.errorCount)) |
441 self.progressCounterErrorCount.setText(str(self.errorCount)) |
421 self.errorsListWidget.insertItem(0, self.trUtf8("Error: {0}").format(test)) |
442 itm = QListWidgetItem(self.trUtf8("Error: {0}").format(test)) |
422 self.errorInfo.insert(0, (test, exc)) |
443 itm.setData(Qt.UserRole, (test, exc)) |
|
444 self.errorsListWidget.insertItem(0, itm) |
|
445 |
|
446 def testSkipped(self, test, reason): |
|
447 """ |
|
448 Public method called if a test was skipped. |
|
449 |
|
450 @param test name of the failed test (string) |
|
451 @param reason reason for skipping the test (string) |
|
452 """ |
|
453 self.skippedCount += 1 |
|
454 self.progressCounterSkippedCount.setText(str(self.skippedCount)) |
|
455 itm = QListWidgetItem(self.trUtf8(" Skipped: {0}").format(reason)) |
|
456 itm.setForeground(Qt.blue) |
|
457 self.testsListWidget.insertItem(1, itm) |
|
458 |
|
459 def testFailedExpected(self, test, exc): |
|
460 """ |
|
461 Public method called if a test fails expected. |
|
462 |
|
463 @param test name of the failed test (string) |
|
464 @param exc string representation of the exception (string) |
|
465 """ |
|
466 self.expectedFailureCount += 1 |
|
467 self.progressCounterExpectedFailureCount.setText(str(self.expectedFailureCount)) |
|
468 itm = QListWidgetItem(self.trUtf8(" Expected Failure")) |
|
469 itm.setForeground(Qt.blue) |
|
470 self.testsListWidget.insertItem(1, itm) |
|
471 |
|
472 def testSucceededUnexpected(self, test): |
|
473 """ |
|
474 Public method called if a test succeeds unexpectedly. |
|
475 |
|
476 @param test name of the failed test (string) |
|
477 """ |
|
478 self.unexpectedSuccessCount += 1 |
|
479 self.progressCounterUnexpectedSuccessCount.setText( |
|
480 str(self.unexpectedSuccessCount)) |
|
481 itm = QListWidgetItem(self.trUtf8(" Unexpected Success")) |
|
482 itm.setForeground(Qt.red) |
|
483 self.testsListWidget.insertItem(1, itm) |
423 |
484 |
424 def testStarted(self, test, doc): |
485 def testStarted(self, test, doc): |
425 """ |
486 """ |
426 Public method called if a test is about to be run. |
487 Public method called if a test is about to be run. |
427 |
488 |
464 |
525 |
465 @param lbitem the listbox item that was double clicked |
526 @param lbitem the listbox item that was double clicked |
466 """ |
527 """ |
467 self.errListIndex = self.errorsListWidget.row(lbitem) |
528 self.errListIndex = self.errorsListWidget.row(lbitem) |
468 text = lbitem.text() |
529 text = lbitem.text() |
|
530 self.on_errorsListWidget_currentTextChanged(text) |
469 |
531 |
470 # get the error info |
532 # get the error info |
471 test, tracebackLines = self.errorInfo[self.errListIndex] |
533 test, tracebackText = lbitem.data(Qt.UserRole) |
472 tracebackText = "".join(tracebackLines) |
|
473 |
534 |
474 # now build the dialog |
535 # now build the dialog |
475 self.dlg = QDialog() |
536 self.dlg = QDialog() |
476 ui = Ui_UnittestStacktraceDialog() |
537 ui = Ui_UnittestStacktraceDialog() |
477 ui.setupUi(self.dlg) |
538 ui.setupUi(self.dlg) |
|
539 self.dlg.traceback = ui.traceback |
478 |
540 |
479 ui.showButton = ui.buttonBox.addButton( |
541 ui.showButton = ui.buttonBox.addButton( |
480 self.trUtf8("Show Source"), QDialogButtonBox.ActionRole) |
542 self.trUtf8("Show Source"), QDialogButtonBox.ActionRole) |
481 ui.buttonBox.button(QDialogButtonBox.Close).setDefault(True) |
543 ui.buttonBox.button(QDialogButtonBox.Close).setDefault(True) |
482 |
544 |
523 """ |
585 """ |
524 Constructor |
586 Constructor |
525 |
587 |
526 @param parent The parent widget. |
588 @param parent The parent widget. |
527 """ |
589 """ |
528 unittest.TestResult.__init__(self) |
590 super().__init__() |
529 self.parent = parent |
591 self.parent = parent |
530 |
592 |
531 def addFailure(self, test, err): |
593 def addFailure(self, test, err): |
532 """ |
594 """ |
533 Method called if a test failed. |
595 Method called if a test failed. |
534 |
596 |
|
597 @param test reference to the test object |
|
598 @param err error traceback |
|
599 """ |
|
600 super().addFailure(test, err) |
|
601 tracebackLines = self._exc_info_to_string(err, test) |
|
602 self.parent.testFailed(str(test), tracebackLines) |
|
603 |
|
604 def addError(self, test, err): |
|
605 """ |
|
606 Method called if a test errored. |
|
607 |
|
608 @param test reference to the test object |
|
609 @param err error traceback |
|
610 """ |
|
611 super().addError(test, err) |
|
612 tracebackLines = self._exc_info_to_string(err, test) |
|
613 self.parent.testErrored(str(test), tracebackLines) |
|
614 |
|
615 def addSkip(self, test, reason): |
|
616 """ |
|
617 Method called if a test was skipped. |
|
618 |
|
619 @param test reference to the test object |
|
620 @param reason reason for skipping the test (string) |
|
621 """ |
|
622 super().addSkip(test, reason) |
|
623 self.parent.testSkipped(str(test), reason) |
|
624 |
|
625 def addExpectedFailure(self, test, err): |
|
626 """ |
|
627 Method called if a test failed expected. |
|
628 |
|
629 @param test reference to the test object |
|
630 @param err error traceback |
|
631 """ |
|
632 super().addExpectedFailure(test, err) |
|
633 tracebackLines = self._exc_info_to_string(err, test) |
|
634 self.parent.testFailedExpected(str(test), tracebackLines) |
|
635 |
|
636 def addUnexpectedSuccess(self, test): |
|
637 """ |
|
638 Method called if a test succeeded expectedly. |
|
639 |
|
640 @param test reference to the test object |
|
641 """ |
|
642 super().addUnexpectedSuccess(test) |
|
643 self.parent.testSucceededUnexpected(str(test)) |
|
644 |
|
645 def startTest(self, test): |
|
646 """ |
|
647 Method called at the start of a test. |
|
648 |
535 @param test Reference to the test object |
649 @param test Reference to the test object |
536 @param err The error traceback |
650 """ |
537 """ |
651 super().startTest(test) |
538 unittest.TestResult.addFailure(self, test, err) |
652 self.parent.testStarted(str(test), test.shortDescription()) |
539 tracebackLines = traceback.format_exception(*err + (10,)) |
653 |
540 self.parent.testFailed(str(test), tracebackLines) |
654 def stopTest(self, test): |
541 |
655 """ |
542 def addError(self, test, err): |
656 Method called at the end of a test. |
543 """ |
|
544 Method called if a test errored. |
|
545 |
657 |
546 @param test Reference to the test object |
658 @param test Reference to the test object |
547 @param err The error traceback |
659 """ |
548 """ |
660 super().stopTest(test) |
549 unittest.TestResult.addError(self, test, err) |
|
550 tracebackLines = traceback.format_exception(*err + (10,)) |
|
551 self.parent.testErrored(str(test), tracebackLines) |
|
552 |
|
553 def startTest(self, test): |
|
554 """ |
|
555 Method called at the start of a test. |
|
556 |
|
557 @param test Reference to the test object |
|
558 """ |
|
559 unittest.TestResult.startTest(self, test) |
|
560 self.parent.testStarted(str(test), test.shortDescription()) |
|
561 |
|
562 def stopTest(self, test): |
|
563 """ |
|
564 Method called at the end of a test. |
|
565 |
|
566 @param test Reference to the test object |
|
567 """ |
|
568 unittest.TestResult.stopTest(self, test) |
|
569 self.parent.testFinished() |
661 self.parent.testFinished() |
570 |
662 |
571 |
663 |
572 class UnittestWindow(QMainWindow): |
664 class UnittestWindow(QMainWindow): |
573 """ |
665 """ |