16 import os |
16 import os |
17 |
17 |
18 from PyQt5.QtCore import pyqtSignal, QEvent, Qt, pyqtSlot |
18 from PyQt5.QtCore import pyqtSignal, QEvent, Qt, pyqtSlot |
19 from PyQt5.QtGui import QColor |
19 from PyQt5.QtGui import QColor |
20 from PyQt5.QtWidgets import QWidget, QDialog, QApplication, QDialogButtonBox, \ |
20 from PyQt5.QtWidgets import QWidget, QDialog, QApplication, QDialogButtonBox, \ |
21 QListWidgetItem, QComboBox |
21 QListWidgetItem, QComboBox, QTreeWidgetItem |
22 |
22 |
23 from E5Gui.E5Application import e5App |
23 from E5Gui.E5Application import e5App |
24 from E5Gui import E5MessageBox |
24 from E5Gui import E5MessageBox |
25 from E5Gui.E5MainWindow import E5MainWindow |
25 from E5Gui.E5MainWindow import E5MainWindow |
26 from E5Gui.E5PathPicker import E5PathPickerModes |
26 from E5Gui.E5PathPicker import E5PathPickerModes |
74 self.discoveryPicker.setInsertPolicy(QComboBox.InsertAtTop) |
74 self.discoveryPicker.setInsertPolicy(QComboBox.InsertAtTop) |
75 self.discoveryPicker.setSizeAdjustPolicy( |
75 self.discoveryPicker.setSizeAdjustPolicy( |
76 QComboBox.AdjustToMinimumContentsLength) |
76 QComboBox.AdjustToMinimumContentsLength) |
77 |
77 |
78 # TODO: add a "Discover" button enabled upon selection of 'auto-discovery' |
78 # TODO: add a "Discover" button enabled upon selection of 'auto-discovery' |
|
79 self.discoverButton = self.buttonBox.addButton( |
|
80 self.tr("Discover"), QDialogButtonBox.ActionRole) |
|
81 self.discoverButton.setToolTip(self.tr( |
|
82 "Discover tests")) |
|
83 self.discoverButton.setWhatsThis(self.tr( |
|
84 """<b>Discover</b>""" |
|
85 """<p>This button starts a discovery of available tests.</p>""")) |
79 self.startButton = self.buttonBox.addButton( |
86 self.startButton = self.buttonBox.addButton( |
80 self.tr("Start"), QDialogButtonBox.ActionRole) |
87 self.tr("Start"), QDialogButtonBox.ActionRole) |
81 self.startButton.setToolTip(self.tr( |
88 self.startButton.setToolTip(self.tr( |
82 "Start the selected testsuite")) |
89 "Start the selected testsuite")) |
83 self.startButton.setWhatsThis(self.tr( |
90 self.startButton.setWhatsThis(self.tr( |
316 Private slot handling state changes of the 'discover' checkbox. |
324 Private slot handling state changes of the 'discover' checkbox. |
317 |
325 |
318 @param checked state of the checkbox |
326 @param checked state of the checkbox |
319 @type bool |
327 @type bool |
320 """ |
328 """ |
321 # TODO: enable this code once the discover button is available |
329 self.discoverButton.setEnabled(checked) |
322 # self.discoverButton.setEnabled(checked) |
330 if not bool(self.discoveryPicker.currentText()): |
|
331 if self.__forProject: |
|
332 project = e5App().getObject("Project") |
|
333 if project.isOpen(): |
|
334 self.insertDiscovery(project.getProjectPath()) |
|
335 return |
|
336 |
|
337 self.insertDiscovery(Preferences.getMultiProject("Workspace")) |
323 |
338 |
324 def on_buttonBox_clicked(self, button): |
339 def on_buttonBox_clicked(self, button): |
325 """ |
340 """ |
326 Private slot called by a button of the button box clicked. |
341 Private slot called by a button of the button box clicked. |
327 |
342 |
328 @param button button that was clicked (QAbstractButton) |
343 @param button button that was clicked (QAbstractButton) |
329 """ |
344 """ |
330 if button == self.startButton: |
345 if button == self.discoverButton: |
|
346 self.__discover() |
|
347 elif button == self.startButton: |
331 self.startTests() |
348 self.startTests() |
332 elif button == self.stopButton: |
349 elif button == self.stopButton: |
333 self.__stopTests() |
350 self.__stopTests() |
334 elif button == self.startFailedButton: |
351 elif button == self.startFailedButton: |
335 self.startTests(failedOnly=True) |
352 self.startTests(failedOnly=True) |
|
353 |
|
354 @pyqtSlot() |
|
355 def __discover(self): |
|
356 """ |
|
357 Private slot to discover unit test but don't run them. |
|
358 """ |
|
359 if self.running: |
|
360 return |
|
361 |
|
362 self.discoveryList.clear() |
|
363 |
|
364 discoveryStart = self.discoveryPicker.currentText() |
|
365 self.sbLabel.setText(self.tr("Discovering Tests")) |
|
366 QApplication.processEvents() |
|
367 |
|
368 self.testName = self.tr("Unittest with auto-discovery") |
|
369 if self.__dbs: |
|
370 # TODO: implement this later |
|
371 pass |
|
372 else: |
|
373 # we are running as an application |
|
374 if not discoveryStart: |
|
375 E5MessageBox.critical( |
|
376 self, |
|
377 self.tr("Unittest"), |
|
378 self.tr("You must enter a start directory for" |
|
379 " auto-discovery.")) |
|
380 return |
|
381 |
|
382 # clean up list of imported modules to force a reimport upon |
|
383 # running the test |
|
384 if self.savedModulelist: |
|
385 for modname in list(sys.modules.keys()): |
|
386 if modname not in self.savedModulelist: |
|
387 # delete it |
|
388 del(sys.modules[modname]) |
|
389 self.savedModulelist = sys.modules.copy() |
|
390 |
|
391 # now try to discover the testsuite |
|
392 os.chdir(discoveryStart) |
|
393 try: |
|
394 test = unittest.defaultTestLoader.discover(discoveryStart) |
|
395 if test: |
|
396 testsList = self.__assembleTestCasesList(test) |
|
397 self.__populateDiscoveryResults(testsList) |
|
398 self.sbLabel.setText( |
|
399 self.tr("Discovered %n Test(s)", "", len(testsList))) |
|
400 self.tabWidget.setCurrentIndex(0) |
|
401 except Exception: |
|
402 exc_type, exc_value, exc_tb = sys.exc_info() |
|
403 E5MessageBox.critical( |
|
404 self, |
|
405 self.tr("Unittest"), |
|
406 self.tr( |
|
407 "<p>Unable to discover tests.<br/>" |
|
408 "{0}<br/>{1}</p>") |
|
409 .format(str(exc_type), str(exc_value))) |
|
410 |
|
411 def __assembleTestCasesList(self, suite): |
|
412 """ |
|
413 Private method to assemble a list of test cases included in a test |
|
414 suite. |
|
415 |
|
416 @param suite test suite to be inspected |
|
417 @type unittest.TestSuite |
|
418 @return list of tuples containing the test case ID and short |
|
419 description |
|
420 @rtype list of tuples of (str, str) |
|
421 """ |
|
422 testCases = [] |
|
423 for test in suite: |
|
424 if isinstance(test, unittest.TestSuite): |
|
425 testCases.extend(self.__assembleTestCasesList(test)) |
|
426 else: |
|
427 testCases.append((test.id(), test.shortDescription())) |
|
428 return testCases |
|
429 |
|
430 def __findDiscoveryItem(self, modulePath): |
|
431 """ |
|
432 Private method to find an item given the module path. |
|
433 |
|
434 @param modulePath path of the module in dotted notation |
|
435 @type str |
|
436 @return reference to the item or None |
|
437 @rtype QTreeWidgetItem or None |
|
438 """ |
|
439 itm = self.discoveryList.topLevelItem(0) |
|
440 while itm is not None: |
|
441 if itm.data(0, Qt.UserRole) == modulePath: |
|
442 return itm |
|
443 itm = self.discoveryList.itemBelow(itm) |
|
444 |
|
445 return None |
|
446 |
|
447 def __populateDiscoveryResults(self, tests): |
|
448 """ |
|
449 Private method to populate the test discovery results list. |
|
450 |
|
451 @param tests list of tuples containing the discovery results |
|
452 @type list of tuples of (str, str) |
|
453 """ |
|
454 for test, _testDescription in tests: |
|
455 testPath = test.split(".") |
|
456 pitm = None |
|
457 for index in range(1, len(testPath) + 1): |
|
458 modulePath = ".".join(testPath[:index]) |
|
459 itm = self.__findDiscoveryItem(modulePath) |
|
460 if itm is not None: |
|
461 pitm = itm |
|
462 else: |
|
463 if pitm is None: |
|
464 itm = QTreeWidgetItem(self.discoveryList, |
|
465 [testPath[index - 1]]) |
|
466 else: |
|
467 itm = QTreeWidgetItem(pitm, |
|
468 [testPath[index - 1]]) |
|
469 pitm.setExpanded(True) |
|
470 itm.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) |
|
471 itm.setCheckState(0, Qt.Unchecked) |
|
472 itm.setData(0, Qt.UserRole, modulePath) |
|
473 pitm = itm |
336 |
474 |
337 @pyqtSlot() |
475 @pyqtSlot() |
338 def startTests(self, failedOnly=False): |
476 def startTests(self, failedOnly=False): |
339 """ |
477 """ |
340 Public slot to start the test. |
478 Public slot to start the test. |
546 cover.stop() |
686 cover.stop() |
547 cover.save() |
687 cover.save() |
548 self.__setStoppedMode() |
688 self.__setStoppedMode() |
549 sys.path = self.savedSysPath |
689 sys.path = self.savedSysPath |
550 |
690 |
551 def __assembleTestCasesList(self, suite): |
|
552 """ |
|
553 Private method to assemble a list of test cases included in a test |
|
554 suite. |
|
555 |
|
556 @param suite test suite to be inspected |
|
557 @type unittest.TestSuite |
|
558 @return list of tuples containing the test case ID and short |
|
559 description |
|
560 @rtype list of tuples of (str, str) |
|
561 """ |
|
562 testCases = [] |
|
563 for test in suite: |
|
564 if isinstance(test, unittest.TestSuite): |
|
565 testCases.extend(self.__assembleTestCasesList(test)) |
|
566 else: |
|
567 testCases.append((test.id(), test.shortDescription())) |
|
568 return testCases |
|
569 |
|
570 def __UTPrepared(self, nrTests, exc_type, exc_value): |
691 def __UTPrepared(self, nrTests, exc_type, exc_value): |
571 """ |
692 """ |
572 Private slot to handle the utPrepared signal. |
693 Private slot to handle the utPrepared signal. |
573 |
694 |
574 If the unittest suite was loaded successfully, we ask the |
695 If the unittest suite was loaded successfully, we ask the |