eric7/PyUnit/UnittestDialog.py

branch
eric7-maintenance
changeset 9131
bc0c1b6d6adb
parent 9112
9967ae9f0906
child 9136
9e0cf68f727b
equal deleted inserted replaced
9112:9967ae9f0906 9131:bc0c1b6d6adb
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the UI to the pyunit package.
8 """
9
10 import unittest
11 import sys
12 import time
13 import re
14 import os
15 import contextlib
16
17 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QFileInfo
18 from PyQt6.QtGui import QColor
19 from PyQt6.QtWidgets import (
20 QWidget, QDialog, QApplication, QDialogButtonBox, QListWidgetItem,
21 QComboBox, QTreeWidgetItem
22 )
23
24 from EricWidgets.EricApplication import ericApp
25 from EricWidgets import EricMessageBox
26 from EricWidgets.EricMainWindow import EricMainWindow
27 from EricWidgets.EricPathPicker import EricPathPickerModes
28
29 from .Ui_UnittestDialog import Ui_UnittestDialog
30
31 import UI.PixmapCache
32
33 import Preferences
34
35 from Globals import (
36 recentNameUnittestDiscoverHistory, recentNameUnittestFileHistory,
37 recentNameUnittestTestnameHistory
38 )
39
40
41 class UnittestDialog(QWidget, Ui_UnittestDialog):
42 """
43 Class implementing the UI to the pyunit package.
44
45 @signal unittestFile(str, int, bool) emitted to show the source of a
46 unittest file
47 @signal unittestStopped() emitted after a unit test was run
48 """
49 unittestFile = pyqtSignal(str, int, bool)
50 unittestStopped = pyqtSignal()
51
52 TestCaseNameRole = Qt.ItemDataRole.UserRole
53 TestCaseFileRole = Qt.ItemDataRole.UserRole + 1
54
55 ErrorsInfoRole = Qt.ItemDataRole.UserRole
56
57 SkippedColorDarkTheme = QColor("#00aaff")
58 FailedExpectedColorDarkTheme = QColor("#ccaaff")
59 SucceededUnexpectedColorDarkTheme = QColor("#ff99dd")
60 SkippedColorLightTheme = QColor("#0000ff")
61 FailedExpectedColorLightTheme = QColor("#7700bb")
62 SucceededUnexpectedColorLightTheme = QColor("#ff0000")
63
64 def __init__(self, prog=None, dbs=None, ui=None, parent=None, name=None):
65 """
66 Constructor
67
68 @param prog filename of the program to open
69 @type str
70 @param dbs reference to the debug server object. It is an indication
71 whether we were called from within the eric IDE.
72 @type DebugServer
73 @param ui reference to the UI object
74 @type UserInterface
75 @param parent parent widget of this dialog
76 @type QWidget
77 @param name name of this dialog
78 @type str
79 """
80 super().__init__(parent)
81 if name:
82 self.setObjectName(name)
83 self.setupUi(self)
84
85 self.clearHistoriesButton.setIcon(
86 UI.PixmapCache.getIcon("clearPrivateData"))
87
88 self.testsuitePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
89 self.testsuitePicker.setInsertPolicy(
90 QComboBox.InsertPolicy.InsertAtTop)
91 self.testsuitePicker.setSizeAdjustPolicy(
92 QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
93
94 self.discoveryPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
95 self.discoveryPicker.setInsertPolicy(
96 QComboBox.InsertPolicy.InsertAtTop)
97 self.discoveryPicker.setSizeAdjustPolicy(
98 QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
99
100 self.testComboBox.lineEdit().setClearButtonEnabled(True)
101
102 self.discoverButton = self.buttonBox.addButton(
103 self.tr("Discover"), QDialogButtonBox.ButtonRole.ActionRole)
104 self.discoverButton.setToolTip(self.tr(
105 "Discover tests"))
106 self.discoverButton.setWhatsThis(self.tr(
107 """<b>Discover</b>"""
108 """<p>This button starts a discovery of available tests.</p>"""))
109 self.startButton = self.buttonBox.addButton(
110 self.tr("Start"), QDialogButtonBox.ButtonRole.ActionRole)
111
112 self.startButton.setToolTip(self.tr(
113 "Start the selected testsuite"))
114 self.startButton.setWhatsThis(self.tr(
115 """<b>Start Test</b>"""
116 """<p>This button starts the selected testsuite.</p>"""))
117
118 self.startFailedButton = self.buttonBox.addButton(
119 self.tr("Rerun Failed"), QDialogButtonBox.ButtonRole.ActionRole)
120 self.startFailedButton.setToolTip(
121 self.tr("Reruns failed tests of the selected testsuite"))
122 self.startFailedButton.setWhatsThis(self.tr(
123 """<b>Rerun Failed</b>"""
124 """<p>This button reruns all failed tests of the selected"""
125 """ testsuite.</p>"""))
126
127 self.stopButton = self.buttonBox.addButton(
128 self.tr("Stop"), QDialogButtonBox.ButtonRole.ActionRole)
129 self.stopButton.setToolTip(self.tr("Stop the running unittest"))
130 self.stopButton.setWhatsThis(self.tr(
131 """<b>Stop Test</b>"""
132 """<p>This button stops a running unittest.</p>"""))
133
134 self.discoverButton.setEnabled(False)
135 self.stopButton.setEnabled(False)
136 self.startButton.setDefault(True)
137 self.startFailedButton.setEnabled(False)
138
139 self.__dbs = dbs
140 self.__forProject = False
141
142 self.setWindowFlags(
143 self.windowFlags() |
144 Qt.WindowType.WindowContextHelpButtonHint
145 )
146 self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
147 self.setWindowTitle(self.tr("Unittest"))
148 if dbs:
149 self.ui = ui
150
151 self.debuggerCheckBox.setChecked(True)
152
153 # virtual environment manager is only used in the integrated
154 # variant
155 self.__venvManager = ericApp().getObject("VirtualEnvManager")
156 self.__populateVenvComboBox()
157 self.__venvManager.virtualEnvironmentAdded.connect(
158 self.__populateVenvComboBox)
159 self.__venvManager.virtualEnvironmentRemoved.connect(
160 self.__populateVenvComboBox)
161 self.__venvManager.virtualEnvironmentChanged.connect(
162 self.__populateVenvComboBox)
163 else:
164 self.__venvManager = None
165 self.debuggerCheckBox.setVisible(False)
166 self.venvComboBox.setVisible(bool(self.__venvManager))
167 self.venvLabel.setVisible(bool(self.__venvManager))
168
169 self.__setProgressColor("green")
170 self.progressLed.setDarkFactor(150)
171 self.progressLed.off()
172
173 self.discoverHistory = []
174 self.fileHistory = []
175 self.testNameHistory = []
176 self.running = False
177 self.savedModulelist = None
178 self.savedSysPath = sys.path
179 self.savedCwd = os.getcwd()
180
181 self.rxPatterns = [
182 self.tr("^Failure: "),
183 self.tr("^Error: "),
184 # These are for untranslated/partially translated situations
185 "^Failure: ",
186 "^Error: ",
187 ]
188
189 self.__failedTests = set()
190
191 # now connect the debug server signals if called from the eric IDE
192 if self.__dbs:
193 self.__dbs.utDiscovered.connect(self.__UTDiscovered)
194 self.__dbs.utPrepared.connect(self.__UTPrepared)
195 self.__dbs.utFinished.connect(self.__setStoppedMode)
196 self.__dbs.utStartTest.connect(self.testStarted)
197 self.__dbs.utStopTest.connect(self.testFinished)
198 self.__dbs.utTestFailed.connect(self.testFailed)
199 self.__dbs.utTestErrored.connect(self.testErrored)
200 self.__dbs.utTestSkipped.connect(self.testSkipped)
201 self.__dbs.utTestFailedExpected.connect(self.testFailedExpected)
202 self.__dbs.utTestSucceededUnexpected.connect(
203 self.testSucceededUnexpected)
204
205 self.__editors = []
206
207 self.__loadRecent()
208
209 self.insertProg(prog)
210 self.insertTestName("")
211
212 self.clearHistoriesButton.clicked.connect(self.clearRecent)
213
214 def keyPressEvent(self, evt):
215 """
216 Protected slot to handle key press events.
217
218 @param evt key press event to handle (QKeyEvent)
219 """
220 if evt.key() == Qt.Key.Key_Escape and self.__dbs:
221 self.close()
222
223 def __populateVenvComboBox(self):
224 """
225 Private method to (re-)populate the virtual environments selector.
226 """
227 currentText = self.venvComboBox.currentText()
228 self.venvComboBox.clear()
229 self.venvComboBox.addItem("")
230 self.venvComboBox.addItems(
231 sorted(self.__venvManager.getVirtualenvNames()))
232 index = self.venvComboBox.findText(currentText)
233 if index < 0:
234 index = 0
235 self.venvComboBox.setCurrentIndex(index)
236
237 def __setProgressColor(self, color):
238 """
239 Private method to set the color of the progress color label.
240
241 @param color colour to be shown (string)
242 """
243 self.progressLed.setColor(QColor(color))
244
245 def setProjectMode(self, forProject):
246 """
247 Public method to set the project mode of the dialog.
248
249 @param forProject flag indicating to run for the open project
250 @type bool
251 """
252 self.__forProject = forProject
253 if forProject:
254 project = ericApp().getObject("Project")
255 if project.isOpen():
256 self.insertDiscovery(project.getProjectPath())
257 else:
258 self.insertDiscovery("")
259 else:
260 self.insertDiscovery("")
261
262 self.discoveryList.clear()
263 self.tabWidget.setCurrentIndex(0)
264
265 def insertDiscovery(self, start):
266 """
267 Public slot to insert the discovery start directory into the
268 discoveryPicker object.
269
270 @param start start directory name to be inserted
271 @type str
272 """
273 current = self.discoveryPicker.currentText()
274
275 # prepend the given directory to the discovery picker
276 if start is None:
277 start = ""
278 if start in self.discoverHistory:
279 self.discoverHistory.remove(start)
280 self.discoverHistory.insert(0, start)
281 self.discoveryPicker.clear()
282 self.discoveryPicker.addItems(self.discoverHistory)
283
284 if current:
285 self.discoveryPicker.setText(current)
286
287 def insertProg(self, prog):
288 """
289 Public slot to insert the filename prog into the testsuitePicker
290 object.
291
292 @param prog filename to be inserted (string)
293 """
294 current = self.testsuitePicker.currentText()
295
296 # prepend the selected file to the testsuite picker
297 if prog is None:
298 prog = ""
299 if prog in self.fileHistory:
300 self.fileHistory.remove(prog)
301 self.fileHistory.insert(0, prog)
302 self.testsuitePicker.clear()
303 self.testsuitePicker.addItems(self.fileHistory)
304
305 if current:
306 self.testsuitePicker.setText(current)
307
308 def insertTestName(self, testName):
309 """
310 Public slot to insert a test name into the testComboBox object.
311
312 @param testName name of the test to be inserted (string)
313 """
314 current = self.testComboBox.currentText()
315
316 # prepend the selected file to the testsuite combobox
317 if testName is None:
318 testName = ""
319 if testName in self.testNameHistory:
320 self.testNameHistory.remove(testName)
321 self.testNameHistory.insert(0, testName)
322 self.testComboBox.clear()
323 self.testComboBox.addItems(self.testNameHistory)
324
325 if current:
326 self.testComboBox.setCurrentText(current)
327
328 @pyqtSlot()
329 def on_testsuitePicker_aboutToShowPathPickerDialog(self):
330 """
331 Private slot called before the test suite selection dialog is shown.
332 """
333 if self.__dbs:
334 py3Extensions = ' '.join(
335 ["*{0}".format(ext)
336 for ext in self.__dbs.getExtensions('Python3')]
337 )
338 fileFilter = self.tr(
339 "Python3 Files ({0});;All Files (*)"
340 ).format(py3Extensions)
341 else:
342 fileFilter = self.tr("Python Files (*.py);;All Files (*)")
343 self.testsuitePicker.setFilters(fileFilter)
344
345 defaultDirectory = Preferences.getMultiProject("Workspace")
346 if not defaultDirectory:
347 defaultDirectory = os.path.expanduser("~")
348 if self.__dbs:
349 project = ericApp().getObject("Project")
350 if self.__forProject and project.isOpen():
351 defaultDirectory = project.getProjectPath()
352 self.testsuitePicker.setDefaultDirectory(defaultDirectory)
353
354 @pyqtSlot(str)
355 def on_testsuitePicker_pathSelected(self, suite):
356 """
357 Private slot called after a test suite has been selected.
358
359 @param suite file name of the test suite
360 @type str
361 """
362 self.insertProg(suite)
363
364 @pyqtSlot(str)
365 def on_testsuitePicker_editTextChanged(self, path):
366 """
367 Private slot handling changes of the test suite path.
368
369 @param path path of the test suite file
370 @type str
371 """
372 self.startFailedButton.setEnabled(False)
373
374 @pyqtSlot(bool)
375 def on_discoverCheckBox_toggled(self, checked):
376 """
377 Private slot handling state changes of the 'discover' checkbox.
378
379 @param checked state of the checkbox
380 @type bool
381 """
382 self.discoverButton.setEnabled(checked)
383 self.discoveryList.clear()
384
385 if not bool(self.discoveryPicker.currentText()):
386 if self.__forProject:
387 project = ericApp().getObject("Project")
388 if project.isOpen():
389 self.insertDiscovery(project.getProjectPath())
390 return
391
392 self.insertDiscovery(Preferences.getMultiProject("Workspace"))
393
394 def on_buttonBox_clicked(self, button):
395 """
396 Private slot called by a button of the button box clicked.
397
398 @param button button that was clicked (QAbstractButton)
399 """
400 if button == self.discoverButton:
401 self.__discover()
402 self.__saveRecent()
403 elif button == self.startButton:
404 self.startTests()
405 self.__saveRecent()
406 elif button == self.stopButton:
407 self.__stopTests()
408 elif button == self.startFailedButton:
409 self.startTests(failedOnly=True)
410
411 def __loadRecent(self):
412 """
413 Private method to load the most recently used lists.
414 """
415 Preferences.Prefs.rsettings.sync()
416
417 # 1. discovery history
418 self.discoverHistory = []
419 rs = Preferences.Prefs.rsettings.value(
420 recentNameUnittestDiscoverHistory)
421 if rs is not None:
422 recent = [f
423 for f in Preferences.toList(rs)
424 if QFileInfo(f).exists()]
425 self.discoverHistory = recent[
426 :Preferences.getDebugger("RecentNumber")]
427
428 # 2. test file history
429 self.fileHistory = []
430 rs = Preferences.Prefs.rsettings.value(
431 recentNameUnittestFileHistory)
432 if rs is not None:
433 recent = [f
434 for f in Preferences.toList(rs)
435 if QFileInfo(f).exists()]
436 self.fileHistory = recent[
437 :Preferences.getDebugger("RecentNumber")]
438
439 # 3. test name history
440 self.testNameHistory = []
441 rs = Preferences.Prefs.rsettings.value(
442 recentNameUnittestTestnameHistory)
443 if rs is not None:
444 recent = [n for n in Preferences.toList(rs) if n]
445 self.testNameHistory = recent[
446 :Preferences.getDebugger("RecentNumber")]
447
448 def __saveRecent(self):
449 """
450 Private method to save the most recently used lists.
451 """
452 Preferences.Prefs.rsettings.setValue(
453 recentNameUnittestDiscoverHistory, self.discoverHistory)
454 Preferences.Prefs.rsettings.setValue(
455 recentNameUnittestFileHistory, self.fileHistory)
456 Preferences.Prefs.rsettings.setValue(
457 recentNameUnittestTestnameHistory, self.testNameHistory)
458
459 Preferences.Prefs.rsettings.sync()
460
461 @pyqtSlot()
462 def clearRecent(self):
463 """
464 Public slot to clear the recently used lists.
465 """
466 # clear histories
467 self.discoverHistory = []
468 self.fileHistory = []
469 self.testNameHistory = []
470
471 # clear widgets with histories
472 self.discoveryPicker.clear()
473 self.testsuitePicker.clear()
474 self.testComboBox.clear()
475
476 # sync histories
477 self.__saveRecent()
478
479 @pyqtSlot()
480 def __discover(self):
481 """
482 Private slot to discover unit test but don't run them.
483 """
484 if self.running:
485 return
486
487 self.discoveryList.clear()
488
489 discoveryStart = self.discoveryPicker.currentText()
490 self.insertDiscovery(discoveryStart)
491 self.sbLabel.setText(self.tr("Discovering Tests"))
492 QApplication.processEvents()
493
494 self.testName = self.tr("Unittest with auto-discovery")
495 if self.__dbs:
496 venvName = self.venvComboBox.currentText()
497
498 # we are cooperating with the eric IDE
499 project = ericApp().getObject("Project")
500 if self.__forProject:
501 mainScript = project.getMainScript(True)
502 clientType = project.getProjectLanguage()
503 if mainScript:
504 workdir = os.path.dirname(os.path.abspath(mainScript))
505 else:
506 workdir = project.getProjectPath()
507 sysPath = [workdir]
508 if not discoveryStart:
509 discoveryStart = workdir
510 else:
511 if not discoveryStart:
512 EricMessageBox.critical(
513 self,
514 self.tr("Unittest"),
515 self.tr("You must enter a start directory for"
516 " auto-discovery."))
517 return
518
519 workdir = ""
520 clientType = "Python3"
521 sysPath = []
522 self.__dbs.remoteUTDiscover(clientType, self.__forProject,
523 venvName, sysPath, workdir,
524 discoveryStart)
525 else:
526 # we are running as an application
527 if not discoveryStart:
528 EricMessageBox.critical(
529 self,
530 self.tr("Unittest"),
531 self.tr("You must enter a start directory for"
532 " auto-discovery."))
533 return
534
535 if discoveryStart:
536 sys.path = (
537 [os.path.abspath(discoveryStart)] +
538 self.savedSysPath
539 )
540
541 # clean up list of imported modules to force a reimport upon
542 # running the test
543 if self.savedModulelist:
544 for modname in list(sys.modules.keys()):
545 if modname not in self.savedModulelist:
546 # delete it
547 del(sys.modules[modname])
548 self.savedModulelist = sys.modules.copy()
549
550 # now try to discover the testsuite
551 os.chdir(discoveryStart)
552 try:
553 testLoader = unittest.TestLoader()
554 test = testLoader.discover(discoveryStart)
555 if hasattr(testLoader, "errors") and bool(testLoader.errors):
556 EricMessageBox.critical(
557 self,
558 self.tr("Unittest"),
559 self.tr(
560 "<p>Unable to discover tests.</p>"
561 "<p>{0}</p>"
562 ).format("<br/>".join(testLoader.errors)
563 .replace("\n", "<br/>"))
564 )
565 self.sbLabel.clear()
566 else:
567 testsList = self.__assembleTestCasesList(
568 test, discoveryStart)
569 self.__populateDiscoveryResults(testsList)
570 self.sbLabel.setText(
571 self.tr("Discovered %n Test(s)", "",
572 len(testsList)))
573 self.tabWidget.setCurrentIndex(0)
574 except Exception:
575 exc_type, exc_value, exc_tb = sys.exc_info()
576 EricMessageBox.critical(
577 self,
578 self.tr("Unittest"),
579 self.tr(
580 "<p>Unable to discover tests.</p>"
581 "<p>{0}<br/>{1}</p>")
582 .format(str(exc_type),
583 str(exc_value).replace("\n", "<br/>"))
584 )
585 self.sbLabel.clear()
586
587 sys.path = self.savedSysPath
588
589 def __assembleTestCasesList(self, suite, start):
590 """
591 Private method to assemble a list of test cases included in a test
592 suite.
593
594 @param suite test suite to be inspected
595 @type unittest.TestSuite
596 @param start name of directory discovery was started at
597 @type str
598 @return list of tuples containing the test case ID, a short description
599 and the path of the test file name
600 @rtype list of tuples of (str, str, str)
601 """
602 testCases = []
603 for test in suite:
604 if isinstance(test, unittest.TestSuite):
605 testCases.extend(self.__assembleTestCasesList(test, start))
606 else:
607 testId = test.id()
608 if (
609 "ModuleImportFailure" not in testId and
610 "LoadTestsFailure" not in testId and
611 "_FailedTest" not in testId
612 ):
613 filename = os.path.join(
614 start,
615 test.__module__.replace(".", os.sep) + ".py")
616 testCases.append(
617 (test.id(), test.shortDescription(), filename)
618 )
619 return testCases
620
621 def __findDiscoveryItem(self, modulePath):
622 """
623 Private method to find an item given the module path.
624
625 @param modulePath path of the module in dotted notation
626 @type str
627 @return reference to the item or None
628 @rtype QTreeWidgetItem or None
629 """
630 itm = self.discoveryList.topLevelItem(0)
631 while itm is not None:
632 if itm.data(0, UnittestDialog.TestCaseNameRole) == modulePath:
633 return itm
634 itm = self.discoveryList.itemBelow(itm)
635
636 return None
637
638 def __populateDiscoveryResults(self, tests):
639 """
640 Private method to populate the test discovery results list.
641
642 @param tests list of tuples containing the discovery results
643 @type list of tuples of (str, str, str)
644 """
645 for test, _testDescription, filename in tests:
646 testPath = test.split(".")
647 pitm = None
648 for index in range(1, len(testPath) + 1):
649 modulePath = ".".join(testPath[:index])
650 itm = self.__findDiscoveryItem(modulePath)
651 if itm is not None:
652 pitm = itm
653 else:
654 if pitm is None:
655 itm = QTreeWidgetItem(self.discoveryList,
656 [testPath[index - 1]])
657 else:
658 itm = QTreeWidgetItem(pitm,
659 [testPath[index - 1]])
660 pitm.setExpanded(True)
661 itm.setFlags(Qt.ItemFlag.ItemIsUserCheckable |
662 Qt.ItemFlag.ItemIsEnabled)
663 itm.setCheckState(0, Qt.CheckState.Unchecked)
664 itm.setData(0, UnittestDialog.TestCaseNameRole, modulePath)
665 if (
666 os.path.splitext(os.path.basename(filename))[0] ==
667 itm.text(0)
668 ):
669 itm.setData(0, UnittestDialog.TestCaseFileRole,
670 filename)
671 elif pitm:
672 fn = pitm.data(0, UnittestDialog.TestCaseFileRole)
673 if fn:
674 itm.setData(0, UnittestDialog.TestCaseFileRole, fn)
675 pitm = itm
676
677 def __selectedTestCases(self, parent=None):
678 """
679 Private method to assemble the list of selected test cases and suites.
680
681 @param parent reference to the parent item
682 @type QTreeWidgetItem
683 @return list of selected test cases
684 @rtype list of str
685 """
686 selectedTests = []
687 itemsList = [
688 self.discoveryList.topLevelItem(index)
689 for index in range(self.discoveryList.topLevelItemCount())
690 ] if parent is None else [
691 parent.child(index)
692 for index in range(parent.childCount())
693 ]
694
695 for itm in itemsList:
696 if (
697 itm.checkState(0) == Qt.CheckState.Checked and
698 itm.childCount() == 0
699 ):
700 selectedTests.append(
701 itm.data(0, UnittestDialog.TestCaseNameRole))
702 if itm.childCount():
703 # recursively check children
704 selectedTests.extend(self.__selectedTestCases(itm))
705
706 return selectedTests
707
708 def __UTDiscovered(self, testCases, exc_type, exc_value):
709 """
710 Private slot to handle the utDiscovered signal.
711
712 If the unittest suite was loaded successfully, we ask the
713 client to run the test suite.
714
715 @param testCases list of detected test cases
716 @type str
717 @param exc_type exception type occured during discovery
718 @type str
719 @param exc_value value of exception occured during discovery
720 @type str
721 """
722 if testCases:
723 self.__populateDiscoveryResults(testCases)
724 self.sbLabel.setText(
725 self.tr("Discovered %n Test(s)", "",
726 len(testCases)))
727 self.tabWidget.setCurrentIndex(0)
728 else:
729 EricMessageBox.critical(
730 self,
731 self.tr("Unittest"),
732 self.tr("<p>Unable to discover tests.</p>"
733 "<p>{0}<br/>{1}</p>")
734 .format(exc_type, exc_value.replace("\n", "<br/>"))
735 )
736
737 @pyqtSlot(QTreeWidgetItem, int)
738 def on_discoveryList_itemChanged(self, item, column):
739 """
740 Private slot handling the user checking or unchecking an item.
741
742 @param item reference to the item
743 @type QTreeWidgetItem
744 @param column changed column
745 @type int
746 """
747 if column == 0:
748 for index in range(item.childCount()):
749 item.child(index).setCheckState(0, item.checkState(0))
750
751 @pyqtSlot(QTreeWidgetItem, int)
752 def on_discoveryList_itemDoubleClicked(self, item, column):
753 """
754 Private slot handling the user double clicking an item.
755
756 @param item reference to the item
757 @type QTreeWidgetItem
758 @param column column of the double click
759 @type int
760 """
761 if item:
762 filename = item.data(0, UnittestDialog.TestCaseFileRole)
763 if filename:
764 if self.__dbs:
765 # running as part of eric IDE
766 self.unittestFile.emit(filename, 1, False)
767 else:
768 self.__openEditor(filename, 1)
769
770 @pyqtSlot()
771 def startTests(self, failedOnly=False):
772 """
773 Public slot to start the test.
774
775 @param failedOnly flag indicating to run only failed tests (boolean)
776 """
777 if self.running:
778 return
779
780 discover = self.discoverCheckBox.isChecked()
781 if discover:
782 discoveryStart = self.discoveryPicker.currentText()
783 testFileName = ""
784 testName = ""
785
786 if discoveryStart:
787 self.insertDiscovery(discoveryStart)
788 else:
789 discoveryStart = ""
790 testFileName = self.testsuitePicker.currentText()
791 testName = self.testComboBox.currentText()
792 if testName:
793 self.insertTestName(testName)
794 if testFileName and not testName:
795 testName = "suite"
796
797 if not discover and not testFileName and not testName:
798 EricMessageBox.critical(
799 self,
800 self.tr("Unittest"),
801 self.tr("You must select auto-discovery or enter a test suite"
802 " file or a dotted test name."))
803 return
804
805 # prepend the selected file to the testsuite combobox
806 self.insertProg(testFileName)
807 self.sbLabel.setText(self.tr("Preparing Testsuite"))
808 QApplication.processEvents()
809
810 if discover:
811 self.testName = self.tr("Unittest with auto-discovery")
812 else:
813 # build the module name from the filename without extension
814 if testFileName:
815 self.testName = os.path.splitext(
816 os.path.basename(testFileName))[0]
817 elif testName:
818 self.testName = testName
819 else:
820 self.testName = self.tr("<Unnamed Test>")
821
822 if failedOnly:
823 testCases = []
824 else:
825 testCases = self.__selectedTestCases()
826
827 if not testCases and self.discoveryList.topLevelItemCount():
828 ok = EricMessageBox.yesNo(
829 self,
830 self.tr("Unittest"),
831 self.tr("""No test case has been selected. Shall all"""
832 """ test cases be run?"""))
833 if not ok:
834 return
835
836 if self.__dbs:
837 venvName = self.venvComboBox.currentText()
838
839 # we are cooperating with the eric IDE
840 project = ericApp().getObject("Project")
841 if self.__forProject:
842 mainScript = project.getMainScript(True)
843 clientType = project.getProjectLanguage()
844 if mainScript:
845 workdir = os.path.dirname(os.path.abspath(mainScript))
846 coverageFile = os.path.splitext(mainScript)[0]
847 else:
848 workdir = project.getProjectPath()
849 coverageFile = os.path.join(discoveryStart, "unittest")
850 sysPath = [workdir]
851 if discover and not discoveryStart:
852 discoveryStart = workdir
853 else:
854 if discover:
855 if not discoveryStart:
856 EricMessageBox.critical(
857 self,
858 self.tr("Unittest"),
859 self.tr("You must enter a start directory for"
860 " auto-discovery."))
861 return
862
863 coverageFile = os.path.join(discoveryStart, "unittest")
864 workdir = ""
865 clientType = "Python3"
866 elif testFileName:
867 mainScript = os.path.abspath(testFileName)
868 workdir = os.path.dirname(mainScript)
869 clientType = "Python3"
870 coverageFile = os.path.splitext(mainScript)[0]
871 else:
872 coverageFile = os.path.abspath("unittest")
873 workdir = ""
874 clientType = "Python3"
875 sysPath = []
876 if failedOnly and self.__failedTests:
877 failed = list(self.__failedTests)
878 if discover:
879 workdir = discoveryStart
880 discover = False
881 else:
882 failed = []
883 self.__failedTests = set()
884 self.__dbs.remoteUTPrepare(
885 testFileName, self.testName, testName, failed,
886 self.coverageCheckBox.isChecked(), coverageFile,
887 self.coverageEraseCheckBox.isChecked(), clientType=clientType,
888 forProject=self.__forProject, workdir=workdir,
889 venvName=venvName, syspath=sysPath,
890 discover=discover, discoveryStart=discoveryStart,
891 testCases=testCases, debug=self.debuggerCheckBox.isChecked())
892 else:
893 # we are running as an application
894 if discover and not discoveryStart:
895 EricMessageBox.critical(
896 self,
897 self.tr("Unittest"),
898 self.tr("You must enter a start directory for"
899 " auto-discovery."))
900 return
901
902 if testFileName:
903 sys.path = (
904 [os.path.dirname(os.path.abspath(testFileName))] +
905 self.savedSysPath
906 )
907 elif discoveryStart:
908 sys.path = (
909 [os.path.abspath(discoveryStart)] +
910 self.savedSysPath
911 )
912
913 # clean up list of imported modules to force a reimport upon
914 # running the test
915 if self.savedModulelist:
916 for modname in list(sys.modules.keys()):
917 if modname not in self.savedModulelist:
918 # delete it
919 del(sys.modules[modname])
920 self.savedModulelist = sys.modules.copy()
921
922 os.chdir(self.savedCwd)
923
924 # now try to generate the testsuite
925 try:
926 testLoader = unittest.TestLoader()
927 if failedOnly and self.__failedTests:
928 failed = list(self.__failedTests)
929 if discover:
930 os.chdir(discoveryStart)
931 discover = False
932 else:
933 failed = []
934 if discover:
935 if testCases:
936 test = testLoader.loadTestsFromNames(testCases)
937 else:
938 test = testLoader.discover(discoveryStart)
939 else:
940 if testFileName:
941 module = __import__(self.testName)
942 else:
943 module = None
944 if failedOnly and self.__failedTests:
945 if module:
946 failed = [t.split(".", 1)[1]
947 for t in self.__failedTests]
948 else:
949 failed = list(self.__failedTests)
950 test = testLoader.loadTestsFromNames(
951 failed, module)
952 else:
953 test = testLoader.loadTestsFromName(
954 testName, module)
955 except Exception:
956 exc_type, exc_value, exc_tb = sys.exc_info()
957 EricMessageBox.critical(
958 self,
959 self.tr("Unittest"),
960 self.tr(
961 "<p>Unable to run test <b>{0}</b>.</p>"
962 "<p>{1}<br/>{2}</p>")
963 .format(self.testName, str(exc_type),
964 str(exc_value).replace("\n", "<br/>"))
965 )
966 return
967
968 # now set up the coverage stuff
969 if self.coverageCheckBox.isChecked():
970 if discover:
971 covname = os.path.join(discoveryStart, "unittest")
972 elif testFileName:
973 covname = os.path.splitext(
974 os.path.abspath(testFileName))[0]
975 else:
976 covname = "unittest"
977
978 from DebugClients.Python.coverage import coverage
979 cover = coverage(data_file="{0}.coverage".format(covname))
980 if self.coverageEraseCheckBox.isChecked():
981 cover.erase()
982 else:
983 cover = None
984
985 self.testResult = QtTestResult(
986 self, self.failfastCheckBox.isChecked())
987 self.totalTests = test.countTestCases()
988 if self.totalTests == 0:
989 EricMessageBox.warning(
990 self,
991 self.tr("Unittest"),
992 self.tr("""No unittest were found. Aborting..."""))
993 else:
994 self.__failedTests = set()
995 self.__setRunningMode()
996 if cover:
997 cover.start()
998 test.run(self.testResult)
999 if cover:
1000 cover.stop()
1001 cover.save()
1002 self.__setStoppedMode()
1003 sys.path = self.savedSysPath
1004
1005 def __UTPrepared(self, nrTests, exc_type, exc_value):
1006 """
1007 Private slot to handle the utPrepared signal.
1008
1009 If the unittest suite was loaded successfully, we ask the
1010 client to run the test suite.
1011
1012 @param nrTests number of tests contained in the test suite (integer)
1013 @param exc_type type of exception occured during preparation (string)
1014 @param exc_value value of exception occured during preparation (string)
1015 """
1016 if nrTests == 0:
1017 EricMessageBox.critical(
1018 self,
1019 self.tr("Unittest"),
1020 self.tr(
1021 "<p>Unable to run test <b>{0}</b>.</p>"
1022 "<p>{1}<br/>{2}</p>")
1023 .format(self.testName, exc_type,
1024 exc_value.replace("\n", "<br/>"))
1025 )
1026 return
1027
1028 self.totalTests = nrTests
1029 self.__setRunningMode()
1030 self.__dbs.remoteUTRun(debug=self.debuggerCheckBox.isChecked(),
1031 failfast=self.failfastCheckBox.isChecked())
1032
1033 @pyqtSlot()
1034 def __stopTests(self):
1035 """
1036 Private slot to stop the test.
1037 """
1038 if self.__dbs:
1039 self.__dbs.remoteUTStop()
1040 elif self.testResult:
1041 self.testResult.stop()
1042
1043 def on_errorsListWidget_currentTextChanged(self, text):
1044 """
1045 Private slot to handle the highlighted signal.
1046
1047 @param text current text (string)
1048 """
1049 if text:
1050 for pattern in self.rxPatterns:
1051 text = re.sub(pattern, "", text)
1052
1053 foundItems = self.testsListWidget.findItems(
1054 text, Qt.MatchFlag.MatchExactly)
1055 if len(foundItems) > 0:
1056 itm = foundItems[0]
1057 self.testsListWidget.setCurrentItem(itm)
1058 self.testsListWidget.scrollToItem(itm)
1059
1060 def __setRunningMode(self):
1061 """
1062 Private method to set the GUI in running mode.
1063 """
1064 self.running = True
1065 self.tabWidget.setCurrentIndex(1)
1066
1067 # reset counters and error infos
1068 self.runCount = 0
1069 self.failCount = 0
1070 self.errorCount = 0
1071 self.skippedCount = 0
1072 self.expectedFailureCount = 0
1073 self.unexpectedSuccessCount = 0
1074 self.remainingCount = self.totalTests
1075
1076 # reset the GUI
1077 self.progressCounterRunCount.setText(str(self.runCount))
1078 self.progressCounterRemCount.setText(str(self.remainingCount))
1079 self.progressCounterFailureCount.setText(str(self.failCount))
1080 self.progressCounterErrorCount.setText(str(self.errorCount))
1081 self.progressCounterSkippedCount.setText(str(self.skippedCount))
1082 self.progressCounterExpectedFailureCount.setText(
1083 str(self.expectedFailureCount))
1084 self.progressCounterUnexpectedSuccessCount.setText(
1085 str(self.unexpectedSuccessCount))
1086
1087 self.errorsListWidget.clear()
1088 self.testsListWidget.clear()
1089
1090 self.progressProgressBar.setRange(0, self.totalTests)
1091 self.__setProgressColor("green")
1092 self.progressProgressBar.reset()
1093
1094 self.stopButton.setEnabled(True)
1095 self.startButton.setEnabled(False)
1096 self.startFailedButton.setEnabled(False)
1097 self.stopButton.setDefault(True)
1098
1099 self.sbLabel.setText(self.tr("Running"))
1100 self.progressLed.on()
1101 QApplication.processEvents()
1102
1103 self.startTime = time.time()
1104
1105 def __setStoppedMode(self):
1106 """
1107 Private method to set the GUI in stopped mode.
1108 """
1109 self.stopTime = time.time()
1110 self.timeTaken = float(self.stopTime - self.startTime)
1111 self.running = False
1112
1113 failedAvailable = bool(self.__failedTests)
1114 self.startButton.setEnabled(True)
1115 self.startFailedButton.setEnabled(failedAvailable)
1116 self.stopButton.setEnabled(False)
1117 if failedAvailable:
1118 self.startFailedButton.setDefault(True)
1119 self.startButton.setDefault(False)
1120 else:
1121 self.startFailedButton.setDefault(False)
1122 self.startButton.setDefault(True)
1123 self.sbLabel.setText(
1124 self.tr("Ran %n test(s) in {0:.3f}s", "", self.runCount)
1125 .format(self.timeTaken))
1126 self.progressLed.off()
1127
1128 self.unittestStopped.emit()
1129
1130 self.raise_()
1131 self.activateWindow()
1132
1133 def testFailed(self, test, exc, testId):
1134 """
1135 Public method called if a test fails.
1136
1137 @param test name of the test (string)
1138 @param exc string representation of the exception (string)
1139 @param testId id of the test (string)
1140 """
1141 self.failCount += 1
1142 self.progressCounterFailureCount.setText(str(self.failCount))
1143 itm = QListWidgetItem(self.tr("Failure: {0}").format(test))
1144 itm.setData(UnittestDialog.ErrorsInfoRole, (test, exc))
1145 self.errorsListWidget.insertItem(0, itm)
1146 self.__failedTests.add(testId)
1147
1148 def testErrored(self, test, exc, testId):
1149 """
1150 Public method called if a test errors.
1151
1152 @param test name of the test (string)
1153 @param exc string representation of the exception (string)
1154 @param testId id of the test (string)
1155 """
1156 self.errorCount += 1
1157 self.progressCounterErrorCount.setText(str(self.errorCount))
1158 itm = QListWidgetItem(self.tr("Error: {0}").format(test))
1159 itm.setData(UnittestDialog.ErrorsInfoRole, (test, exc))
1160 self.errorsListWidget.insertItem(0, itm)
1161 self.__failedTests.add(testId)
1162
1163 def testSkipped(self, test, reason, testId):
1164 """
1165 Public method called if a test was skipped.
1166
1167 @param test name of the test (string)
1168 @param reason reason for skipping the test (string)
1169 @param testId id of the test (string)
1170 """
1171 self.skippedCount += 1
1172 self.progressCounterSkippedCount.setText(str(self.skippedCount))
1173 itm = QListWidgetItem(self.tr(" Skipped: {0}").format(reason))
1174 if ericApp().usesDarkPalette():
1175 itm.setForeground(self.SkippedColorDarkTheme)
1176 else:
1177 itm.setForeground(self.SkippedColorLightTheme)
1178 self.testsListWidget.insertItem(1, itm)
1179
1180 def testFailedExpected(self, test, exc, testId):
1181 """
1182 Public method called if a test fails as expected.
1183
1184 @param test name of the test (string)
1185 @param exc string representation of the exception (string)
1186 @param testId id of the test (string)
1187 """
1188 self.expectedFailureCount += 1
1189 self.progressCounterExpectedFailureCount.setText(
1190 str(self.expectedFailureCount))
1191 itm = QListWidgetItem(self.tr(" Expected Failure"))
1192 if ericApp().usesDarkPalette():
1193 itm.setForeground(self.FailedExpectedColorDarkTheme)
1194 else:
1195 itm.setForeground(self.FailedExpectedColorLightTheme)
1196 self.testsListWidget.insertItem(1, itm)
1197
1198 def testSucceededUnexpected(self, test, testId):
1199 """
1200 Public method called if a test succeeds unexpectedly.
1201
1202 @param test name of the test (string)
1203 @param testId id of the test (string)
1204 """
1205 self.unexpectedSuccessCount += 1
1206 self.progressCounterUnexpectedSuccessCount.setText(
1207 str(self.unexpectedSuccessCount))
1208 itm = QListWidgetItem(self.tr(" Unexpected Success"))
1209 if ericApp().usesDarkPalette():
1210 itm.setForeground(self.SucceededUnexpectedColorDarkTheme)
1211 else:
1212 itm.setForeground(self.SucceededUnexpectedColorLightTheme)
1213 self.testsListWidget.insertItem(1, itm)
1214
1215 def testStarted(self, test, doc):
1216 """
1217 Public method called if a test is about to be run.
1218
1219 @param test name of the started test (string)
1220 @param doc documentation of the started test (string)
1221 """
1222 if doc:
1223 self.testsListWidget.insertItem(0, " {0}".format(doc))
1224 self.testsListWidget.insertItem(0, test)
1225 if self.__dbs is None:
1226 QApplication.processEvents()
1227
1228 def testFinished(self):
1229 """
1230 Public method called if a test has finished.
1231
1232 <b>Note</b>: It is also called if it has already failed or errored.
1233 """
1234 # update the counters
1235 self.remainingCount -= 1
1236 self.runCount += 1
1237 self.progressCounterRunCount.setText(str(self.runCount))
1238 self.progressCounterRemCount.setText(str(self.remainingCount))
1239
1240 # update the progressbar
1241 if self.errorCount:
1242 self.__setProgressColor("red")
1243 elif self.failCount:
1244 self.__setProgressColor("orange")
1245 self.progressProgressBar.setValue(self.runCount)
1246
1247 def on_errorsListWidget_itemDoubleClicked(self, lbitem):
1248 """
1249 Private slot called by doubleclicking an errorlist entry.
1250
1251 It will popup a dialog showing the stacktrace.
1252 If called from eric, an additional button is displayed
1253 to show the python source in an eric source viewer (in
1254 erics main window.
1255
1256 @param lbitem the listbox item that was double clicked
1257 """
1258 self.errListIndex = self.errorsListWidget.row(lbitem)
1259 text = lbitem.text()
1260 self.on_errorsListWidget_currentTextChanged(text)
1261
1262 # get the error info
1263 test, tracebackText = lbitem.data(UnittestDialog.ErrorsInfoRole)
1264
1265 # now build the dialog
1266 from .Ui_UnittestStacktraceDialog import Ui_UnittestStacktraceDialog
1267 self.dlg = QDialog(self)
1268 ui = Ui_UnittestStacktraceDialog()
1269 ui.setupUi(self.dlg)
1270 self.dlg.traceback = ui.traceback
1271
1272 ui.showButton = ui.buttonBox.addButton(
1273 self.tr("Show Source"), QDialogButtonBox.ButtonRole.ActionRole)
1274 ui.showButton.clicked.connect(self.__showSource)
1275
1276 ui.buttonBox.button(
1277 QDialogButtonBox.StandardButton.Close).setDefault(True)
1278
1279 self.dlg.setWindowTitle(text)
1280 ui.testLabel.setText(test)
1281 ui.traceback.setPlainText(tracebackText)
1282
1283 # and now fire it up
1284 self.dlg.show()
1285 self.dlg.exec()
1286
1287 def __showSource(self):
1288 """
1289 Private slot to show the source of a traceback in an eric editor.
1290 """
1291 # get the error info
1292 tracebackLines = self.dlg.traceback.toPlainText().splitlines()
1293 # find the last entry matching the pattern
1294 for index in range(len(tracebackLines) - 1, -1, -1):
1295 fmatch = re.search(r'File "(.*?)", line (\d*?),.*',
1296 tracebackLines[index])
1297 if fmatch:
1298 break
1299 if fmatch:
1300 fn, ln = fmatch.group(1, 2)
1301 if self.__dbs:
1302 # running as part of eric IDE
1303 self.unittestFile.emit(fn, int(ln), True)
1304 else:
1305 self.__openEditor(fn, int(ln))
1306
1307 def hasFailedTests(self):
1308 """
1309 Public method to check, if there are failed tests from the last run.
1310
1311 @return flag indicating the presence of failed tests (boolean)
1312 """
1313 return bool(self.__failedTests)
1314
1315 def __openEditor(self, filename, linenumber):
1316 """
1317 Private method to open an editor window for the given file.
1318
1319 Note: This method opens an editor window when the unittest dialog
1320 is called as a standalone application.
1321
1322 @param filename path of the file to be opened
1323 @type str
1324 @param linenumber line number to place the cursor at
1325 @type int
1326 """
1327 from QScintilla.MiniEditor import MiniEditor
1328 editor = MiniEditor(filename, "Python3", self)
1329 editor.gotoLine(linenumber)
1330 editor.show()
1331
1332 self.__editors.append(editor)
1333
1334 def closeEvent(self, event):
1335 """
1336 Protected method to handle the close event.
1337
1338 @param event close event
1339 @type QCloseEvent
1340 """
1341 event.accept()
1342
1343 for editor in self.__editors:
1344 with contextlib.suppress(Exception):
1345 editor.close()
1346
1347
1348 class QtTestResult(unittest.TestResult):
1349 """
1350 A TestResult derivative to work with a graphical GUI.
1351
1352 For more details see pyunit.py of the standard Python distribution.
1353 """
1354 def __init__(self, parent, failfast):
1355 """
1356 Constructor
1357
1358 @param parent reference to the parent widget
1359 @type UnittestDialog
1360 @param failfast flag indicating to stop at the first error
1361 @type bool
1362 """
1363 super().__init__()
1364 self.parent = parent
1365 self.failfast = failfast
1366
1367 def addFailure(self, test, err):
1368 """
1369 Public method called if a test failed.
1370
1371 @param test reference to the test object
1372 @param err error traceback
1373 """
1374 super().addFailure(test, err)
1375 tracebackLines = self._exc_info_to_string(err, test)
1376 self.parent.testFailed(str(test), tracebackLines, test.id())
1377
1378 def addError(self, test, err):
1379 """
1380 Public method called if a test errored.
1381
1382 @param test reference to the test object
1383 @param err error traceback
1384 """
1385 super().addError(test, err)
1386 tracebackLines = self._exc_info_to_string(err, test)
1387 self.parent.testErrored(str(test), tracebackLines, test.id())
1388
1389 def addSubTest(self, test, subtest, err):
1390 """
1391 Public method called for each subtest to record its result.
1392
1393 @param test reference to the test object
1394 @param subtest reference to the subtest object
1395 @param err error traceback
1396 """
1397 if err is not None:
1398 super().addSubTest(test, subtest, err)
1399 tracebackLines = self._exc_info_to_string(err, test)
1400 if issubclass(err[0], test.failureException):
1401 self.parent.testFailed(
1402 str(subtest), tracebackLines, test.id())
1403 else:
1404 self.parent.testErrored(
1405 str(subtest), tracebackLines, test.id())
1406
1407 def addSkip(self, test, reason):
1408 """
1409 Public method called if a test was skipped.
1410
1411 @param test reference to the test object
1412 @param reason reason for skipping the test (string)
1413 """
1414 super().addSkip(test, reason)
1415 self.parent.testSkipped(str(test), reason, test.id())
1416
1417 def addExpectedFailure(self, test, err):
1418 """
1419 Public method called if a test failed expected.
1420
1421 @param test reference to the test object
1422 @param err error traceback
1423 """
1424 super().addExpectedFailure(test, err)
1425 tracebackLines = self._exc_info_to_string(err, test)
1426 self.parent.testFailedExpected(str(test), tracebackLines, test.id())
1427
1428 def addUnexpectedSuccess(self, test):
1429 """
1430 Public method called if a test succeeded expectedly.
1431
1432 @param test reference to the test object
1433 """
1434 super().addUnexpectedSuccess(test)
1435 self.parent.testSucceededUnexpected(str(test), test.id())
1436
1437 def startTest(self, test):
1438 """
1439 Public method called at the start of a test.
1440
1441 @param test Reference to the test object
1442 """
1443 super().startTest(test)
1444 self.parent.testStarted(str(test), test.shortDescription())
1445
1446 def stopTest(self, test):
1447 """
1448 Public method called at the end of a test.
1449
1450 @param test Reference to the test object
1451 """
1452 super().stopTest(test)
1453 self.parent.testFinished()
1454
1455
1456 class UnittestWindow(EricMainWindow):
1457 """
1458 Main window class for the standalone dialog.
1459 """
1460 def __init__(self, prog=None, parent=None):
1461 """
1462 Constructor
1463
1464 @param prog filename of the program to open
1465 @param parent reference to the parent widget (QWidget)
1466 """
1467 super().__init__(parent)
1468 self.cw = UnittestDialog(prog, parent=self)
1469 self.cw.installEventFilter(self)
1470 size = self.cw.size()
1471 self.setCentralWidget(self.cw)
1472 self.resize(size)
1473
1474 self.setStyle(Preferences.getUI("Style"),
1475 Preferences.getUI("StyleSheet"))
1476
1477 self.cw.buttonBox.accepted.connect(self.close)
1478 self.cw.buttonBox.rejected.connect(self.close)
1479
1480 def eventFilter(self, obj, event):
1481 """
1482 Public method to filter events.
1483
1484 @param obj reference to the object the event is meant for (QObject)
1485 @param event reference to the event object (QEvent)
1486 @return flag indicating, whether the event was handled (boolean)
1487 """
1488 if event.type() == QEvent.Type.Close:
1489 QApplication.exit()
1490 return True
1491
1492 return False
1493
1494
1495 def clearSavedHistories(self):
1496 """
1497 Function to clear the saved history lists.
1498 """
1499 Preferences.Prefs.rsettings.setValue(
1500 recentNameUnittestDiscoverHistory, [])
1501 Preferences.Prefs.rsettings.setValue(
1502 recentNameUnittestFileHistory, [])
1503 Preferences.Prefs.rsettings.setValue(
1504 recentNameUnittestTestnameHistory, [])
1505
1506 Preferences.Prefs.rsettings.sync()

eric ide

mercurial