--- a/PyUnit/UnittestDialog.py Sun Mar 24 18:25:31 2019 +0100 +++ b/PyUnit/UnittestDialog.py Sun Mar 24 19:18:56 2019 +0100 @@ -18,7 +18,7 @@ from PyQt5.QtCore import pyqtSignal, QEvent, Qt, pyqtSlot from PyQt5.QtGui import QColor from PyQt5.QtWidgets import QWidget, QDialog, QApplication, QDialogButtonBox, \ - QListWidgetItem, QComboBox + QListWidgetItem, QComboBox, QTreeWidgetItem from E5Gui.E5Application import e5App from E5Gui import E5MessageBox @@ -76,6 +76,13 @@ QComboBox.AdjustToMinimumContentsLength) # TODO: add a "Discover" button enabled upon selection of 'auto-discovery' + self.discoverButton = self.buttonBox.addButton( + self.tr("Discover"), QDialogButtonBox.ActionRole) + self.discoverButton.setToolTip(self.tr( + "Discover tests")) + self.discoverButton.setWhatsThis(self.tr( + """<b>Discover</b>""" + """<p>This button starts a discovery of available tests.</p>""")) self.startButton = self.buttonBox.addButton( self.tr("Start"), QDialogButtonBox.ActionRole) self.startButton.setToolTip(self.tr( @@ -97,6 +104,7 @@ self.stopButton.setWhatsThis(self.tr( """<b>Stop Test</b>""" """<p>This button stops a running unittest.</p>""")) + self.discoverButton.setEnabled(False) self.stopButton.setEnabled(False) self.startButton.setDefault(True) self.startFailedButton.setEnabled(False) @@ -318,8 +326,15 @@ @param checked state of the checkbox @type bool """ - # TODO: enable this code once the discover button is available -# self.discoverButton.setEnabled(checked) + self.discoverButton.setEnabled(checked) + if not bool(self.discoveryPicker.currentText()): + if self.__forProject: + project = e5App().getObject("Project") + if project.isOpen(): + self.insertDiscovery(project.getProjectPath()) + return + + self.insertDiscovery(Preferences.getMultiProject("Workspace")) def on_buttonBox_clicked(self, button): """ @@ -327,7 +342,9 @@ @param button button that was clicked (QAbstractButton) """ - if button == self.startButton: + if button == self.discoverButton: + self.__discover() + elif button == self.startButton: self.startTests() elif button == self.stopButton: self.__stopTests() @@ -335,6 +352,127 @@ self.startTests(failedOnly=True) @pyqtSlot() + def __discover(self): + """ + Private slot to discover unit test but don't run them. + """ + if self.running: + return + + self.discoveryList.clear() + + discoveryStart = self.discoveryPicker.currentText() + self.sbLabel.setText(self.tr("Discovering Tests")) + QApplication.processEvents() + + self.testName = self.tr("Unittest with auto-discovery") + if self.__dbs: + # TODO: implement this later + pass + else: + # we are running as an application + if not discoveryStart: + E5MessageBox.critical( + self, + self.tr("Unittest"), + self.tr("You must enter a start directory for" + " auto-discovery.")) + return + + # clean up list of imported modules to force a reimport upon + # running the test + if self.savedModulelist: + for modname in list(sys.modules.keys()): + if modname not in self.savedModulelist: + # delete it + del(sys.modules[modname]) + self.savedModulelist = sys.modules.copy() + + # now try to discover the testsuite + os.chdir(discoveryStart) + try: + test = unittest.defaultTestLoader.discover(discoveryStart) + if test: + testsList = self.__assembleTestCasesList(test) + self.__populateDiscoveryResults(testsList) + self.sbLabel.setText( + self.tr("Discovered %n Test(s)", "", len(testsList))) + self.tabWidget.setCurrentIndex(0) + except Exception: + exc_type, exc_value, exc_tb = sys.exc_info() + E5MessageBox.critical( + self, + self.tr("Unittest"), + self.tr( + "<p>Unable to discover tests.<br/>" + "{0}<br/>{1}</p>") + .format(str(exc_type), str(exc_value))) + + def __assembleTestCasesList(self, suite): + """ + Private method to assemble a list of test cases included in a test + suite. + + @param suite test suite to be inspected + @type unittest.TestSuite + @return list of tuples containing the test case ID and short + description + @rtype list of tuples of (str, str) + """ + testCases = [] + for test in suite: + if isinstance(test, unittest.TestSuite): + testCases.extend(self.__assembleTestCasesList(test)) + else: + testCases.append((test.id(), test.shortDescription())) + return testCases + + def __findDiscoveryItem(self, modulePath): + """ + Private method to find an item given the module path. + + @param modulePath path of the module in dotted notation + @type str + @return reference to the item or None + @rtype QTreeWidgetItem or None + """ + itm = self.discoveryList.topLevelItem(0) + while itm is not None: + if itm.data(0, Qt.UserRole) == modulePath: + return itm + itm = self.discoveryList.itemBelow(itm) + + return None + + def __populateDiscoveryResults(self, tests): + """ + Private method to populate the test discovery results list. + + @param tests list of tuples containing the discovery results + @type list of tuples of (str, str) + """ + for test, _testDescription in tests: + testPath = test.split(".") + pitm = None + for index in range(1, len(testPath) + 1): + modulePath = ".".join(testPath[:index]) + itm = self.__findDiscoveryItem(modulePath) + if itm is not None: + pitm = itm + else: + if pitm is None: + itm = QTreeWidgetItem(self.discoveryList, + [testPath[index - 1]]) + else: + itm = QTreeWidgetItem(pitm, + [testPath[index - 1]]) + pitm.setExpanded(True) + itm.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) + itm.setCheckState(0, Qt.Unchecked) + itm.setData(0, Qt.UserRole, modulePath) + pitm = itm + + @pyqtSlot() def startTests(self, failedOnly=False): """ Public slot to start the test. @@ -466,6 +604,9 @@ if testFileName: sys.path = [os.path.dirname(os.path.abspath(testFileName))] + \ self.savedSysPath + elif discoveryStart: + sys.path = [os.path.abspath(discoveryStart)] + \ + self.savedSysPath # clean up list of imported modules to force a reimport upon # running the test @@ -489,7 +630,6 @@ failed = [] if discover: test = unittest.defaultTestLoader.discover(discoveryStart) -# testsList = self.__assembleTestCasesList(test) else: if testFileName: module = __import__(self.testName) @@ -548,25 +688,6 @@ self.__setStoppedMode() sys.path = self.savedSysPath - def __assembleTestCasesList(self, suite): - """ - Private method to assemble a list of test cases included in a test - suite. - - @param suite test suite to be inspected - @type unittest.TestSuite - @return list of tuples containing the test case ID and short - description - @rtype list of tuples of (str, str) - """ - testCases = [] - for test in suite: - if isinstance(test, unittest.TestSuite): - testCases.extend(self.__assembleTestCasesList(test)) - else: - testCases.append((test.id(), test.shortDescription())) - return testCases - def __UTPrepared(self, nrTests, exc_type, exc_value): """ Private slot to handle the utPrepared signal.