diff --git a/qt/python/mantidqt/utils/qt/testing/__init__.py b/qt/python/mantidqt/utils/qt/testing/__init__.py index 7aee12dd5186f33a0a0b11f28f2051f84a7ee57a..19a8885cfe81a5134675f81103b61d5810b03e8e 100644 --- a/qt/python/mantidqt/utils/qt/testing/__init__.py +++ b/qt/python/mantidqt/utils/qt/testing/__init__.py @@ -29,15 +29,17 @@ from mantidqt.utils.qt.plugins import setup_library_paths from .modal_tester import ModalTester -# Reference to created QApplication instance so that item is kept alive -QAPP = None - - def requires_qapp(cls): """ Converts a unittest.TestCase class to a GUI test case by wrapping all test methods in a decorator that makes sure that a QApplication is created. Qt widgets don't work without QApplication. + + Note: It seems possible to segfault the Python process if the QApplication object + is destroyed too late. This seems nearly guaranteed if the QApplication object is stored + as a global variable at module scope. For this reason this decorator stores + its instance as an object attribute. + Usage: @requires_qapp @@ -51,33 +53,30 @@ def requires_qapp(cls): :param cls: Class instance """ - def is_test_method(name, x): - # Python 3 returns a functions type for methods not bound to an instance - return name.startswith('test') and (isfunction(x) or ismethod(x)) - - for name in dir(cls): - attr = getattr(cls, name) - if is_test_method(name, attr): - setattr(cls, name, _requires_qapp_impl(attr)) - return cls + def do_nothing(self): + pass + orig_setUp = getattr(cls, 'setUp', do_nothing) + orig_tearDown = getattr(cls, 'tearDown', do_nothing) -def _requires_qapp_impl(test_method): - """ - Decorator for GUI test methods. Creates a QApplication before - executing the test. - :param test_method: A test method. - """ - def _wrapper(self): - global QAPP - if not QAPP: + def setUp(self): + qapp = QApplication.instance() + if qapp is None: setup_library_paths() - QAPP = QApplication(['']) - test_method(self) - QAPP.closeAllWindows() - gc.collect() - - return _wrapper + self._qapp = QApplication(['']) + else: + self._qapp = qapp + orig_setUp(self) + + def tearDown(self): + orig_tearDown(self) + if self._qapp is not None: + self._qapp.closeAllWindows() + gc.collect() + + cls.setUp = setUp + cls.tearDown = tearDown + return cls def select_item_in_tree(tree, item_label): diff --git a/qt/python/mantidqt/utils/qt/testing/modal_tester.py b/qt/python/mantidqt/utils/qt/testing/modal_tester.py index f7588248a440d61205f725fb82b002afd1c44507..46903979b457005d1d8cb7eef78f4325fe627d0a 100644 --- a/qt/python/mantidqt/utils/qt/testing/modal_tester.py +++ b/qt/python/mantidqt/utils/qt/testing/modal_tester.py @@ -39,13 +39,13 @@ import traceback from qtpy.QtCore import QTimer from qtpy.QtWidgets import QApplication -QAPP = None - class ModalTester(object): """ Helper class for testing modal widgets (dialogs). """ + _qapp = None + def __init__(self, creator, tester): """ Initialise ModalTester. @@ -53,9 +53,11 @@ class ModalTester(object): A modal widget must have exec_() method. :param tester: A function taking a widget as its argument that does testing. """ - global QAPP - if QAPP is None: - QAPP = QApplication(['']) + qapp = QApplication.instance() + if qapp is None: + self._qapp = QApplication([' ']) + else: + self._qapp = qapp self.creator = creator self.tester = tester self.widget = None @@ -70,7 +72,7 @@ class ModalTester(object): self.widget = self.creator() except: traceback.print_exc() - QAPP.exit(0) + self._qapp.exit(0) if self.widget is not None: self.widget.exec_() @@ -79,7 +81,7 @@ class ModalTester(object): This function runs every time in QApplication's event loop. Call the testing function. """ - modal_widget = QAPP.activeModalWidget() + modal_widget = self._qapp.activeModalWidget() if modal_widget is not None: if self.widget is modal_widget: try: @@ -87,7 +89,7 @@ class ModalTester(object): self.passed = True except: traceback.print_exc() - QAPP.exit(0) + self._qapp.exit(0) if modal_widget.isVisible(): modal_widget.close() @@ -103,4 +105,4 @@ class ModalTester(object): # This calls __call__() method QTimer.singleShot(0, self) # Start the event loop - QAPP.exec_() + self._qapp.exec_() diff --git a/qt/python/mantidqt/utils/test/test_modal_tester.py b/qt/python/mantidqt/utils/test/test_modal_tester.py index 6cd3fb69b8b159412527307f7da4f6200fda64d0..66ec5ff868b2092b947658ad327bd18462163dd3 100644 --- a/qt/python/mantidqt/utils/test/test_modal_tester.py +++ b/qt/python/mantidqt/utils/test/test_modal_tester.py @@ -22,9 +22,10 @@ import unittest from qtpy.QtWidgets import QInputDialog -from mantidqt.utils.qt.testing import ModalTester +from mantidqt.utils.qt.testing import requires_qapp, ModalTester +@requires_qapp class TestModalTester(unittest.TestCase): def test_pass_widget_closed(self): diff --git a/qt/python/mantidqt/utils/test/test_qt_utils.py b/qt/python/mantidqt/utils/test/test_qt_utils.py index 7ab77872df5385077d804161d78b42c274a9dcc3..6e9497eaf7ed1d56aa70560cfefb4ec20a89c5e8 100644 --- a/qt/python/mantidqt/utils/test/test_qt_utils.py +++ b/qt/python/mantidqt/utils/test/test_qt_utils.py @@ -20,26 +20,20 @@ from __future__ import (absolute_import, division, print_function, import unittest from qtpy.QtCore import QObject, Qt, Slot -from qtpy.QtWidgets import QAction, QApplication, QMenu, QToolBar +from qtpy.QtWidgets import QAction, QMenu, QToolBar try: from qtpy.QtCore import SIGNAL NEW_STYLE_SIGNAL = False except ImportError: NEW_STYLE_SIGNAL = True +from mantidqt.utils.qt.testing import requires_qapp from mantidqt.utils.qt import add_actions, create_action -QAPP = None - +@requires_qapp class CreateActionTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - global QAPP - if QApplication.instance() is None: - QAPP = QApplication(['']) - def test_parent_and_name_only_required(self): class Parent(QObject): pass @@ -79,14 +73,9 @@ class CreateActionTest(unittest.TestCase): self.assertEqual(Qt.WindowShortcut, action.shortcutContext()) +@requires_qapp class AddActionsTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - global QAPP - if QApplication.instance() is None: - QAPP = QApplication(['']) - def test_add_actions_with_qmenu_target(self): test_act_1 = create_action(None, "Test Action 1") test_act_2 = create_action(None, "Test Action 2") diff --git a/qt/python/mantidqt/widgets/test/test_algorithm_dialog.py b/qt/python/mantidqt/widgets/test/test_algorithm_dialog.py index 3d425ab9b82b1271ead357e1d6522116dbb4c678..52121d0bd7096351c28f23c82f3467c81ae20b9a 100644 --- a/qt/python/mantidqt/widgets/test/test_algorithm_dialog.py +++ b/qt/python/mantidqt/widgets/test/test_algorithm_dialog.py @@ -69,3 +69,7 @@ class TestAlgorithmDialog(unittest.TestCase): self.assertTrue(dialog is not None) input_widgets = dialog.findChildren(QLineEdit) self.assertEqual(len(input_widgets), 3) + + +if __name__ == '__main__': + unittest.main() diff --git a/qt/python/mantidqt/widgets/test/test_messagedisplay.py b/qt/python/mantidqt/widgets/test/test_messagedisplay.py index 31dd406783d9c0f0aadd43ac9c715976eed580e6..01a2fc1287d9ef0367e118ce9a69ebe7ca346757 100644 --- a/qt/python/mantidqt/widgets/test/test_messagedisplay.py +++ b/qt/python/mantidqt/widgets/test/test_messagedisplay.py @@ -19,22 +19,14 @@ from __future__ import (absolute_import, division, print_function, import unittest -from qtpy.QtWidgets import QApplication - from mantidqt.widgets.messagedisplay import MessageDisplay - -QAPP = None +from mantidqt.utils.qt.testing import requires_qapp +@requires_qapp class MessageDisplayTest(unittest.TestCase): """Minimal testing as it is exported from C++""" - @classmethod - def setUpClass(cls): - global QAPP - if QApplication.instance() is None: - QAPP = QApplication(['']) - def test_widget_creation(self): display = MessageDisplay() self.assertTrue(display is not None)