diff --git a/mslib/msui/mscolab.py b/mslib/msui/mscolab.py index 2c59ace2d..9b9f88551 100644 --- a/mslib/msui/mscolab.py +++ b/mslib/msui/mscolab.py @@ -519,7 +519,7 @@ def __init__(self, parent=None, data_dir=None): self.ui.serverOptionsCb.currentIndexChanged.connect(self.server_options_handler) # set up user menu and add to toolbutton - self.user_menu = QtWidgets.QMenu() + self.user_menu = QtWidgets.QMenu(self.ui) self.profile_action = self.user_menu.addAction("Profile", self.open_profile_window) self.user_menu.addSeparator() self.logout_action = self.user_menu.addAction("Logout", self.logout) @@ -790,7 +790,7 @@ def open_profile_window(self): def on_context_menu(point): self.gravatar_menu.exec_(self.profile_dialog.gravatarLabel.mapToGlobal(point)) - self.prof_diag = QtWidgets.QDialog() + self.prof_diag = QtWidgets.QDialog(self.ui) self.profile_dialog = ui_profile.Ui_ProfileWindow() self.profile_dialog.setupUi(self.prof_diag) self.profile_dialog.buttonBox.accepted.connect(lambda: self.prof_diag.close()) @@ -800,7 +800,7 @@ def on_context_menu(point): self.profile_dialog.deleteAccountBtn.clicked.connect(self.delete_account) # add context menu for right click on image - self.gravatar_menu = QtWidgets.QMenu() + self.gravatar_menu = QtWidgets.QMenu(self.ui) self.gravatar_menu.addAction('Fetch Gravatar', lambda: self.fetch_gravatar(refresh=True)) self.gravatar_menu.addAction('Remove Gravatar', lambda: self.remove_gravatar()) self.profile_dialog.gravatarLabel.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) @@ -812,7 +812,7 @@ def on_context_menu(point): def delete_account(self): # ToDo rename to delete_own_account if verify_user_token(self.mscolab_server_url, self.token): - w = QtWidgets.QWidget() + w = QtWidgets.QWidget(self.ui) qm = QtWidgets.QMessageBox reply = qm.question(w, self.tr('Continue?'), self.tr("You're about to delete your account. You cannot undo this operation!"), @@ -870,7 +870,7 @@ def browse(): self.add_proj_dialog.f_content = file_content self.add_proj_dialog.selectedFile.setText(file_name) - self.proj_diag = QtWidgets.QDialog() + self.proj_diag = QtWidgets.QDialog(self.ui) self.add_proj_dialog = add_operation_ui.Ui_addOperationDialog() self.add_proj_dialog.setupUi(self.proj_diag) self.add_proj_dialog.f_content = None @@ -897,21 +897,21 @@ def add_operation(self): description = self.add_proj_dialog.description.toPlainText() category = self.add_proj_dialog.category.text() if not path: - self.error_dialog = QtWidgets.QErrorMessage() + self.error_dialog = QtWidgets.QErrorMessage(self.ui) self.error_dialog.showMessage('Path can\'t be empty') return elif not description: - self.error_dialog = QtWidgets.QErrorMessage() + self.error_dialog = QtWidgets.QErrorMessage(self.ui) self.error_dialog.showMessage('Description can\'t be empty') return # same regex as for path validation elif not re.match("^[a-zA-Z0-9_-]*$", category): - self.error_dialog = QtWidgets.QErrorMessage() + self.error_dialog = QtWidgets.QErrorMessage(self.ui) self.error_dialog.showMessage('Category can\'t contain spaces or special characters') return # regex checks if the whole path from beginning to end only contains alphanumerical characters or _ and - elif not re.match("^[a-zA-Z0-9_-]*$", path): - self.error_dialog = QtWidgets.QErrorMessage() + self.error_dialog = QtWidgets.QErrorMessage(self.ui) self.error_dialog.showMessage('Path can\'t contain spaces or special characters') return @@ -943,7 +943,7 @@ def add_operation(self): self.conn.handle_new_operation(op_id) self.signal_operation_added.emit(op_id, path) else: - self.error_dialog = QtWidgets.QErrorMessage() + self.error_dialog = QtWidgets.QErrorMessage(self.ui) self.error_dialog.showMessage('The path already exists') def get_recent_op_id(self): @@ -1134,7 +1134,7 @@ def handle_delete_operation(self): def handle_leave_operation(self): logging.debug("handle_leave_operation") - w = QtWidgets.QWidget() + w = QtWidgets.QWidget(self.ui) qm = QtWidgets.QMessageBox reply = qm.question(w, self.tr('Mission Support System'), self.tr("Do you want to leave this operation?"), diff --git a/mslib/msui/mscolab_chat.py b/mslib/msui/mscolab_chat.py index e1e7e8d1c..3dd2cfd6b 100644 --- a/mslib/msui/mscolab_chat.py +++ b/mslib/msui/mscolab_chat.py @@ -460,7 +460,7 @@ def __init__(self, message, chat_window): else: self.attachment_path = message["text"] self.context_menu = QtWidgets.QMenu(self) - self.textArea = QtWidgets.QWidget() + self.textArea = QtWidgets.QWidget(self) self.replyArea = None self.replyScroll = QtWidgets.QScrollArea() self.setup_message_box() diff --git a/mslib/msui/msui_mainwindow.py b/mslib/msui/msui_mainwindow.py index aed56354e..80ae8713f 100644 --- a/mslib/msui/msui_mainwindow.py +++ b/mslib/msui/msui_mainwindow.py @@ -444,6 +444,7 @@ def __init__(self, mscolab_data_dir=None, tutorial_mode=False, *args): except (ImportError, AttributeError) as error: logging.debug("AttributeError, ImportError Exception %s", error) + self.force_close = False self.config_editor = None self.local_active = True self.new_flight_track_counter = 0 @@ -1069,10 +1070,13 @@ def closeEvent(self, event): Overloads QtGui.QMainWindow.closeEvent(). This method is called if Qt receives a window close request for our application window. """ - ret = QtWidgets.QMessageBox.warning( - self, self.tr("Mission Support System"), - self.tr("Do you want to close the Mission Support System application?"), - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) + if self.force_close: + ret = QtWidgets.QMessageBox.Yes + else: + ret = QtWidgets.QMessageBox.warning( + self, self.tr("Mission Support System"), + self.tr("Do you want to close the Mission Support System application?"), + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if ret == QtWidgets.QMessageBox.Yes: if self.mscolab.help_dialog is not None: diff --git a/mslib/msui/multiple_flightpath_dockwidget.py b/mslib/msui/multiple_flightpath_dockwidget.py index 31484a20e..fcb9f1916 100644 --- a/mslib/msui/multiple_flightpath_dockwidget.py +++ b/mslib/msui/multiple_flightpath_dockwidget.py @@ -334,7 +334,7 @@ def select_color(self): self.list_flighttrack.currentItem().checkState() == QtCore.Qt.Checked): wp_model = self.list_flighttrack.currentItem().flighttrack_model if wp_model == self.active_flight_track: - self.error_dialog = QtWidgets.QErrorMessage() + self.error_dialog = QtWidgets.QErrorMessage(self) self.error_dialog.showMessage('Use "options" to change color of an activated flighttrack.') else: color = QtWidgets.QColorDialog.getColor() @@ -735,7 +735,7 @@ def select_color(self): self.list_operation_track.currentItem().checkState() == QtCore.Qt.Checked): op_id = self.list_operation_track.currentItem().op_id if self.list_operation_track.currentItem().op_id == self.active_op_id: - self.error_dialog = QtWidgets.QErrorMessage() + self.error_dialog = QtWidgets.QErrorMessage(self) self.error_dialog.showMessage('Use "options" to change color of an activated operation.') else: color = QtWidgets.QColorDialog.getColor() diff --git a/mslib/msui/qt5/ui_kmloverlay_dockwidget.py b/mslib/msui/qt5/ui_kmloverlay_dockwidget.py index b3901e3cf..12b49bec1 100644 --- a/mslib/msui/qt5/ui_kmloverlay_dockwidget.py +++ b/mslib/msui/qt5/ui_kmloverlay_dockwidget.py @@ -125,4 +125,3 @@ def retranslateUi(self, KMLOverlayDockWidget): ui.setupUi(KMLOverlayDockWidget) KMLOverlayDockWidget.show() sys.exit(app.exec_()) - diff --git a/mslib/msui/qt5/ui_mscolab_connect_dialog.py b/mslib/msui/qt5/ui_mscolab_connect_dialog.py index 93f8ee09b..0830b3fbb 100644 --- a/mslib/msui/qt5/ui_mscolab_connect_dialog.py +++ b/mslib/msui/qt5/ui_mscolab_connect_dialog.py @@ -41,7 +41,7 @@ def setupUi(self, MSColabConnectDialog): self.gridLayout_4.addWidget(self.line, 1, 0, 1, 1) self.stackedWidget = QtWidgets.QStackedWidget(MSColabConnectDialog) self.stackedWidget.setObjectName("stackedWidget") - self.loginPage = QtWidgets.QWidget() + self.loginPage = QtWidgets.QWidget(MSColabConnectDialog) self.loginPage.setObjectName("loginPage") self.gridLayout_3 = QtWidgets.QGridLayout(self.loginPage) self.gridLayout_3.setContentsMargins(100, 0, 100, 0) @@ -74,7 +74,7 @@ def setupUi(self, MSColabConnectDialog): self.loginPasswordLe.setObjectName("loginPasswordLe") self.gridLayout_3.addWidget(self.loginPasswordLe, 2, 0, 1, 2) self.stackedWidget.addWidget(self.loginPage) - self.newuserPage = QtWidgets.QWidget() + self.newuserPage = QtWidgets.QWidget(MSColabConnectDialog) self.newuserPage.setObjectName("newuserPage") self.gridLayout_2 = QtWidgets.QGridLayout(self.newuserPage) self.gridLayout_2.setContentsMargins(50, 0, 50, 0) @@ -118,7 +118,7 @@ def setupUi(self, MSColabConnectDialog): self.newConfirmPasswordLe.setObjectName("newConfirmPasswordLe") self.gridLayout_2.addWidget(self.newConfirmPasswordLe, 4, 1, 1, 1) self.stackedWidget.addWidget(self.newuserPage) - self.httpAuthPage = QtWidgets.QWidget() + self.httpAuthPage = QtWidgets.QWidget(MSColabConnectDialog) self.httpAuthPage.setObjectName("httpAuthPage") self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.httpAuthPage) self.verticalLayout_4.setContentsMargins(0, 0, 0, 0) @@ -147,7 +147,7 @@ def setupUi(self, MSColabConnectDialog): spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_4.addItem(spacerItem) self.stackedWidget.addWidget(self.httpAuthPage) - self.idpAuthPage = QtWidgets.QWidget() + self.idpAuthPage = QtWidgets.QWidget(MSColabConnectDialog) self.idpAuthPage.setEnabled(True) self.idpAuthPage.setObjectName("idpAuthPage") self.layoutWidget = QtWidgets.QWidget(self.idpAuthPage) diff --git a/mslib/msui/qt5/ui_mscolab_help_dialog.py b/mslib/msui/qt5/ui_mscolab_help_dialog.py index 45824a0b2..79464ab39 100644 --- a/mslib/msui/qt5/ui_mscolab_help_dialog.py +++ b/mslib/msui/qt5/ui_mscolab_help_dialog.py @@ -21,7 +21,7 @@ def setupUi(self, mscolabHelpDialog): self.tabWidget = QtWidgets.QTabWidget(mscolabHelpDialog) self.tabWidget.setTabShape(QtWidgets.QTabWidget.Rounded) self.tabWidget.setObjectName("tabWidget") - self.connectingTab = QtWidgets.QWidget() + self.connectingTab = QtWidgets.QWidget(mscolabHelpDialog) self.connectingTab.setObjectName("connectingTab") self.gridLayout_4 = QtWidgets.QGridLayout(self.connectingTab) self.gridLayout_4.setContentsMargins(0, 0, 0, 0) @@ -91,7 +91,7 @@ def setupUi(self, mscolabHelpDialog): self.verticalLayout_3.setStretch(3, 1) self.gridLayout_4.addLayout(self.verticalLayout_3, 0, 0, 1, 1) self.tabWidget.addTab(self.connectingTab, "") - self.createOperationTab = QtWidgets.QWidget() + self.createOperationTab = QtWidgets.QWidget(mscolabHelpDialog) self.createOperationTab.setObjectName("createOperationTab") self.gridLayout_3 = QtWidgets.QGridLayout(self.createOperationTab) self.gridLayout_3.setContentsMargins(0, 0, 0, 0) @@ -119,7 +119,7 @@ def setupUi(self, mscolabHelpDialog): self.verticalLayout_4.addLayout(self.horizontalLayout_11) self.gridLayout_3.addLayout(self.verticalLayout_4, 0, 0, 1, 1) self.tabWidget.addTab(self.createOperationTab, "") - self.adminTab = QtWidgets.QWidget() + self.adminTab = QtWidgets.QWidget(mscolabHelpDialog) self.adminTab.setObjectName("adminTab") self.gridLayout_5 = QtWidgets.QGridLayout(self.adminTab) self.gridLayout_5.setContentsMargins(0, 0, 0, 0) @@ -149,7 +149,7 @@ def setupUi(self, mscolabHelpDialog): self.verticalLayout_5.addLayout(self.horizontalLayout_12) self.gridLayout_5.addLayout(self.verticalLayout_5, 0, 0, 1, 1) self.tabWidget.addTab(self.adminTab, "") - self.chatTab = QtWidgets.QWidget() + self.chatTab = QtWidgets.QWidget(mscolabHelpDialog) self.chatTab.setObjectName("chatTab") self.gridLayout_7 = QtWidgets.QGridLayout(self.chatTab) self.gridLayout_7.setContentsMargins(0, 0, 0, 0) @@ -179,7 +179,7 @@ def setupUi(self, mscolabHelpDialog): self.verticalLayout_6.addLayout(self.horizontalLayout_13) self.gridLayout_7.addLayout(self.verticalLayout_6, 0, 0, 1, 1) self.tabWidget.addTab(self.chatTab, "") - self.versionTab = QtWidgets.QWidget() + self.versionTab = QtWidgets.QWidget(mscolabHelpDialog) self.versionTab.setObjectName("versionTab") self.gridLayout_9 = QtWidgets.QGridLayout(self.versionTab) self.gridLayout_9.setContentsMargins(0, 0, 0, 0) @@ -209,7 +209,7 @@ def setupUi(self, mscolabHelpDialog): self.verticalLayout_7.addLayout(self.horizontalLayout_14) self.gridLayout_9.addLayout(self.verticalLayout_7, 0, 0, 1, 1) self.tabWidget.addTab(self.versionTab, "") - self.operationWorkTab = QtWidgets.QWidget() + self.operationWorkTab = QtWidgets.QWidget(mscolabHelpDialog) self.operationWorkTab.setObjectName("operationWorkTab") self.gridLayout_8 = QtWidgets.QGridLayout(self.operationWorkTab) self.gridLayout_8.setContentsMargins(0, 0, 0, 0) diff --git a/mslib/msui/wms_control.py b/mslib/msui/wms_control.py index bf4827ad3..79c14f746 100644 --- a/mslib/msui/wms_control.py +++ b/mslib/msui/wms_control.py @@ -512,13 +512,13 @@ def __init__(self, parent=None, default_WMS=None, wms_cache=None, view=None): # Progress dialog to inform the user about image ongoing retrievals. self.pdlg = QtWidgets.QProgressDialog( - "retrieving image...", "Cancel", 0, 10, parent=self.parent()) + "retrieving image...", "Cancel", 0, 10, parent=self) self.pdlg.close() # Progress dialog to inform the user about ongoing capability requests. self.capabilities_worker = Worker(None) self.cpdlg = QtWidgets.QProgressDialog( - "retrieving wms capabilities...", "Cancel", 0, 10, parent=self.multilayers) + "retrieving wms capabilities...", "Cancel", 0, 10, parent=self) self.cpdlg.canceled.connect(self.stop_capabilities_retrieval) self.cpdlg.close() diff --git a/mslib/support/qt_json_view/view.py b/mslib/support/qt_json_view/view.py index 12415f926..72f569f77 100644 --- a/mslib/support/qt_json_view/view.py +++ b/mslib/support/qt_json_view/view.py @@ -17,7 +17,7 @@ def __init__(self, parent=None): def _menu(self, position): """Show the actions of the DataType (if any).""" - menu = QtWidgets.QMenu() + menu = QtWidgets.QMenu(self) index = self.indexAt(position) data = index.data(TypeRole) if data is None: diff --git a/tests/_test_msui/test_editor.py b/tests/_test_msui/test_editor.py index f3dc3cfc6..1c0927b69 100644 --- a/tests/_test_msui/test_editor.py +++ b/tests/_test_msui/test_editor.py @@ -49,7 +49,6 @@ def setup(self, qtbot): yield if os.path.exists(self.save_file_name): os.remove(self.save_file_name) - self.window.hide() @mock.patch("mslib.msui.editor.get_open_filename", return_value=sample_file) def test_file_open(self, mockfile): diff --git a/tests/_test_msui/test_kmloverlay_dockwidget.py b/tests/_test_msui/test_kmloverlay_dockwidget.py index b669cff13..77e1ea508 100644 --- a/tests/_test_msui/test_kmloverlay_dockwidget.py +++ b/tests/_test_msui/test_kmloverlay_dockwidget.py @@ -49,13 +49,13 @@ def setup(self, qtbot): self.view.map.gcpoints_path = mock.Mock(side_effect=lambda x, y: (x, y)) self.window = kd.KMLOverlayControlWidget(view=self.view) + qtbot.add_widget(self.window) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) # start load test self.window.select_all() self.window.remove_file() yield - self.window.close() if os.path.exists(save_kml): os.remove(save_kml) diff --git a/tests/_test_msui/test_linearview.py b/tests/_test_msui/test_linearview.py index 2a43fdd13..b454f79bb 100644 --- a/tests/_test_msui/test_linearview.py +++ b/tests/_test_msui/test_linearview.py @@ -34,16 +34,16 @@ from mslib.msui import flighttrack as ft import mslib.msui.linearview as tv from mslib.msui.mpl_qtwidget import _DEFAULT_SETTINGS_LINEARVIEW +from tests.utils import set_force_close class Test_MSS_LV_Options_Dialog: @pytest.fixture(autouse=True) def setup(self, qtbot): self.window = tv.MSUI_LV_Options_Dialog(settings=_DEFAULT_SETTINGS_LINEARVIEW) + qtbot.add_widget(self.window) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) - yield - self.window.hide() def test_show(self): pass @@ -62,10 +62,9 @@ def setup(self, qtbot): 0, rows=len(initial_waypoints), waypoints=initial_waypoints) self.window = tv.MSUILinearViewWindow(model=waypoints_model) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) - yield - self.window.hide() def test_open_wms(self): self.window.cbTools.currentIndexChanged.emit(1) @@ -97,13 +96,13 @@ def setup(self, qtbot, mswms_server): waypoints_model.insertRows( 0, rows=len(initial_waypoints), waypoints=initial_waypoints) self.window = tv.MSUILinearViewWindow(model=waypoints_model) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) self.window.cbTools.currentIndexChanged.emit(1) self.wms_control = self.window.docks[0].widget() self.wms_control.multilayers.cbWMS_URL.setEditText("") yield - self.window.hide() shutil.rmtree(self.tempdir) def query_server(self, qtbot, url): diff --git a/tests/_test_msui/test_mpl_map.py b/tests/_test_msui/test_mpl_map.py index dbe6b0651..c1776b37c 100644 --- a/tests/_test_msui/test_mpl_map.py +++ b/tests/_test_msui/test_mpl_map.py @@ -36,6 +36,7 @@ def setup(self, qtbot): kwargs = {'resolution': 'l', 'area_thresh': 1000.0, 'ax': plt.gca(), 'llcrnrlon': -15.0, 'llcrnrlat': 35.0, 'urcrnrlon': 30.0, 'urcrnrlat': 65.0, 'epsg': '4326'} self.map = MapCanvas(**kwargs) + qtbot.add_widget(plt.get_current_fig_manager().window) def test_no_coastsegs(self): """ diff --git a/tests/_test_msui/test_mscolab.py b/tests/_test_msui/test_mscolab.py index 6384d461c..81506b858 100644 --- a/tests/_test_msui/test_mscolab.py +++ b/tests/_test_msui/test_mscolab.py @@ -39,7 +39,7 @@ from mslib.msui.flighttrack import WaypointsTableModel from PyQt5 import QtCore, QtTest, QtWidgets from mslib.utils.config import read_config_file, config_loader, modify_config_file -from tests.utils import create_msui_settings_file, ExceptionMock +from tests.utils import create_msui_settings_file, ExceptionMock, set_force_close from mslib.msui import msui from mslib.msui import mscolab from mslib.mscolab.seed import add_user, get_user, add_operation, add_user_to_operation @@ -57,6 +57,7 @@ def setup(self, qtbot, mscolab_server): self.user = get_user(self.userdata[0]) self.main_window = msui.MSUIMainWindow(mscolab_data_dir=mscolab_settings.MSCOLAB_DATA_DIR) + qtbot.add_widget(self.main_window, before_close_func=set_force_close) self.main_window.create_new_flight_track() self.main_window.show() self.window = mscolab.MSColab_ConnectDialog(parent=self.main_window, mscolab=self.main_window.mscolab) @@ -69,8 +70,6 @@ def setup(self, qtbot, mscolab_server): mslib.utils.auth.del_password_from_keyring(service_name="MSCOLAB", username=email) yield self.main_window.mscolab.logout() - self.window.hide() - self.main_window.hide() def test_url_combo(self): assert self.window.urlCb.count() >= 1 @@ -279,21 +278,15 @@ def setup(self, qtbot, mscolab_app, mscolab_server): assert add_user_to_operation(path=self.operation_name3, access_level="collaborator", emailid=self.userdata3[0]) self.window = msui.MSUIMainWindow(mscolab_data_dir=mscolab_settings.MSCOLAB_DATA_DIR) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.create_new_flight_track() self.window.show() self.total_created_operations = 0 yield self.window.mscolab.logout() - if self.window.mscolab.version_window: - self.window.mscolab.version_window.close() if self.window.mscolab.conn: self.window.mscolab.conn.disconnect() - # force close all open views - while self.window.listViews.count() > 0: - self.window.listViews.item(0).window.handle_force_close() - # close all hanging operation option windows - self.window.mscolab.close_external_windows() def test_activate_operation(self, qtbot): self._connect_to_mscolab(qtbot) diff --git a/tests/_test_msui/test_mscolab_admin_window.py b/tests/_test_msui/test_mscolab_admin_window.py index 515868e3e..e6b804fb1 100644 --- a/tests/_test_msui/test_mscolab_admin_window.py +++ b/tests/_test_msui/test_mscolab_admin_window.py @@ -24,11 +24,11 @@ See the License for the specific language governing permissions and limitations under the License. """ -import mock import pytest from mslib.mscolab.conf import mscolab_settings -from PyQt5 import QtCore, QtTest, QtWidgets +from PyQt5 import QtCore, QtTest +from tests.utils import set_force_close from mslib.msui import mscolab from mslib.msui import msui from mslib.mscolab.seed import add_user, get_user, add_operation, add_user_to_operation @@ -59,6 +59,7 @@ def setup(self, qtbot, mscolab_server): assert add_user_to_operation(path="tokyo", emailid=self.userdata[0], access_level="creator") self.window = msui.MSUIMainWindow(mscolab_data_dir=mscolab_settings.MSCOLAB_DATA_DIR) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.create_new_flight_track() self.window.show() # connect and login to mscolab @@ -72,12 +73,8 @@ def setup(self, qtbot, mscolab_server): QtTest.QTest.qWaitForWindowExposed(self.window) yield self.window.mscolab.logout() - if self.window.mscolab.admin_window: - self.window.mscolab.admin_window.close() if self.window.mscolab.conn: self.window.mscolab.conn.disconnect() - with mock.patch("PyQt5.QtWidgets.QMessageBox.warning", return_value=QtWidgets.QMessageBox.Yes): - self.window.close() def test_permission_filter(self): len_added_users = self.admin_window.modifyUsersTable.rowCount() diff --git a/tests/_test_msui/test_mscolab_merge_waypoints.py b/tests/_test_msui/test_mscolab_merge_waypoints.py index 1d0fb9340..81bc4ee22 100644 --- a/tests/_test_msui/test_mscolab_merge_waypoints.py +++ b/tests/_test_msui/test_mscolab_merge_waypoints.py @@ -33,7 +33,7 @@ from mslib.mscolab.conf import mscolab_settings from PyQt5 import QtCore, QtTest from tests.utils import (mscolab_register_and_login, mscolab_create_operation, - mscolab_delete_all_operations, mscolab_delete_user) + mscolab_delete_all_operations, mscolab_delete_user, set_force_close) from mslib.msui import mscolab from mslib.msui import msui from mslib.utils.config import modify_config_file @@ -45,6 +45,7 @@ def setup(self, qtbot, mscolab_app, mscolab_server): self.app = mscolab_app self.url = mscolab_server self.window = msui.MSUIMainWindow(mscolab_data_dir=mscolab_settings.MSCOLAB_DATA_DIR) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.create_new_flight_track() self.emailid = 'merge@alpha.org' yield @@ -57,8 +58,6 @@ def setup(self, qtbot, mscolab_app, mscolab_server): if mss_dir.exists('local_mscolab_data'): mss_dir.removetree('local_mscolab_data') assert mss_dir.exists('local_mscolab_data') is False - if self.window.mscolab.version_window: - self.window.mscolab.version_window.close() if self.window.mscolab.conn: self.window.mscolab.conn.disconnect() diff --git a/tests/_test_msui/test_mscolab_operation.py b/tests/_test_msui/test_mscolab_operation.py index 59d62b7cc..c02a34cbe 100644 --- a/tests/_test_msui/test_mscolab_operation.py +++ b/tests/_test_msui/test_mscolab_operation.py @@ -29,6 +29,7 @@ from mslib.mscolab.conf import mscolab_settings from mslib.mscolab.models import Message from PyQt5 import QtCore, QtTest, QtWidgets +from tests.utils import set_force_close from mslib.msui import mscolab from mslib.msui import msui from mslib.mscolab.seed import add_user, get_user, add_operation, add_user_to_operation @@ -55,6 +56,7 @@ def setup(self, qtbot, mscolab_app, mscolab_server): assert add_user_to_operation(path=self.operation_name, emailid=self.userdata[0]) self.user = get_user(self.userdata[0]) self.window = msui.MSUIMainWindow(mscolab_data_dir=mscolab_settings.MSCOLAB_DATA_DIR) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.create_new_flight_track() self.window.show() # connect and login to mscolab @@ -68,11 +70,8 @@ def setup(self, qtbot, mscolab_app, mscolab_server): QtTest.QTest.qWaitForWindowExposed(self.window) yield self.window.mscolab.logout() - if self.window.mscolab.chat_window: - self.window.mscolab.chat_window.hide() if self.window.mscolab.conn: self.window.mscolab.conn.disconnect() - self.window.hide() def test_send_message(self, qtbot): self._send_message(qtbot, "**test message**") diff --git a/tests/_test_msui/test_mscolab_version_history.py b/tests/_test_msui/test_mscolab_version_history.py index 72f218c58..b09235567 100644 --- a/tests/_test_msui/test_mscolab_version_history.py +++ b/tests/_test_msui/test_mscolab_version_history.py @@ -27,6 +27,7 @@ import pytest import mock +from tests.utils import set_force_close from mslib.mscolab.conf import mscolab_settings from PyQt5 import QtCore, QtTest, QtWidgets from mslib.msui import mscolab @@ -46,6 +47,7 @@ def setup(self, qtbot, mscolab_server): assert add_user_to_operation(path=self.operation_name, emailid=self.userdata[0]) self.user = get_user(self.userdata[0]) self.window = msui.MSUIMainWindow(mscolab_data_dir=mscolab_settings.MSCOLAB_DATA_DIR) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.create_new_flight_track() self.window.show() # connect and login to mscolab @@ -60,8 +62,6 @@ def setup(self, qtbot, mscolab_server): QtTest.QTest.qWaitForWindowExposed(self.window) yield self.window.mscolab.logout() - if self.window.mscolab.version_window: - self.window.mscolab.version_window.close() if self.window.mscolab.conn: self.window.mscolab.conn.disconnect() diff --git a/tests/_test_msui/test_msui.py b/tests/_test_msui/test_msui.py index 7f534cbb2..4aa087c0f 100644 --- a/tests/_test_msui/test_msui.py +++ b/tests/_test_msui/test_msui.py @@ -38,7 +38,7 @@ from tests.constants import ROOT_DIR, POSIX, MSUI_CONFIG_PATH from mslib.msui import msui from mslib.msui import msui_mainwindow as msui_mw -from tests.utils import ExceptionMock +from tests.utils import ExceptionMock, set_force_close from mslib.utils.config import read_config_file @@ -70,14 +70,15 @@ class Test_MSS_TutorialMode: def setup(self, qtbot, qapp): qapp.setApplicationDisplayName("MSUI") self.main_window = msui_mw.MSUIMainWindow(tutorial_mode=True) + qtbot.add_widget(self.main_window, before_close_func=set_force_close) + with qtbot.wait_active(self.main_window): + self.main_window.show() + self.main_window.activateWindow() self.main_window.create_new_flight_track() - self.main_window.show() self.main_window.shortcuts_dlg = msui_mw.MSUI_ShortcutsDialog( tutorial_mode=True) self.main_window.show_shortcuts(search_mode=True) self.tutorial_dir = fs.path.combine(MSUI_CONFIG_PATH, 'tutorial_images') - yield - self.main_window.hide() def test_tutorial_dir(self): dir_name, name = fs.path.split(self.tutorial_dir) @@ -97,8 +98,7 @@ class Test_MSS_AboutDialog: @pytest.fixture(autouse=True) def setup(self, qtbot): self.window = msui_mw.MSUI_AboutDialog() - yield - self.window.hide() + qtbot.add_widget(self.window) def test_milestone_url(self): with urlopen(self.window.milestone_url) as f: @@ -111,11 +111,11 @@ class Test_MSS_ShortcutDialog: @pytest.fixture(autouse=True) def setup(self, qtbot): self.main_window = msui_mw.MSUIMainWindow() - self.main_window.show() + qtbot.add_widget(self.main_window, before_close_func=set_force_close) + with qtbot.wait_active(self.main_window): + self.main_window.show() + self.main_window.activateWindow() self.shortcuts = msui_mw.MSUI_ShortcutsDialog() - yield - self.shortcuts.hide() - self.main_window.hide() def test_shortcuts_present(self): # Assert list gets filled properly @@ -176,6 +176,7 @@ def setup(self, qtbot): 'data/') self.window = msui.MSUIMainWindow() + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.create_new_flight_track() self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) @@ -185,9 +186,6 @@ def setup(self, qtbot): 'empty_msui_settings.json', ) read_config_file(path=config_file) - for i in range(self.window.listViews.count()): - self.window.listViews.item(i).window.hide() - self.window.hide() def test_no_updater(self): assert not hasattr(self.window, "updater") diff --git a/tests/_test_msui/test_multiple_flightpath_dockwidget.py b/tests/_test_msui/test_multiple_flightpath_dockwidget.py index d25ffc3cf..7f6f129f5 100644 --- a/tests/_test_msui/test_multiple_flightpath_dockwidget.py +++ b/tests/_test_msui/test_multiple_flightpath_dockwidget.py @@ -29,6 +29,7 @@ from mslib.msui import msui from mslib.msui.multiple_flightpath_dockwidget import MultipleFlightpathControlWidget from mslib.msui import flighttrack as ft +from tests.utils import set_force_close import mslib.msui.topview as tv @@ -36,6 +37,7 @@ class Test_MultipleFlightpathControlWidget: @pytest.fixture(autouse=True) def setup(self, qtbot): self.window = msui.MSUIMainWindow() + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.create_new_flight_track() self.window.actionNewFlightTrack.trigger() @@ -46,10 +48,9 @@ def setup(self, qtbot): 0, rows=len(initial_waypoints), waypoints=initial_waypoints) self.widget = tv.MSUITopViewWindow(model=self.waypoints_model, mainwindow=self.window) + qtbot.add_widget(self.widget, before_close_func=set_force_close) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) - yield - self.window.hide() def test_initialization(self): widget = MultipleFlightpathControlWidget(parent=self.widget, diff --git a/tests/_test_msui/test_remotesensing.py b/tests/_test_msui/test_remotesensing.py index 4981a3439..eb13aa86c 100644 --- a/tests/_test_msui/test_remotesensing.py +++ b/tests/_test_msui/test_remotesensing.py @@ -137,6 +137,7 @@ def setup(self, qtbot): datetime.datetime(2023, 4, 15, 11, 18, 27, 735581)] self.solar_type = ('sun', 'total (horizon)') self.remote_widget = RemoteSensingControlWidget(view=self.view) + qtbot.add_widget(self.remote_widget) @pytest.mark.parametrize( "lon0, lat0, h0, lon1, lat1, h1, obs_azi, expected", diff --git a/tests/_test_msui/test_satellite_dockwidget.py b/tests/_test_msui/test_satellite_dockwidget.py index 92a661a29..02c69d81f 100644 --- a/tests/_test_msui/test_satellite_dockwidget.py +++ b/tests/_test_msui/test_satellite_dockwidget.py @@ -37,10 +37,9 @@ class Test_SatelliteDockWidget: def setup(self, qtbot): self.view = mock.Mock() self.window = sd.SatelliteControlWidget(view=self.view) + qtbot.add_widget(self.window) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) - yield - self.window.hide() def test_load(self): path = os.path.join(os.path.dirname(__file__), "../", "data", "satellite_predictor.txt") diff --git a/tests/_test_msui/test_sideview.py b/tests/_test_msui/test_sideview.py index 22b3302c7..4263ee274 100644 --- a/tests/_test_msui/test_sideview.py +++ b/tests/_test_msui/test_sideview.py @@ -35,16 +35,16 @@ from mslib.msui import flighttrack as ft import mslib.msui.sideview as tv from mslib.msui.mpl_qtwidget import _DEFAULT_SETTINGS_SIDEVIEW +from tests.utils import set_force_close class Test_MSS_SV_OptionsDialog: @pytest.fixture(autouse=True) def setup(self, qtbot): self.window = tv.MSUI_SV_OptionsDialog(settings=_DEFAULT_SETTINGS_SIDEVIEW) + qtbot.add_widget(self.window) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) - yield - self.window.hide() def test_show(self): pass @@ -81,10 +81,9 @@ def setup(self, qtbot): 0, rows=len(initial_waypoints), waypoints=initial_waypoints) self.window = tv.MSUISideViewWindow(model=waypoints_model) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) - yield - self.window.hide() def test_open_wms(self): self.window.cbTools.currentIndexChanged.emit(1) @@ -137,13 +136,13 @@ def setup(self, qtbot, mswms_server): waypoints_model.insertRows( 0, rows=len(initial_waypoints), waypoints=initial_waypoints) self.window = tv.MSUISideViewWindow(model=waypoints_model) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) self.window.cbTools.currentIndexChanged.emit(1) self.wms_control = self.window.docks[0].widget() self.wms_control.multilayers.cbWMS_URL.setEditText("") yield - self.window.hide() shutil.rmtree(self.tempdir) def query_server(self, qtbot, url): diff --git a/tests/_test_msui/test_suffix.py b/tests/_test_msui/test_suffix.py index 4f4fc580f..54e532178 100644 --- a/tests/_test_msui/test_suffix.py +++ b/tests/_test_msui/test_suffix.py @@ -37,10 +37,9 @@ class Test_SuffixChange: @pytest.fixture(autouse=True) def setup(self, qtbot): self.window = tv.MSUI_SV_OptionsDialog(settings=_DEFAULT_SETTINGS_SIDEVIEW) + qtbot.add_widget(self.window) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) - yield - self.window.hide() def test_suffixchange(self): suffix = [' hPa', ' km', ' hft'] diff --git a/tests/_test_msui/test_tableview.py b/tests/_test_msui/test_tableview.py index 6f14668e8..a704eaa25 100644 --- a/tests/_test_msui/test_tableview.py +++ b/tests/_test_msui/test_tableview.py @@ -32,6 +32,7 @@ from PyQt5 import QtWidgets, QtCore, QtTest from mslib.msui import flighttrack as ft from mslib.msui.performance_settings import DEFAULT_PERFORMANCE +from tests.utils import set_force_close import mslib.msui.tableview as tv @@ -50,11 +51,10 @@ def setup(self, qtbot): 0, rows=len(initial_waypoints), waypoints=initial_waypoints) self.window = tv.MSUITableViewWindow(model=waypoints_model) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) - yield - self.window.hide() def test_open_hex(self): """ diff --git a/tests/_test_msui/test_topview.py b/tests/_test_msui/test_topview.py index 88aaf5e40..123cfdc48 100644 --- a/tests/_test_msui/test_topview.py +++ b/tests/_test_msui/test_topview.py @@ -35,16 +35,16 @@ from mslib.msui import flighttrack as ft from mslib.msui.msui import MSUIMainWindow from mslib.msui.mpl_qtwidget import _DEFAULT_SETTINGS_TOPVIEW +from tests.utils import set_force_close class Test_MSS_TV_MapAppearanceDialog: @pytest.fixture(autouse=True) def setup(self, qtbot): self.window = tv.MSUI_TV_MapAppearanceDialog(settings=_DEFAULT_SETTINGS_TOPVIEW) + qtbot.add_widget(self.window) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) - yield - self.window.hide() def test_show(self): pass @@ -57,15 +57,15 @@ class Test_MSSTopViewWindow: @pytest.fixture(autouse=True) def setup(self, qtbot): mainwindow = MSUIMainWindow() + qtbot.add_widget(mainwindow, before_close_func=set_force_close) initial_waypoints = [ft.Waypoint(40., 25., 0), ft.Waypoint(60., -10., 0), ft.Waypoint(40., 10, 0)] waypoints_model = ft.WaypointsTableModel("") waypoints_model.insertRows( 0, rows=len(initial_waypoints), waypoints=initial_waypoints) self.window = tv.MSUITopViewWindow(model=waypoints_model, mainwindow=mainwindow) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) - yield - self.window.hide() def test_open_wms(self): self.window.cbTools.currentIndexChanged.emit(1) @@ -211,14 +211,15 @@ def setup(self, qtbot, mswms_server): 0, rows=len(initial_waypoints), waypoints=initial_waypoints) mainwindow = MSUIMainWindow() + qtbot.add_widget(mainwindow, before_close_func=set_force_close) self.window = tv.MSUITopViewWindow(model=waypoints_model, mainwindow=mainwindow) + qtbot.add_widget(self.window, before_close_func=set_force_close) self.window.show() QtTest.QTest.qWaitForWindowExposed(self.window) self.window.cbTools.currentIndexChanged.emit(1) self.wms_control = self.window.docks[0].widget() self.wms_control.multilayers.cbWMS_URL.setEditText("") yield - self.window.hide() shutil.rmtree(self.tempdir) def query_server(self, qtbot, url): @@ -241,16 +242,14 @@ def test_server_getmap(self, qtbot): class Test_MSUITopViewWindow: - @pytest.fixture(autouse=True) - def setup(self, qtbot): - pass - - def test_kwargs_update_does_not_harm(self): + def test_kwargs_update_does_not_harm(self, qtbot): initial_waypoints = [ft.Waypoint(40., 25., 0), ft.Waypoint(60., -10., 0), ft.Waypoint(40., 10, 0)] waypoints_model = ft.WaypointsTableModel("") waypoints_model.insertRows(0, rows=len(initial_waypoints), waypoints=initial_waypoints) mainwindow = MSUIMainWindow() - self.window = tv.MSUITopViewWindow(model=waypoints_model, mainwindow=mainwindow) + qtbot.add_widget(mainwindow, before_close_func=set_force_close) + window = tv.MSUITopViewWindow(model=waypoints_model, mainwindow=mainwindow) + qtbot.add_widget(window, before_close_func=set_force_close) # user_options is a global var from mslib.utils.config import user_options diff --git a/tests/_test_msui/test_updater.py b/tests/_test_msui/test_updater.py index 452ee88d7..8272f9f62 100644 --- a/tests/_test_msui/test_updater.py +++ b/tests/_test_msui/test_updater.py @@ -128,8 +128,9 @@ def test_exception(self): @mock.patch("subprocess.Popen", new=SubprocessSameMock) @mock.patch("subprocess.run", new=SubprocessSameMock) @mock.patch("PyQt5.QtWidgets.QMessageBox.information", return_value=QtWidgets.QMessageBox.Yes) - def test_ui(self, mock): + def test_ui(self, mock, qtbot): ui = UpdaterUI() + qtbot.add_widget(ui) ui.updater.on_update_available.emit("", "") assert ui.statusLabel.text() == "Update successful. Please restart MSS." assert ui.btRestart.isEnabled() diff --git a/tests/_test_msui/test_wms_capabilities.py b/tests/_test_msui/test_wms_capabilities.py index a512a0b79..03de07c36 100644 --- a/tests/_test_msui/test_wms_capabilities.py +++ b/tests/_test_msui/test_wms_capabilities.py @@ -49,20 +49,21 @@ def setup(self, qtbot): self.capabilities.provider.contact.city = None yield - def start_window(self): + def start_window(self, qtbot): self.window = wc.WMSCapabilitiesBrowser( url="http://example.com", capabilities=self.capabilities) + qtbot.add_widget(self.window) QtTest.QTest.qWaitForWindowExposed(self.window) - def test_window_start(self): - self.start_window() + def test_window_start(self, qtbot): + self.start_window(qtbot) - def test_window_contact_none(self): + def test_window_contact_none(self, qtbot): self.capabilities.provider.contact = None - self.start_window() + self.start_window(qtbot) - def test_switch_view(self): - self.start_window() + def test_switch_view(self, qtbot): + self.start_window(qtbot) QtTest.QTest.mouseClick(self.window.cbFullView, QtCore.Qt.LeftButton) QtTest.QTest.mouseClick(self.window.cbFullView, QtCore.Qt.LeftButton) diff --git a/tests/_test_msui/test_wms_control.py b/tests/_test_msui/test_wms_control.py index 86b4c18c1..ffaf1610b 100644 --- a/tests/_test_msui/test_wms_control.py +++ b/tests/_test_msui/test_wms_control.py @@ -56,7 +56,7 @@ def _with_mswms_server(self, mswms_server): parsed_url = urllib.parse.urlparse(self.url) self.scheme, self.host, self.port = parsed_url.scheme, parsed_url.hostname, parsed_url.port - def _setup(self, widget_type): + def _setup(self, widget_type, qtbot): wc.WMS_SERVICE_CACHE = {} if widget_type == "hsec": self.view = HSecViewMockup() @@ -73,6 +73,7 @@ def _setup(self, widget_type): waypoints_model.insertRows(0, rows=len(initial_waypoints), waypoints=initial_waypoints) self.window = wc.VSecWMSControlWidget( view=self.view, wms_cache=self.tempdir, waypoints_model=waypoints_model) + qtbot.add_widget(self.window) self.window.show() # Remove all previous cached URLs @@ -84,7 +85,6 @@ def _setup(self, widget_type): QtTest.QTest.mouseClick(self.window.cbCacheEnabled, QtCore.Qt.LeftButton) def _teardown(self): - self.window.hide() shutil.rmtree(self.tempdir) def query_server(self, qtbot, url): @@ -98,7 +98,7 @@ def query_server(self, qtbot, url): class Test_HSecWMSControlWidget(WMSControlWidgetSetup): @pytest.fixture(autouse=True) def setup(self, qtbot): - self._setup("hsec") + self._setup("hsec", qtbot) yield self._teardown() @@ -410,7 +410,7 @@ def test_server_no_thread(self, mockthread, qtbot): class Test_VSecWMSControlWidget(WMSControlWidgetSetup): @pytest.fixture(autouse=True) def setup(self, qtbot): - self._setup("vsec") + self._setup("vsec", qtbot) yield self._teardown() @@ -498,6 +498,7 @@ class TestWMSControlWidgetSetupSimple: def setup(self, qtbot): self.view = HSecViewMockup() self.window = wc.HSecWMSControlWidget(view=self.view) + qtbot.add_widget(self.window) self.window.show() # Remove all previous cached URLs @@ -505,9 +506,6 @@ def setup(self, qtbot): server = self.window.multilayers.listLayers.findItems(url, QtCore.Qt.MatchFixedString)[0] self.window.multilayers.delete_server(server) - yield - self.window.hide() - def test_xml(self): testxml = self.xml.format("", self.srs_base, self.dimext_time + self.dimext_inittime + self.dimext_elevation) self.window.activate_wms(wc.MSUIWebMapService(None, version='1.1.1', xml=testxml)) diff --git a/tests/fixtures.py b/tests/fixtures.py index 88891a1b4..b50505eaa 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -32,7 +32,6 @@ import eventlet import eventlet.wsgi -from PyQt5 import QtWidgets from contextlib import contextmanager from mslib.mscolab.conf import mscolab_settings from mslib.mscolab.server import APP, initialize_managers @@ -41,8 +40,8 @@ from tests.utils import is_url_response_ok -@pytest.fixture -def fail_if_open_message_boxes_left(): +@contextmanager +def _fail_if_open_message_boxes_left(): # Mock every MessageBox widget in the test suite to avoid unwanted freezes on unhandled error popups etc. with mock.patch("PyQt5.QtWidgets.QMessageBox.question") as q, \ mock.patch("PyQt5.QtWidgets.QMessageBox.information") as i, \ @@ -57,29 +56,45 @@ def fail_if_open_message_boxes_left(): pytest.fail(f"An unhandled message box popped up during your test!\n{summary}") -@pytest.fixture -def close_remaining_widgets(): +@contextmanager +def _fail_if_any_widgets_left_open(qapp, qtbot): yield - # Try to close all remaining widgets after each test - for qobject in set(QtWidgets.QApplication.topLevelWindows() + QtWidgets.QApplication.topLevelWidgets()): - try: - qobject.destroy() - # Some objects deny permission, pass in that case - except RuntimeError: - pass + + def assert_no_widgets_left_open(): + widgets = set(qapp.topLevelWindows() + qapp.topLevelWidgets()) + assert len(widgets) == 0, f"There are Qt widgets left open at the end of the test!\n{widgets=}" + qtbot.wait_until(assert_no_widgets_left_open) + + +@contextmanager +def _close_remaining_widgets(qapp, qtbot): + yield + for widget in set(qapp.topLevelWindows() + qapp.topLevelWidgets()): + widget.close() + widget.deleteLater() + qtbot.wait_until(lambda: len(qapp.topLevelWidgets()) == 0) @pytest.fixture -def qtbot(qtbot, fail_if_open_message_boxes_left, close_remaining_widgets): - """Fixture that re-defines the qtbot fixture from pytest-qt with additional checks.""" - yield qtbot - # Wait for a while after the requesting test has finished. At time of writing this - # is required to (mostly) stabilize the coverage reports, because tests don't - # properly close their Qt-related stuff and therefore there is no guarantee about - # what the Qt event loop has or hasn't done yet. Waiting just gives it a bit more - # time to converge on the same result every time the tests are executed. This is a - # band-aid fix, the proper fix is to make sure each test cleans up after itself. - qtbot.wait(5000) +def qtbot(qapp, qtbot): + """Override of the qtbot fixture from pytest-qt. + + This fixture makes sure that every test using it will fail if there are either any + message boxes left open at the end or if any Qt widgets were not properly closed. + Afterwards it will destroy all remaining top-level widgets to make sure that they do + not affect the following tests. + """ + with _close_remaining_widgets(qapp, qtbot), \ + _fail_if_any_widgets_left_open(qapp, qtbot), \ + _fail_if_open_message_boxes_left(): + yield qtbot + # Wait for a while after the requesting test has finished. At time of writing this + # is required to (mostly) stabilize the coverage reports, because tests don't + # properly close their Qt-related stuff and therefore there is no guarantee about + # what the Qt event loop has or hasn't done yet. Waiting just gives it a bit more + # time to converge on the same result every time the tests are executed. This is a + # band-aid fix, the proper fix is to make sure each test cleans up after itself. + qtbot.wait(5000) @pytest.fixture(scope="session") diff --git a/tests/utils.py b/tests/utils.py index 7e0083f5d..35c4d0c0a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -185,3 +185,7 @@ def __init__(self, exc): def raise_exc(self, *args, **kwargs): raise self.exc + + +def set_force_close(widget): + widget.force_close = True