eric7/Unittest/UnittestWidget.py

branch
unittest
changeset 9062
7f27bf3b50c3
parent 9059
e7fd342f8bfc
child 9063
f1d7dd7ae471
equal deleted inserted replaced
9061:22dab1be7953 9062:7f27bf3b50c3
6 """ 6 """
7 Module implementing a widget to orchestrate unit test execution. 7 Module implementing a widget to orchestrate unit test execution.
8 """ 8 """
9 9
10 import enum 10 import enum
11 import locale
11 import os 12 import os
12 13
13 from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QCoreApplication 14 from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QCoreApplication
14 from PyQt6.QtWidgets import ( 15 from PyQt6.QtWidgets import (
15 QAbstractButton, QComboBox, QDialogButtonBox, QWidget 16 QAbstractButton, QComboBox, QDialogButtonBox, QWidget
22 23
23 from .Ui_UnittestWidget import Ui_UnittestWidget 24 from .Ui_UnittestWidget import Ui_UnittestWidget
24 25
25 from .UTTestResultsTree import TestResultsModel, TestResultsTreeView 26 from .UTTestResultsTree import TestResultsModel, TestResultsTreeView
26 from .Interfaces import Frameworks 27 from .Interfaces import Frameworks
27 from .Interfaces.UTExecutorBase import UTTestConfig, UTTestResult 28 from .Interfaces.UTExecutorBase import (
29 UTTestConfig, UTTestResult, ResultCategory
30 )
28 from .Interfaces.UTFrameworkRegistry import UTFrameworkRegistry 31 from .Interfaces.UTFrameworkRegistry import UTFrameworkRegistry
29 32
30 import Preferences 33 import Preferences
31 import UI.PixmapCache 34 import UI.PixmapCache
32 35
43 """ 46 """
44 IDLE = 0 # idle, no test were run yet 47 IDLE = 0 # idle, no test were run yet
45 RUNNING = 1 # test run being performed 48 RUNNING = 1 # test run being performed
46 STOPPED = 2 # test run finished 49 STOPPED = 2 # test run finished
47 50
51
52 # TODO: add a "Show Coverage" function using PyCoverageDialog
48 53
49 class UnittestWidget(QWidget, Ui_UnittestWidget): 54 class UnittestWidget(QWidget, Ui_UnittestWidget):
50 """ 55 """
51 Class implementing a widget to orchestrate unit test execution. 56 Class implementing a widget to orchestrate unit test execution.
52 """ 57 """
173 self.__insertDiscovery(project.getProjectPath()) 178 self.__insertDiscovery(project.getProjectPath())
174 else: 179 else:
175 self.__insertDiscovery("") 180 self.__insertDiscovery("")
176 else: 181 else:
177 self.__insertDiscovery("") 182 self.__insertDiscovery("")
178 self.__insertProg(testfile) 183 self.__insertTestFile(testfile)
179 self.__insertTestName("") 184 self.__insertTestName("")
180 185
181 self.clearHistoriesButton.clicked.connect(self.clearRecent) 186 self.clearHistoriesButton.clicked.connect(self.clearRecent)
182 187
183 self.tabWidget.setCurrentIndex(0) 188 self.tabWidget.setCurrentIndex(0)
251 history.insert(0, item) 256 history.insert(0, item)
252 widget.clear() 257 widget.clear()
253 widget.addItems(history) 258 widget.addItems(history)
254 259
255 if current: 260 if current:
256 widget.setText(current) 261 widget.setEditText(current)
257 262
258 @pyqtSlot(str) 263 @pyqtSlot(str)
259 def __insertDiscovery(self, start): 264 def __insertDiscovery(self, start):
260 """ 265 """
261 Private slot to insert the discovery start directory into the 266 Private slot to insert the discovery start directory into the
266 """ 271 """
267 self.__insertHistory(self.discoveryPicker, self.__discoverHistory, 272 self.__insertHistory(self.discoveryPicker, self.__discoverHistory,
268 start) 273 start)
269 274
270 @pyqtSlot(str) 275 @pyqtSlot(str)
271 def __insertProg(self, prog): 276 def __insertTestFile(self, prog):
272 """ 277 """
273 Private slot to insert a test file name into the testsuitePicker 278 Private slot to insert a test file name into the testsuitePicker
274 object. 279 object.
275 280
276 @param prog test file name to be inserted 281 @param prog test file name to be inserted
390 else: 395 else:
391 self.__startButton.setEnabled(False) 396 self.__startButton.setEnabled(False)
392 self.__startButton.setDefault(False) 397 self.__startButton.setDefault(False)
393 398
394 # Start Failed button 399 # Start Failed button
395 # TODO: not implemented yet 400 # TODO: not implemented yet (Start Failed button)
396 401
397 # Stop button 402 # Stop button
398 self.__stopButton.setEnabled( 403 self.__stopButton.setEnabled(
399 self.__mode == UnittestWidgetModes.RUNNING) 404 self.__mode == UnittestWidgetModes.RUNNING)
400 self.__stopButton.setDefault( 405 self.__stopButton.setDefault(
401 self.__mode == UnittestWidgetModes.RUNNING) 406 self.__mode == UnittestWidgetModes.RUNNING)
407
408 # Close button
409 self.buttonBox.button(
410 QDialogButtonBox.StandardButton.Close
411 ).setEnabled(self.__mode in (
412 UnittestWidgetModes.IDLE, UnittestWidgetModes.STOPPED
413 ))
414
415 def __updateProgress(self):
416 """
417 Private method update the progress indicators.
418 """
419 self.progressCounterRunCount.setText(
420 str(self.__runCount))
421 self.progressCounterRemCount.setText(
422 str(self.__totalCount - self.__runCount))
423 self.progressProgressBar.setMaximum(self.__totalCount)
424 self.progressProgressBar.setValue(self.__runCount)
402 425
403 def __setIdleMode(self): 426 def __setIdleMode(self):
404 """ 427 """
405 Private method to switch the widget to idle mode. 428 Private method to switch the widget to idle mode.
406 """ 429 """
407 self.__mode = UnittestWidgetModes.IDLE 430 self.__mode = UnittestWidgetModes.IDLE
408 self.__updateButtonBoxButtons() 431 self.__updateButtonBoxButtons()
432 self.tabWidget.setCurrentIndex(0)
409 433
410 def __setRunningMode(self): 434 def __setRunningMode(self):
411 """ 435 """
412 Private method to switch the widget to running mode. 436 Private method to switch the widget to running mode.
413 """ 437 """
414 # TODO: not implemented yet 438 self.__mode = UnittestWidgetModes.RUNNING
415 pass 439
440 self.__totalCount = 0
441 self.__runCount = 0
442
443 self.__coverageFile = ""
444 # TODO: implement the handling of the 'Show Coverage' button
445
446 self.sbLabel.setText(self.tr("Running"))
447 self.tabWidget.setCurrentIndex(1)
448 self.__updateButtonBoxButtons()
449 self.__updateProgress()
450
451 self.__resultsModel.clear()
416 452
417 def __setStoppedMode(self): 453 def __setStoppedMode(self):
418 """ 454 """
419 Private method to switch the widget to stopped mode. 455 Private method to switch the widget to stopped mode.
420 """ 456 """
421 # TODO: not implemented yet 457 self.__mode = UnittestWidgetModes.STOPPED
422 pass 458
459 self.__updateButtonBoxButtons()
460
461 self.raise_()
462 self.activateWindow()
423 463
424 @pyqtSlot(QAbstractButton) 464 @pyqtSlot(QAbstractButton)
425 def on_buttonBox_clicked(self, button): 465 def on_buttonBox_clicked(self, button):
426 """ 466 """
427 Private slot called by a button of the button box clicked. 467 Private slot called by a button of the button box clicked.
428 468
429 @param button button that was clicked 469 @param button button that was clicked
430 @type QAbstractButton 470 @type QAbstractButton
431 """ 471 """
432 ## if button == self.discoverButton:
433 ## self.__discover()
434 ## self.__saveRecent()
435 ## elif button == self.__startButton:
436 if button == self.__startButton: 472 if button == self.__startButton:
437 self.startTests() 473 self.startTests()
438 self.__saveRecent() 474 self.__saveRecent()
439 elif button == self.__stopButton: 475 elif button == self.__stopButton:
440 self.__stopTests() 476 self.__stopTests()
521 self.__insertDiscovery(discoveryStart) 557 self.__insertDiscovery(discoveryStart)
522 else: 558 else:
523 discoveryStart = "" 559 discoveryStart = ""
524 testFileName = self.testsuitePicker.currentText() 560 testFileName = self.testsuitePicker.currentText()
525 if testFileName: 561 if testFileName:
526 self.__insertProg(testFileName) 562 self.__insertTestFile(testFileName)
527 testName = self.testComboBox.currentText() 563 testName = self.testComboBox.currentText()
528 if testName: 564 if testName:
529 self.insertTestName(testName) 565 self.__insertTestName(testName)
530 if testFileName and not testName: 566 if testFileName and not testName:
531 testName = "suite" 567 testName = "suite"
568
569 self.sbLabel.setText(self.tr("Preparing Testsuite"))
570 QCoreApplication.processEvents()
532 571
533 interpreter = self.__venvManager.getVirtualenvInterpreter( 572 interpreter = self.__venvManager.getVirtualenvInterpreter(
534 self.__recentEnvironment) 573 self.__recentEnvironment)
535 config = UTTestConfig( 574 config = UTTestConfig(
536 interpreter=interpreter, 575 interpreter=interpreter,
544 ) 583 )
545 584
546 self.__resultsModel.clear() 585 self.__resultsModel.clear()
547 self.__testExecutor = self.__frameworkRegistry.createExecutor( 586 self.__testExecutor = self.__frameworkRegistry.createExecutor(
548 self.__recentFramework, self) 587 self.__recentFramework, self)
549 self.__testExecutor.collected.connect(self.__testCollected) 588 self.__testExecutor.collected.connect(self.__testsCollected)
550 self.__testExecutor.collectError.connect(self.__testsCollectError) 589 self.__testExecutor.collectError.connect(self.__testsCollectError)
551 self.__testExecutor.startTest.connect(self.__testsStarted) 590 self.__testExecutor.startTest.connect(self.__testStarted)
552 self.__testExecutor.testResult.connect(self.__processTestResult) 591 self.__testExecutor.testResult.connect(self.__processTestResult)
553 self.__testExecutor.testFinished.connect(self.__testProcessFinished) 592 self.__testExecutor.testFinished.connect(self.__testProcessFinished)
593 self.__testExecutor.testRunFinished.connect(self.__testRunFinished)
554 self.__testExecutor.stop.connect(self.__testsStopped) 594 self.__testExecutor.stop.connect(self.__testsStopped)
595 self.__testExecutor.coverageDataSaved.connect(self.__coverageData)
596
597 self.__setRunningMode()
555 self.__testExecutor.start(config, []) 598 self.__testExecutor.start(config, [])
556 599
557 # TODO: not yet implemented 600 @pyqtSlot()
558 pass 601 def __stopTests(self):
602 """
603 Private slot to stop the current test run.
604 """
605 self.__testExecutor.stopIfRunning()
559 606
560 @pyqtSlot(list) 607 @pyqtSlot(list)
561 def __testCollected(self, testNames): 608 def __testsCollected(self, testNames):
562 """ 609 """
563 Private slot handling the 'collected' signal of the executor. 610 Private slot handling the 'collected' signal of the executor.
564 611
565 @param testNames list of names of collected tests 612 @param testNames list of tuples containing the test id and test name
566 @type list of str 613 of collected tests
567 """ 614 @type list of tuple of (str, str)
568 # TODO: not implemented yet 615 """
569 pass 616 testResults = [
617 UTTestResult(
618 category=ResultCategory.PENDING,
619 status=self.tr("pending"),
620 name=name,
621 id=id,
622 message=desc,
623 ) for id, name, desc in testNames
624 ]
625 self.__resultsModel.setTestResults(testResults)
626
627 self.__totalCount = len(testResults)
628 self.__updateProgress()
570 629
571 @pyqtSlot(list) 630 @pyqtSlot(list)
572 def __testsCollectError(self, errors): 631 def __testsCollectError(self, errors):
573 """ 632 """
574 Private slot handling the 'collectError' signal of the executor. 633 Private slot handling the 'collectError' signal of the executor.
575 634
576 @param errors list of tuples containing the test name and a description 635 @param errors list of tuples containing the test name and a description
577 of the error 636 of the error
578 @type list of tuple of (str, str) 637 @type list of tuple of (str, str)
579 """ 638 """
580 # TODO: not implemented yet 639 testResults = []
581 pass 640
582 641 for testFile, error in errors:
583 @pyqtSlot(list) 642 if testFile:
584 def __testsStarted(self, testNames): 643 testResults.append(UTTestResult(
644 category=ResultCategory.FAIL,
645 status=self.tr("Failure"),
646 name=testFile,
647 id=testFile,
648 message=self.tr("Collection Error"),
649 extra=error.splitlines()
650 ))
651 else:
652 EricMessageBox.critical(
653 self,
654 self.tr("Collection Error"),
655 self.tr(
656 "<p>There was an error while collecting unit tests."
657 "</p><p>{0}</p>"
658 ).format("<br/>".join(error.splitlines()))
659 )
660
661 if testResults:
662 self.__resultsModel.addTestResults(testResults)
663
664 @pyqtSlot(tuple)
665 def __testStarted(self, test):
585 """ 666 """
586 Private slot handling the 'startTest' signal of the executor. 667 Private slot handling the 'startTest' signal of the executor.
587 668
588 @param testNames list of names of tests about to be run 669 @param test tuple containing the id, name and short description of the
589 @type list of str 670 tests about to be run
590 """ 671 @type tuple of (str, str, str)
591 # TODO: not implemented yet 672 """
592 pass 673 self.__resultsModel.updateTestResults([
674 UTTestResult(
675 category=ResultCategory.RUNNING,
676 status=self.tr("running"),
677 id=test[0],
678 name=test[1],
679 message="" if test[2] is None else test[2],
680 )
681 ])
593 682
594 @pyqtSlot(UTTestResult) 683 @pyqtSlot(UTTestResult)
595 def __processTestResult(self, result): 684 def __processTestResult(self, result):
596 """ 685 """
597 Private slot to handle the receipt of a test result object. 686 Private slot to handle the receipt of a test result object.
598 687
599 @param result test result object 688 @param result test result object
600 @type UTTestResult 689 @type UTTestResult
601 """ 690 """
602 # TODO: not implemented yet 691 self.__runCount += 1
603 pass 692 self.__updateProgress()
693
694 self.__resultsModel.updateTestResults([result])
604 695
605 @pyqtSlot(list, str) 696 @pyqtSlot(list, str)
606 def __testProcessFinished(self, results, output): 697 def __testProcessFinished(self, results, output):
607 """ 698 """
608 Private slot to handle the 'testFinished' signal of the executor. 699 Private slot to handle the 'testFinished' signal of the executor.
611 'testResult' signal 702 'testResult' signal
612 @type list of UTTestResult 703 @type list of UTTestResult
613 @param output string containing the test process output (if any) 704 @param output string containing the test process output (if any)
614 @type str 705 @type str
615 """ 706 """
616 # TODO: not implemented yet 707 self.__setStoppedMode()
617 pass 708 self.__testExecutor = None
709
710 @pyqtSlot(int, float)
711 def __testRunFinished(self, noTests, duration):
712 """
713 Private slot to handle the 'testRunFinished' signal of the executor.
714
715 @param noTests number of tests run by the executor
716 @type int
717 @param duration time needed in seconds to run the tests
718 @type float
719 """
720 self.sbLabel.setText(
721 self.tr("Ran %n test(s) in {0}s", "", noTests).format(
722 locale.format_string("%.3f", duration, grouping=True)
723 )
724 )
725
726 self.__setStoppedMode()
618 727
619 @pyqtSlot() 728 @pyqtSlot()
620 def __testsStopped(self): 729 def __testsStopped(self):
621 """ 730 """
622 Private slot to handle the 'stop' signal of the executor. 731 Private slot to handle the 'stop' signal of the executor.
623 """ 732 """
624 # TODO: not implemented yet 733 self.sbLabel.setText(self.tr("Ran %n test(s)", "", self.__runCount))
625 pass 734
735 self.__setStoppedMode()
736
737 @pyqtSlot(str)
738 def __coverageData(self, coverageFile):
739 """
740 Private slot to handle the 'coverageData' signal of the executor.
741
742 @param coverageFile file containing the coverage data
743 @type str
744 """
745 self.__coverageFile = coverageFile
746
747 # TODO: implement the handling of the 'Show Coverage' button
626 748
627 749
628 class UnittestWindow(EricMainWindow): 750 class UnittestWindow(EricMainWindow):
629 """ 751 """
630 Main window class for the standalone dialog. 752 Main window class for the standalone dialog.

eric ide

mercurial