diff options
Diffstat (limited to 'silx/gui/test/utils.py')
-rw-r--r-- | silx/gui/test/utils.py | 88 |
1 files changed, 76 insertions, 12 deletions
diff --git a/silx/gui/test/utils.py b/silx/gui/test/utils.py index 19c448a..3eee474 100644 --- a/silx/gui/test/utils.py +++ b/silx/gui/test/utils.py @@ -26,7 +26,7 @@ __authors__ = ["T. Vincent"] __license__ = "MIT" -__date__ = "01/09/2017" +__date__ = "27/02/2018" import gc @@ -43,6 +43,8 @@ from silx.gui import qt if qt.BINDING == 'PySide': from PySide.QtTest import QTest +elif qt.BINDING == 'PySide2': + from PySide2.QtTest import QTest elif qt.BINDING == 'PyQt5': from PyQt5.QtTest import QTest elif qt.BINDING == 'PyQt4': @@ -137,11 +139,9 @@ class TestCaseQt(unittest.TestCase): # Makes sure a QApplication exists and do it once for all _qapp = qt.QApplication.instance() or qt.QApplication([]) - # Create/delate a QWidget to make sure init of QDesktopWidget - _dummyWidget = qt.QWidget() - _dummyWidget.setAttribute(qt.Qt.WA_DeleteOnClose) - _dummyWidget.show() - _dummyWidget.close() + # Makes sure QDesktopWidget is init + # Otherwise it happens randomly during the tests + cls._desktopWidget = _qapp.desktop() _qapp.processEvents() @classmethod @@ -163,9 +163,10 @@ class TestCaseQt(unittest.TestCase): # For Python < 3.4 result = getattr(self, '_outcomeForDoCleanups', self._resultForDoCleanups) + skipped = self.id() in [case.id() for case, _ in result.skipped] error = self.id() in [case.id() for case, _ in result.errors] failure = self.id() in [case.id() for case, _ in result.failures] - return not error and not failure + return not error and not failure and not skipped def _checkForUnreleasedWidgets(self): """Test fixture checking that no more widgets exists.""" @@ -175,7 +176,7 @@ class TestCaseQt(unittest.TestCase): if widget not in self.__previousWidgets] del self.__previousWidgets - if qt.BINDING == 'PySide': + if qt.BINDING in ('PySide', 'PySide2'): return # Do not test for leaking widgets with PySide allowedLeakingWidgets = self.allowedLeakingWidgets @@ -318,24 +319,25 @@ class TestCaseQt(unittest.TestCase): """ QTest.qSleep(ms + self.TIMEOUT_WAIT) - def qWait(self, ms=None): + @classmethod + def qWait(cls, ms=None): """Waits for ms milliseconds, events will be processed. See QTest.qWait for details. """ if ms is None: - ms = self.DEFAULT_TIMEOUT_WAIT + ms = cls.DEFAULT_TIMEOUT_WAIT if qt.BINDING == 'PySide': # PySide has no qWait, provide a replacement timeout = int(ms) endTimeMS = int(time.time() * 1000) + timeout while timeout > 0: - self.qapp.processEvents(qt.QEventLoop.AllEvents, + _qapp.processEvents(qt.QEventLoop.AllEvents, maxtime=timeout) timeout = endTimeMS - int(time.time() * 1000) else: - QTest.qWait(ms + self.TIMEOUT_WAIT) + QTest.qWait(ms + cls.TIMEOUT_WAIT) def qWaitForWindowExposed(self, window, timeout=None): """Waits until the window is shown in the screen. @@ -349,6 +351,57 @@ class TestCaseQt(unittest.TestCase): return result + _qobject_destroyed = False + + @classmethod + def _aboutToDestroy(cls): + cls._qobject_destroyed = True + + @classmethod + def qWaitForDestroy(cls, ref): + """ + Wait for Qt object destruction. + + Use a weakref as parameter to avoid any strong references to the + object. + + It have to be used as following. Removing the reference to the object + before calling the function looks to be expected, else + :meth:`deleteLater` will not work. + + .. code-block:: python + + ref = weakref.ref(self.obj) + self.obj = None + self.qWaitForDestroy(ref) + + :param weakref ref: A weakref to an object to avoid any reference + :return: True if the object was destroyed + :rtype: bool + """ + cls._qobject_destroyed = False + if qt.BINDING == 'PyQt4': + # Without this, QWidget will be still alive on PyQt4 + # (at least on Windows Python 2.7) + # If it is not skipped on PySide, silx.gui.dialog tests will + # segfault (at least on Windows Python 2.7) + import gc + gc.collect() + qobject = ref() + if qobject is None: + return True + qobject.destroyed.connect(cls._aboutToDestroy) + qobject.deleteLater() + qobject = None + for _ in range(10): + if cls._qobject_destroyed: + break + cls.qWait(10) + else: + _logger.debug("Object was not destroyed") + + return ref() is None + def logScreenShot(self, level=logging.ERROR): """Take a screenshot and log it into the logging system if the logger is enabled for the expected level. @@ -454,3 +507,14 @@ def getQToolButtonFromAction(action): if isinstance(widget, qt.QToolButton): return widget return None + + +def findChildren(parent, kind, name=None): + if qt.BINDING == "PySide" and name is not None: + result = [] + for obj in parent.findChildren(kind): + if obj.objectName() == name: + result.append(obj) + return result + else: + return parent.findChildren(kind, name=name) |