187 if testName in self.testNameHistory: |
278 if testName in self.testNameHistory: |
188 self.testNameHistory.remove(testName) |
279 self.testNameHistory.remove(testName) |
189 self.testNameHistory.insert(0, testName) |
280 self.testNameHistory.insert(0, testName) |
190 self.testComboBox.clear() |
281 self.testComboBox.clear() |
191 self.testComboBox.addItems(self.testNameHistory) |
282 self.testComboBox.addItems(self.testNameHistory) |
192 |
283 |
193 @pyqtSlot() |
284 @pyqtSlot() |
194 def on_testsuitePicker_aboutToShowPathPickerDialog(self): |
285 def on_testsuitePicker_aboutToShowPathPickerDialog(self): |
195 """ |
286 """ |
196 Private slot called before the test suite selection dialog is shown. |
287 Private slot called before the test suite selection dialog is shown. |
197 """ |
288 """ |
198 if self.dbs: |
289 if self.__dbs: |
199 py2Extensions = \ |
290 py2Extensions = \ |
200 ' '.join(["*{0}".format(ext) |
291 ' '.join(["*{0}".format(ext) |
201 for ext in self.dbs.getExtensions('Python2')]) |
292 for ext in self.__dbs.getExtensions('Python2')]) |
202 py3Extensions = \ |
293 py3Extensions = \ |
203 ' '.join(["*{0}".format(ext) |
294 ' '.join(["*{0}".format(ext) |
204 for ext in self.dbs.getExtensions('Python3')]) |
295 for ext in self.__dbs.getExtensions('Python3')]) |
205 fileFilter = self.tr( |
296 fileFilter = self.tr( |
206 "Python3 Files ({1});;Python2 Files ({0});;All Files (*)")\ |
297 "Python3 Files ({1});;Python2 Files ({0});;All Files (*)")\ |
207 .format(py2Extensions, py3Extensions) |
298 .format(py2Extensions, py3Extensions) |
208 else: |
299 else: |
209 fileFilter = self.tr("Python Files (*.py);;All Files (*)") |
300 fileFilter = self.tr("Python Files (*.py);;All Files (*)") |
210 self.testsuitePicker.setFilters(fileFilter) |
301 self.testsuitePicker.setFilters(fileFilter) |
211 |
302 |
|
303 defaultDirectory = Preferences.getMultiProject("Workspace") |
|
304 if not defaultDirectory: |
|
305 defaultDirectory = os.path.expanduser("~") |
|
306 if self.__dbs: |
|
307 project = e5App().getObject("Project") |
|
308 if self.__forProject and project.isOpen(): |
|
309 defaultDirectory = project.getProjectPath() |
|
310 self.testsuitePicker.setDefaultDirectory(defaultDirectory) |
|
311 |
212 @pyqtSlot(str) |
312 @pyqtSlot(str) |
213 def on_testsuitePicker_pathSelected(self, suite): |
313 def on_testsuitePicker_pathSelected(self, suite): |
214 """ |
314 """ |
215 Private slot called after a test suite has been selected. |
315 Private slot called after a test suite has been selected. |
216 |
316 |
217 @param suite file name of the test suite |
317 @param suite file name of the test suite |
218 @type str |
318 @type str |
219 """ |
319 """ |
220 self.insertProg(suite) |
320 self.insertProg(suite) |
221 |
321 |
222 @pyqtSlot(str) |
322 @pyqtSlot(str) |
223 def on_testsuitePicker_editTextChanged(self, txt): |
323 def on_testsuitePicker_editTextChanged(self, path): |
224 """ |
324 """ |
225 Private slot to handle changes of the test file name. |
325 Private slot handling changes of the test suite path. |
226 |
326 |
227 @param txt name of the test file (string) |
327 @param path path of the test suite file |
228 """ |
328 @type str |
229 if self.dbs: |
329 """ |
230 exts = self.dbs.getExtensions("Python2") |
330 self.startFailedButton.setEnabled(False) |
231 flags = Utilities.extractFlagsFromFile(txt) |
331 |
232 if txt.endswith(exts) or \ |
332 @pyqtSlot(bool) |
233 ("FileType" in flags and |
333 def on_discoverCheckBox_toggled(self, checked): |
234 flags["FileType"] in ["Python", "Python2"]): |
334 """ |
235 self.coverageCheckBox.setChecked(False) |
335 Private slot handling state changes of the 'discover' checkbox. |
236 self.coverageCheckBox.setEnabled(False) |
336 |
237 self.localCheckBox.setChecked(False) |
337 @param checked state of the checkbox |
238 self.localCheckBox.setEnabled(False) |
338 @type bool |
239 return |
339 """ |
240 |
340 self.discoverButton.setEnabled(checked) |
241 self.coverageCheckBox.setEnabled(True) |
341 self.discoveryList.clear() |
242 self.localCheckBox.setEnabled(True) |
342 |
243 |
343 if not bool(self.discoveryPicker.currentText()): |
|
344 if self.__forProject: |
|
345 project = e5App().getObject("Project") |
|
346 if project.isOpen(): |
|
347 self.insertDiscovery(project.getProjectPath()) |
|
348 return |
|
349 |
|
350 self.insertDiscovery(Preferences.getMultiProject("Workspace")) |
|
351 |
244 def on_buttonBox_clicked(self, button): |
352 def on_buttonBox_clicked(self, button): |
245 """ |
353 """ |
246 Private slot called by a button of the button box clicked. |
354 Private slot called by a button of the button box clicked. |
247 |
355 |
248 @param button button that was clicked (QAbstractButton) |
356 @param button button that was clicked (QAbstractButton) |
249 """ |
357 """ |
250 if button == self.startButton: |
358 if button == self.discoverButton: |
251 self.on_startButton_clicked() |
359 self.__discover() |
|
360 elif button == self.startButton: |
|
361 self.startTests() |
252 elif button == self.stopButton: |
362 elif button == self.stopButton: |
253 self.on_stopButton_clicked() |
363 self.__stopTests() |
254 elif button == self.startFailedButton: |
364 elif button == self.startFailedButton: |
255 self.on_startButton_clicked(failedOnly=True) |
365 self.startTests(failedOnly=True) |
256 |
366 |
257 @pyqtSlot() |
367 @pyqtSlot() |
258 def on_startButton_clicked(self, failedOnly=False): |
368 def __discover(self): |
259 """ |
369 """ |
260 Private slot to start the test. |
370 Private slot to discover unit test but don't run them. |
261 |
|
262 @keyparam failedOnly flag indicating to run only failed tests (boolean) |
|
263 """ |
371 """ |
264 if self.running: |
372 if self.running: |
265 return |
373 return |
266 |
374 |
267 prog = self.testsuitePicker.currentText() |
375 self.discoveryList.clear() |
268 if not prog: |
376 |
269 E5MessageBox.critical( |
377 discoveryStart = self.discoveryPicker.currentText() |
270 self, |
378 self.sbLabel.setText(self.tr("Discovering Tests")) |
271 self.tr("Unittest"), |
|
272 self.tr("You must enter a test suite file.")) |
|
273 return |
|
274 |
|
275 # prepend the selected file to the testsuite combobox |
|
276 self.insertProg(prog) |
|
277 self.sbLabel.setText(self.tr("Preparing Testsuite")) |
|
278 QApplication.processEvents() |
379 QApplication.processEvents() |
279 |
380 |
280 testFunctionName = self.testComboBox.currentText() |
381 self.testName = self.tr("Unittest with auto-discovery") |
281 if testFunctionName: |
382 if self.__dbs: |
282 self.insertTestName(testFunctionName) |
383 venvName = self.venvComboBox.currentText() |
283 else: |
384 |
284 testFunctionName = "suite" |
|
285 |
|
286 # build the module name from the filename without extension |
|
287 self.testName = os.path.splitext(os.path.basename(prog))[0] |
|
288 |
|
289 if self.dbs and not self.localCheckBox.isChecked(): |
|
290 # we are cooperating with the eric6 IDE |
385 # we are cooperating with the eric6 IDE |
291 project = e5App().getObject("Project") |
386 project = e5App().getObject("Project") |
292 if project.isOpen() and project.isProjectSource(prog): |
387 if self.__forProject: |
293 mainScript = project.getMainScript(True) |
388 mainScript = os.path.abspath(project.getMainScript(True)) |
294 clientType = project.getProjectLanguage() |
389 clientType = project.getProjectLanguage() |
|
390 if mainScript: |
|
391 workdir = os.path.dirname(mainScript) |
|
392 else: |
|
393 workdir = project.getProjectPath() |
|
394 sysPath = [workdir] |
|
395 if not discoveryStart: |
|
396 discoveryStart = workdir |
295 else: |
397 else: |
296 mainScript = os.path.abspath(prog) |
398 if not discoveryStart: |
297 flags = Utilities.extractFlagsFromFile(mainScript) |
399 E5MessageBox.critical( |
298 if mainScript.endswith( |
400 self, |
299 tuple(Preferences.getPython("PythonExtensions"))) or \ |
401 self.tr("Unittest"), |
300 ("FileType" in flags and |
402 self.tr("You must enter a start directory for" |
301 flags["FileType"] in ["Python", "Python2"]): |
403 " auto-discovery.")) |
302 clientType = "Python2" |
404 return |
303 else: |
405 |
304 clientType = "" |
406 workdir = "" |
305 if failedOnly and self.__failedTests: |
407 clientType = \ |
306 failed = [t.split(".", 1)[1] for t in self.__failedTests] |
408 self.__venvManager.getVirtualenvVariant(venvName) |
307 else: |
409 if not clientType: |
308 failed = [] |
410 # assume Python 3 |
309 self.__failedTests = [] |
411 clientType = "Python3" |
310 self.dbs.remoteUTPrepare( |
412 sysPath = [] |
311 prog, self.testName, testFunctionName, failed, |
413 self.__dbs.remoteUTDiscover(clientType, self.__forProject, |
312 self.coverageCheckBox.isChecked(), mainScript, |
414 workdir, venvName, sysPath, |
313 self.coverageEraseCheckBox.isChecked(), clientType=clientType) |
415 discoveryStart) |
314 else: |
416 else: |
315 # we are running as an application or in local mode |
417 # we are running as an application |
316 sys.path = [os.path.dirname(os.path.abspath(prog))] + \ |
418 if not discoveryStart: |
317 self.savedSysPath |
419 E5MessageBox.critical( |
|
420 self, |
|
421 self.tr("Unittest"), |
|
422 self.tr("You must enter a start directory for" |
|
423 " auto-discovery.")) |
|
424 return |
|
425 |
|
426 if discoveryStart: |
|
427 sys.path = [os.path.abspath(discoveryStart)] + \ |
|
428 self.savedSysPath |
318 |
429 |
319 # clean up list of imported modules to force a reimport upon |
430 # clean up list of imported modules to force a reimport upon |
320 # running the test |
431 # running the test |
321 if self.savedModulelist: |
432 if self.savedModulelist: |
322 for modname in list(sys.modules.keys()): |
433 for modname in list(sys.modules.keys()): |
323 if modname not in self.savedModulelist: |
434 if modname not in self.savedModulelist: |
324 # delete it |
435 # delete it |
325 del(sys.modules[modname]) |
436 del(sys.modules[modname]) |
326 self.savedModulelist = sys.modules.copy() |
437 self.savedModulelist = sys.modules.copy() |
327 |
438 |
328 # now try to generate the testsuite |
439 # now try to discover the testsuite |
|
440 os.chdir(discoveryStart) |
329 try: |
441 try: |
330 module = __import__(self.testName) |
442 testLoader = unittest.TestLoader() |
331 try: |
443 test = testLoader.discover(discoveryStart) |
332 if failedOnly and self.__failedTests: |
444 if hasattr(testLoader, "errors") and \ |
333 test = unittest.defaultTestLoader.loadTestsFromNames( |
445 bool(testLoader.errors): |
334 [t.split(".", 1)[1] for t in self.__failedTests], |
446 E5MessageBox.critical( |
335 module) |
447 self, |
336 else: |
448 self.tr("Unittest"), |
337 test = unittest.defaultTestLoader.loadTestsFromName( |
449 self.tr( |
338 testFunctionName, module) |
450 "<p>Unable to discover tests.</p>" |
339 except AttributeError: |
451 "<p>{0}</p>" |
340 test = unittest.defaultTestLoader.loadTestsFromModule( |
452 ).format("<br/>".join(testLoader.errors) |
341 module) |
453 .replace("\n", "<br/>")) |
|
454 ) |
|
455 self.sbLabel.clear() |
|
456 else: |
|
457 testsList = self.__assembleTestCasesList( |
|
458 test, discoveryStart) |
|
459 self.__populateDiscoveryResults(testsList) |
|
460 self.sbLabel.setText( |
|
461 self.tr("Discovered %n Test(s)", "", |
|
462 len(testsList))) |
|
463 self.tabWidget.setCurrentIndex(0) |
342 except Exception: |
464 except Exception: |
343 exc_type, exc_value, exc_tb = sys.exc_info() |
465 exc_type, exc_value, exc_tb = sys.exc_info() |
344 E5MessageBox.critical( |
466 E5MessageBox.critical( |
345 self, |
467 self, |
346 self.tr("Unittest"), |
468 self.tr("Unittest"), |
347 self.tr( |
469 self.tr( |
348 "<p>Unable to run test <b>{0}</b>.<br>" |
470 "<p>Unable to discover tests.</p>" |
349 "{1}<br>{2}</p>") |
471 "<p>{0}<br/>{1}</p>") |
|
472 .format(str(exc_type), |
|
473 str(exc_value).replace("\n", "<br/>")) |
|
474 ) |
|
475 self.sbLabel.clear() |
|
476 |
|
477 sys.path = self.savedSysPath |
|
478 |
|
479 def __assembleTestCasesList(self, suite, start): |
|
480 """ |
|
481 Private method to assemble a list of test cases included in a test |
|
482 suite. |
|
483 |
|
484 @param suite test suite to be inspected |
|
485 @type unittest.TestSuite |
|
486 @param start name of directory discovery was started at |
|
487 @type str |
|
488 @return list of tuples containing the test case ID, a short description |
|
489 and the path of the test file name |
|
490 @rtype list of tuples of (str, str, str) |
|
491 """ |
|
492 testCases = [] |
|
493 for test in suite: |
|
494 if isinstance(test, unittest.TestSuite): |
|
495 testCases.extend(self.__assembleTestCasesList(test, start)) |
|
496 else: |
|
497 testId = test.id() |
|
498 if "ModuleImportFailure" not in testId and \ |
|
499 "LoadTestsFailure" not in testId and \ |
|
500 "_FailedTest" not in testId: |
|
501 filename = os.path.join( |
|
502 start, |
|
503 test.__module__.replace(".", os.sep) + ".py") |
|
504 testCases.append( |
|
505 (test.id(), test.shortDescription(), filename) |
|
506 ) |
|
507 return testCases |
|
508 |
|
509 def __findDiscoveryItem(self, modulePath): |
|
510 """ |
|
511 Private method to find an item given the module path. |
|
512 |
|
513 @param modulePath path of the module in dotted notation |
|
514 @type str |
|
515 @return reference to the item or None |
|
516 @rtype QTreeWidgetItem or None |
|
517 """ |
|
518 itm = self.discoveryList.topLevelItem(0) |
|
519 while itm is not None: |
|
520 if itm.data(0, UnittestDialog.TestCaseNameRole) == modulePath: |
|
521 return itm |
|
522 itm = self.discoveryList.itemBelow(itm) |
|
523 |
|
524 return None |
|
525 |
|
526 def __populateDiscoveryResults(self, tests): |
|
527 """ |
|
528 Private method to populate the test discovery results list. |
|
529 |
|
530 @param tests list of tuples containing the discovery results |
|
531 @type list of tuples of (str, str, str) |
|
532 """ |
|
533 for test, _testDescription, filename in tests: |
|
534 testPath = test.split(".") |
|
535 pitm = None |
|
536 for index in range(1, len(testPath) + 1): |
|
537 modulePath = ".".join(testPath[:index]) |
|
538 itm = self.__findDiscoveryItem(modulePath) |
|
539 if itm is not None: |
|
540 pitm = itm |
|
541 else: |
|
542 if pitm is None: |
|
543 itm = QTreeWidgetItem(self.discoveryList, |
|
544 [testPath[index - 1]]) |
|
545 else: |
|
546 itm = QTreeWidgetItem(pitm, |
|
547 [testPath[index - 1]]) |
|
548 pitm.setExpanded(True) |
|
549 itm.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) |
|
550 itm.setCheckState(0, Qt.Unchecked) |
|
551 itm.setData(0, UnittestDialog.TestCaseNameRole, modulePath) |
|
552 if os.path.splitext(os.path.basename(filename))[0] == \ |
|
553 itm.text(0): |
|
554 itm.setData(0, UnittestDialog.TestCaseFileRole, |
|
555 filename) |
|
556 elif pitm: |
|
557 fn = pitm.data(0, UnittestDialog.TestCaseFileRole) |
|
558 if fn: |
|
559 itm.setData(0, UnittestDialog.TestCaseFileRole, fn) |
|
560 pitm = itm |
|
561 |
|
562 def __selectedTestCases(self, parent=None): |
|
563 """ |
|
564 Private method to assemble the list of selected test cases and suites. |
|
565 |
|
566 @param parent reference to the parent item |
|
567 @type QTreeWidgetItem |
|
568 @return list of selected test cases |
|
569 @rtype list of str |
|
570 """ |
|
571 selectedTests = [] |
|
572 if parent is None: |
|
573 # top level |
|
574 for index in range(self.discoveryList.topLevelItemCount()): |
|
575 itm = self.discoveryList.topLevelItem(index) |
|
576 if itm.checkState(0) == Qt.Checked: |
|
577 selectedTests.append( |
|
578 itm.data(0, UnittestDialog.TestCaseNameRole)) |
|
579 # ignore children because they are included implicitly |
|
580 elif itm.childCount(): |
|
581 # recursively check children |
|
582 selectedTests.extend(self.__selectedTestCases(itm)) |
|
583 |
|
584 else: |
|
585 # parent item with children |
|
586 for index in range(parent.childCount()): |
|
587 itm = parent.child(index) |
|
588 if itm.checkState(0) == Qt.Checked: |
|
589 selectedTests.append( |
|
590 itm.data(0, UnittestDialog.TestCaseNameRole)) |
|
591 # ignore children because they are included implicitly |
|
592 elif itm.childCount(): |
|
593 # recursively check children |
|
594 selectedTests.extend(self.__selectedTestCases(itm)) |
|
595 |
|
596 return selectedTests |
|
597 |
|
598 def __UTDiscovered(self, testCases, exc_type, exc_value): |
|
599 """ |
|
600 Private slot to handle the utDiscovered signal. |
|
601 |
|
602 If the unittest suite was loaded successfully, we ask the |
|
603 client to run the test suite. |
|
604 |
|
605 @param testCases list of detected test cases |
|
606 @type str |
|
607 @param exc_type exception type occured during discovery |
|
608 @type str |
|
609 @param exc_value value of exception occured during discovery |
|
610 @type str |
|
611 """ |
|
612 if testCases: |
|
613 self.__populateDiscoveryResults(testCases) |
|
614 self.sbLabel.setText( |
|
615 self.tr("Discovered %n Test(s)", "", |
|
616 len(testCases))) |
|
617 self.tabWidget.setCurrentIndex(0) |
|
618 else: |
|
619 E5MessageBox.critical( |
|
620 self, |
|
621 self.tr("Unittest"), |
|
622 self.tr("<p>Unable to discover tests.</p>" |
|
623 "<p>{0}<br/>{1}</p>") |
|
624 .format(exc_type, exc_value.replace("\n", "<br/>")) |
|
625 ) |
|
626 |
|
627 @pyqtSlot(QTreeWidgetItem, int) |
|
628 def on_discoveryList_itemDoubleClicked(self, item, column): |
|
629 """ |
|
630 Private slot handling the user double clicking an item. |
|
631 |
|
632 @param item reference to the item |
|
633 @type QTreeWidgetItem |
|
634 @param column column of the double click |
|
635 @type int |
|
636 """ |
|
637 if item: |
|
638 filename = item.data(0, UnittestDialog.TestCaseFileRole) |
|
639 if filename: |
|
640 if self.__dbs: |
|
641 # running as part of eric IDE |
|
642 self.unittestFile.emit(filename, 1, False) |
|
643 else: |
|
644 self.__openEditor(filename, 1) |
|
645 |
|
646 @pyqtSlot() |
|
647 def startTests(self, failedOnly=False): |
|
648 """ |
|
649 Public slot to start the test. |
|
650 |
|
651 @keyparam failedOnly flag indicating to run only failed tests (boolean) |
|
652 """ |
|
653 if self.running: |
|
654 return |
|
655 |
|
656 discover = self.discoverCheckBox.isChecked() |
|
657 if discover: |
|
658 discoveryStart = self.discoveryPicker.currentText() |
|
659 testFileName = "" |
|
660 testName = "" |
|
661 else: |
|
662 discoveryStart = "" |
|
663 testFileName = self.testsuitePicker.currentText() |
|
664 testName = self.testComboBox.currentText() |
|
665 if testName: |
|
666 self.insertTestName(testName) |
|
667 if testFileName and not testName: |
|
668 testName = "suite" |
|
669 |
|
670 if not discover and not testFileName and not testName: |
|
671 E5MessageBox.critical( |
|
672 self, |
|
673 self.tr("Unittest"), |
|
674 self.tr("You must select auto-discovery or enter a test suite" |
|
675 " file or a dotted test name.")) |
|
676 return |
|
677 |
|
678 # prepend the selected file to the testsuite combobox |
|
679 self.insertProg(testFileName) |
|
680 self.sbLabel.setText(self.tr("Preparing Testsuite")) |
|
681 QApplication.processEvents() |
|
682 |
|
683 if discover: |
|
684 self.testName = self.tr("Unittest with auto-discovery") |
|
685 else: |
|
686 # build the module name from the filename without extension |
|
687 if testFileName: |
|
688 self.testName = os.path.splitext( |
|
689 os.path.basename(testFileName))[0] |
|
690 elif testName: |
|
691 self.testName = testName |
|
692 else: |
|
693 self.testName = self.tr("<Unnamed Test>") |
|
694 |
|
695 if failedOnly: |
|
696 testCases = [] |
|
697 else: |
|
698 testCases = self.__selectedTestCases() |
|
699 |
|
700 if not testCases and self.discoveryList.topLevelItemCount(): |
|
701 ok = E5MessageBox.yesNo( |
|
702 self, |
|
703 self.tr("Unittest"), |
|
704 self.tr("""No test case has been selected. Shall all""" |
|
705 """ test cases be run?""")) |
|
706 if not ok: |
|
707 return |
|
708 |
|
709 if self.__dbs: |
|
710 venvName = self.venvComboBox.currentText() |
|
711 |
|
712 # we are cooperating with the eric6 IDE |
|
713 project = e5App().getObject("Project") |
|
714 if self.__forProject: |
|
715 mainScript = os.path.abspath(project.getMainScript(True)) |
|
716 clientType = project.getProjectLanguage() |
|
717 if mainScript: |
|
718 workdir = os.path.dirname(mainScript) |
|
719 else: |
|
720 workdir = project.getProjectPath() |
|
721 sysPath = [workdir] |
|
722 coverageFile = os.path.splitext(mainScript)[0] |
|
723 if discover and not discoveryStart: |
|
724 discoveryStart = workdir |
|
725 else: |
|
726 if discover: |
|
727 if not discoveryStart: |
|
728 E5MessageBox.critical( |
|
729 self, |
|
730 self.tr("Unittest"), |
|
731 self.tr("You must enter a start directory for" |
|
732 " auto-discovery.")) |
|
733 return |
|
734 |
|
735 coverageFile = os.path.join(discoveryStart, "unittest") |
|
736 workdir = "" |
|
737 clientType = \ |
|
738 self.__venvManager.getVirtualenvVariant(venvName) |
|
739 if not clientType: |
|
740 # assume Python 3 |
|
741 clientType = "Python3" |
|
742 elif testFileName: |
|
743 mainScript = os.path.abspath(testFileName) |
|
744 flags = Utilities.extractFlagsFromFile(mainScript) |
|
745 workdir = os.path.dirname(mainScript) |
|
746 if mainScript.endswith( |
|
747 tuple(Preferences.getPython("PythonExtensions"))) or \ |
|
748 ("FileType" in flags and |
|
749 flags["FileType"] in ["Python", "Python2"]): |
|
750 clientType = "Python2" |
|
751 else: |
|
752 # if it is not Python2 it must be Python3! |
|
753 clientType = "Python3" |
|
754 coverageFile = os.path.splitext(mainScript)[0] |
|
755 else: |
|
756 coverageFile = os.path.abspath("unittest") |
|
757 workdir = "" |
|
758 clientType = \ |
|
759 self.__venvManager.getVirtualenvVariant(venvName) |
|
760 if not clientType: |
|
761 # assume Python 3 |
|
762 clientType = "Python3" |
|
763 sysPath = [] |
|
764 if failedOnly and self.__failedTests: |
|
765 failed = self.__failedTests[:] |
|
766 if discover: |
|
767 workdir = discoveryStart |
|
768 discover = False |
|
769 else: |
|
770 failed = [] |
|
771 self.__failedTests = [] |
|
772 self.__dbs.remoteUTPrepare( |
|
773 testFileName, self.testName, testName, failed, |
|
774 self.coverageCheckBox.isChecked(), coverageFile, |
|
775 self.coverageEraseCheckBox.isChecked(), clientType=clientType, |
|
776 forProject=self.__forProject, workdir=workdir, |
|
777 venvName=venvName, syspath=sysPath, |
|
778 discover=discover, discoveryStart=discoveryStart, |
|
779 testCases=testCases, debug=self.debuggerCheckBox.isChecked()) |
|
780 else: |
|
781 # we are running as an application |
|
782 if discover and not discoveryStart: |
|
783 E5MessageBox.critical( |
|
784 self, |
|
785 self.tr("Unittest"), |
|
786 self.tr("You must enter a start directory for" |
|
787 " auto-discovery.")) |
|
788 return |
|
789 |
|
790 if testFileName: |
|
791 sys.path = [os.path.dirname(os.path.abspath(testFileName))] + \ |
|
792 self.savedSysPath |
|
793 elif discoveryStart: |
|
794 sys.path = [os.path.abspath(discoveryStart)] + \ |
|
795 self.savedSysPath |
|
796 |
|
797 # clean up list of imported modules to force a reimport upon |
|
798 # running the test |
|
799 if self.savedModulelist: |
|
800 for modname in list(sys.modules.keys()): |
|
801 if modname not in self.savedModulelist: |
|
802 # delete it |
|
803 del(sys.modules[modname]) |
|
804 self.savedModulelist = sys.modules.copy() |
|
805 |
|
806 os.chdir(self.savedCwd) |
|
807 |
|
808 # now try to generate the testsuite |
|
809 try: |
|
810 testLoader = unittest.TestLoader() |
|
811 if failedOnly and self.__failedTests: |
|
812 failed = self.__failedTests[:] |
|
813 if discover: |
|
814 os.chdir(discoveryStart) |
|
815 discover = False |
|
816 else: |
|
817 failed = [] |
|
818 if discover: |
|
819 if testCases: |
|
820 test = testLoader.loadTestsFromNames(testCases) |
|
821 else: |
|
822 test = testLoader.discover(discoveryStart) |
|
823 else: |
|
824 if testFileName: |
|
825 module = __import__(self.testName) |
|
826 else: |
|
827 module = None |
|
828 if failedOnly and self.__failedTests: |
|
829 if module: |
|
830 failed = [t.split(".", 1)[1] |
|
831 for t in self.__failedTests] |
|
832 else: |
|
833 failed = self.__failedTests[:] |
|
834 test = testLoader.loadTestsFromNames( |
|
835 failed, module) |
|
836 else: |
|
837 test = testLoader.loadTestsFromName( |
|
838 testName, module) |
|
839 except Exception: |
|
840 exc_type, exc_value, exc_tb = sys.exc_info() |
|
841 E5MessageBox.critical( |
|
842 self, |
|
843 self.tr("Unittest"), |
|
844 self.tr( |
|
845 "<p>Unable to run test <b>{0}</b>.</p>" |
|
846 "<p>{1}<br/>{2}</p>") |
350 .format(self.testName, str(exc_type), |
847 .format(self.testName, str(exc_type), |
351 str(exc_value))) |
848 str(exc_value).replace("\n", "<br/>")) |
|
849 ) |
352 return |
850 return |
353 |
851 |
354 # now set up the coverage stuff |
852 # now set up the coverage stuff |
355 if self.coverageCheckBox.isChecked(): |
853 if self.coverageCheckBox.isChecked(): |
356 if self.dbs: |
854 if discover: |
357 # we are cooperating with the eric6 IDE |
855 covname = os.path.join(discoveryStart, "unittest") |
358 project = e5App().getObject("Project") |
856 elif testFileName: |
359 if project.isOpen() and project.isProjectSource(prog): |
857 covname = \ |
360 mainScript = project.getMainScript(True) |
858 os.path.splitext(os.path.abspath(testFileName))[0] |
361 if not mainScript: |
|
362 mainScript = os.path.abspath(prog) |
|
363 else: |
|
364 mainScript = os.path.abspath(prog) |
|
365 else: |
859 else: |
366 mainScript = os.path.abspath(prog) |
860 covname = "unittest" |
367 |
861 |
368 from DebugClients.Python.coverage import coverage |
862 from DebugClients.Python.coverage import coverage |
369 cover = coverage( |
863 cover = coverage(data_file="{0}.coverage".format(covname)) |
370 data_file="{0}.coverage".format( |
|
371 os.path.splitext(mainScript)[0])) |
|
372 if self.coverageEraseCheckBox.isChecked(): |
864 if self.coverageEraseCheckBox.isChecked(): |
373 cover.erase() |
865 cover.erase() |
374 else: |
866 else: |
375 cover = None |
867 cover = None |
376 |
868 |
377 self.testResult = QtTestResult(self) |
869 self.testResult = QtTestResult( |
|
870 self, self.failfastCheckBox.isChecked()) |
378 self.totalTests = test.countTestCases() |
871 self.totalTests = test.countTestCases() |
379 self.__failedTests = [] |
872 self.__failedTests = [] |
380 self.__setRunningMode() |
873 self.__setRunningMode() |
381 if cover: |
874 if cover: |
382 cover.start() |
875 cover.start() |
435 text, Qt.MatchFlags(Qt.MatchExactly)) |
932 text, Qt.MatchFlags(Qt.MatchExactly)) |
436 if len(foundItems) > 0: |
933 if len(foundItems) > 0: |
437 itm = foundItems[0] |
934 itm = foundItems[0] |
438 self.testsListWidget.setCurrentItem(itm) |
935 self.testsListWidget.setCurrentItem(itm) |
439 self.testsListWidget.scrollToItem(itm) |
936 self.testsListWidget.scrollToItem(itm) |
440 |
937 |
441 def __setRunningMode(self): |
938 def __setRunningMode(self): |
442 """ |
939 """ |
443 Private method to set the GUI in running mode. |
940 Private method to set the GUI in running mode. |
444 """ |
941 """ |
445 self.running = True |
942 self.running = True |
|
943 self.tabWidget.setCurrentIndex(1) |
446 |
944 |
447 # reset counters and error infos |
945 # reset counters and error infos |
448 self.runCount = 0 |
946 self.runCount = 0 |
449 self.failCount = 0 |
947 self.failCount = 0 |
450 self.errorCount = 0 |
948 self.errorCount = 0 |
451 self.skippedCount = 0 |
949 self.skippedCount = 0 |
452 self.expectedFailureCount = 0 |
950 self.expectedFailureCount = 0 |
453 self.unexpectedSuccessCount = 0 |
951 self.unexpectedSuccessCount = 0 |
454 self.remainingCount = self.totalTests |
952 self.remainingCount = self.totalTests |
455 |
953 |
456 # reset the GUI |
954 # reset the GUI |
457 self.progressCounterRunCount.setText(str(self.runCount)) |
955 self.progressCounterRunCount.setText(str(self.runCount)) |
458 self.progressCounterRemCount.setText(str(self.remainingCount)) |
956 self.progressCounterRemCount.setText(str(self.remainingCount)) |
459 self.progressCounterFailureCount.setText(str(self.failCount)) |
957 self.progressCounterFailureCount.setText(str(self.failCount)) |
460 self.progressCounterErrorCount.setText(str(self.errorCount)) |
958 self.progressCounterErrorCount.setText(str(self.errorCount)) |
461 self.progressCounterSkippedCount.setText(str(self.skippedCount)) |
959 self.progressCounterSkippedCount.setText(str(self.skippedCount)) |
462 self.progressCounterExpectedFailureCount.setText( |
960 self.progressCounterExpectedFailureCount.setText( |
463 str(self.expectedFailureCount)) |
961 str(self.expectedFailureCount)) |
464 self.progressCounterUnexpectedSuccessCount.setText( |
962 self.progressCounterUnexpectedSuccessCount.setText( |
465 str(self.unexpectedSuccessCount)) |
963 str(self.unexpectedSuccessCount)) |
|
964 |
466 self.errorsListWidget.clear() |
965 self.errorsListWidget.clear() |
467 self.testsListWidget.clear() |
966 self.testsListWidget.clear() |
|
967 |
468 self.progressProgressBar.setRange(0, self.totalTests) |
968 self.progressProgressBar.setRange(0, self.totalTests) |
469 self.__setProgressColor("green") |
969 self.__setProgressColor("green") |
470 self.progressProgressBar.reset() |
970 self.progressProgressBar.reset() |
|
971 |
471 self.stopButton.setEnabled(True) |
972 self.stopButton.setEnabled(True) |
472 self.startButton.setEnabled(False) |
973 self.startButton.setEnabled(False) |
|
974 self.startFailedButton.setEnabled(False) |
473 self.stopButton.setDefault(True) |
975 self.stopButton.setDefault(True) |
|
976 |
474 self.sbLabel.setText(self.tr("Running")) |
977 self.sbLabel.setText(self.tr("Running")) |
475 self.progressLed.on() |
978 self.progressLed.on() |
476 QApplication.processEvents() |
979 QApplication.processEvents() |
477 |
980 |
478 self.startTime = time.time() |
981 self.startTime = time.time() |
479 |
982 |
480 def __setStoppedMode(self): |
983 def __setStoppedMode(self): |
481 """ |
984 """ |
482 Private method to set the GUI in stopped mode. |
985 Private method to set the GUI in stopped mode. |
483 """ |
986 """ |
484 self.stopTime = time.time() |
987 self.stopTime = time.time() |
485 self.timeTaken = float(self.stopTime - self.startTime) |
988 self.timeTaken = float(self.stopTime - self.startTime) |
486 self.running = False |
989 self.running = False |
487 |
990 |
|
991 failedAvailable = bool(self.__failedTests) |
488 self.startButton.setEnabled(True) |
992 self.startButton.setEnabled(True) |
489 self.startFailedButton.setEnabled(bool(self.__failedTests)) |
993 self.startFailedButton.setEnabled(failedAvailable) |
490 self.stopButton.setEnabled(False) |
994 self.stopButton.setEnabled(False) |
491 if self.__failedTests: |
995 if failedAvailable: |
492 self.startFailedButton.setDefault(True) |
996 self.startFailedButton.setDefault(True) |
493 self.startButton.setDefault(False) |
997 self.startButton.setDefault(False) |
494 else: |
998 else: |
495 self.startFailedButton.setDefault(False) |
999 self.startFailedButton.setDefault(False) |
496 self.startButton.setDefault(True) |
1000 self.startButton.setDefault(True) |
497 if self.runCount == 1: |
1001 self.sbLabel.setText( |
498 self.sbLabel.setText( |
1002 self.tr("Ran %n test(s) in {0:.3f}s", "", self.runCount) |
499 self.tr("Ran {0} test in {1:.3f}s") |
1003 .format(self.timeTaken)) |
500 .format(self.runCount, self.timeTaken)) |
|
501 else: |
|
502 self.sbLabel.setText( |
|
503 self.tr("Ran {0} tests in {1:.3f}s") |
|
504 .format(self.runCount, self.timeTaken)) |
|
505 self.progressLed.off() |
1004 self.progressLed.off() |
506 |
1005 |
507 self.unittestStopped.emit() |
1006 self.unittestStopped.emit() |
508 |
1007 |
|
1008 self.raise_() |
|
1009 self.activateWindow() |
|
1010 |
509 def testFailed(self, test, exc, testId): |
1011 def testFailed(self, test, exc, testId): |
510 """ |
1012 """ |
511 Public method called if a test fails. |
1013 Public method called if a test fails. |
512 |
1014 |
513 @param test name of the test (string) |
1015 @param test name of the test (string) |
718 """ |
1220 """ |
719 A TestResult derivative to work with a graphical GUI. |
1221 A TestResult derivative to work with a graphical GUI. |
720 |
1222 |
721 For more details see pyunit.py of the standard Python distribution. |
1223 For more details see pyunit.py of the standard Python distribution. |
722 """ |
1224 """ |
723 def __init__(self, parent): |
1225 def __init__(self, parent, failfast): |
724 """ |
1226 """ |
725 Constructor |
1227 Constructor |
726 |
1228 |
727 @param parent The parent widget. |
1229 @param parent reference to the parent widget |
|
1230 @type UnittestDialog |
|
1231 @param failfast flag indicating to stop at the first error |
|
1232 @type bool |
728 """ |
1233 """ |
729 super(QtTestResult, self).__init__() |
1234 super(QtTestResult, self).__init__() |
730 self.parent = parent |
1235 self.parent = parent |
731 |
1236 self.failfast = failfast |
|
1237 |
732 def addFailure(self, test, err): |
1238 def addFailure(self, test, err): |
733 """ |
1239 """ |
734 Public method called if a test failed. |
1240 Public method called if a test failed. |
735 |
1241 |
736 @param test reference to the test object |
1242 @param test reference to the test object |
737 @param err error traceback |
1243 @param err error traceback |
738 """ |
1244 """ |
739 super(QtTestResult, self).addFailure(test, err) |
1245 super(QtTestResult, self).addFailure(test, err) |
740 tracebackLines = self._exc_info_to_string(err, test) |
1246 tracebackLines = self._exc_info_to_string(err, test) |
741 self.parent.testFailed(str(test), tracebackLines, test.id()) |
1247 self.parent.testFailed(str(test), tracebackLines, test.id()) |
742 |
1248 |
743 def addError(self, test, err): |
1249 def addError(self, test, err): |
744 """ |
1250 """ |
745 Public method called if a test errored. |
1251 Public method called if a test errored. |
746 |
1252 |
747 @param test reference to the test object |
1253 @param test reference to the test object |
748 @param err error traceback |
1254 @param err error traceback |
749 """ |
1255 """ |
750 super(QtTestResult, self).addError(test, err) |
1256 super(QtTestResult, self).addError(test, err) |
751 tracebackLines = self._exc_info_to_string(err, test) |
1257 tracebackLines = self._exc_info_to_string(err, test) |
752 self.parent.testErrored(str(test), tracebackLines, test.id()) |
1258 self.parent.testErrored(str(test), tracebackLines, test.id()) |
753 |
1259 |
754 def addSkip(self, test, reason): |
1260 def addSkip(self, test, reason): |
755 """ |
1261 """ |
756 Public method called if a test was skipped. |
1262 Public method called if a test was skipped. |
757 |
1263 |
758 @param test reference to the test object |
1264 @param test reference to the test object |
759 @param reason reason for skipping the test (string) |
1265 @param reason reason for skipping the test (string) |
760 """ |
1266 """ |
761 super(QtTestResult, self).addSkip(test, reason) |
1267 super(QtTestResult, self).addSkip(test, reason) |
762 self.parent.testSkipped(str(test), reason, test.id()) |
1268 self.parent.testSkipped(str(test), reason, test.id()) |
763 |
1269 |
764 def addExpectedFailure(self, test, err): |
1270 def addExpectedFailure(self, test, err): |
765 """ |
1271 """ |
766 Public method called if a test failed expected. |
1272 Public method called if a test failed expected. |
767 |
1273 |
768 @param test reference to the test object |
1274 @param test reference to the test object |
769 @param err error traceback |
1275 @param err error traceback |
770 """ |
1276 """ |
771 super(QtTestResult, self).addExpectedFailure(test, err) |
1277 super(QtTestResult, self).addExpectedFailure(test, err) |
772 tracebackLines = self._exc_info_to_string(err, test) |
1278 tracebackLines = self._exc_info_to_string(err, test) |
773 self.parent.testFailedExpected(str(test), tracebackLines, test.id()) |
1279 self.parent.testFailedExpected(str(test), tracebackLines, test.id()) |
774 |
1280 |
775 def addUnexpectedSuccess(self, test): |
1281 def addUnexpectedSuccess(self, test): |
776 """ |
1282 """ |
777 Public method called if a test succeeded expectedly. |
1283 Public method called if a test succeeded expectedly. |
778 |
1284 |
779 @param test reference to the test object |
1285 @param test reference to the test object |
780 """ |
1286 """ |
781 super(QtTestResult, self).addUnexpectedSuccess(test) |
1287 super(QtTestResult, self).addUnexpectedSuccess(test) |
782 self.parent.testSucceededUnexpected(str(test), test.id()) |
1288 self.parent.testSucceededUnexpected(str(test), test.id()) |
783 |
1289 |
784 def startTest(self, test): |
1290 def startTest(self, test): |
785 """ |
1291 """ |
786 Public method called at the start of a test. |
1292 Public method called at the start of a test. |
787 |
1293 |
788 @param test Reference to the test object |
1294 @param test Reference to the test object |