From 738a0a5e1b86ff940aa1add7bd76c912a56123c5 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Wed, 12 Aug 2026 17:03:17 +0100 Subject: [PATCH 01/25] added gui system test base class GuiSystemBase --- tests/system/gui_system_base.py | 43 +++++++++++++++++++++++++ tests/system/gui_system_loading_test.py | 14 ++++++++ 2 files changed, 57 insertions(+) create mode 100644 tests/system/gui_system_base.py create mode 100644 tests/system/gui_system_loading_test.py diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py new file mode 100644 index 00000000..41007dbf --- /dev/null +++ b/tests/system/gui_system_base.py @@ -0,0 +1,43 @@ +import os +import unittest +from collections.abc import Callable + +from PyQt6.QtTest import QTest +from PyQt6.QtWidgets import QApplication + +from rascal2.ui.view import MainWindowView + +SHOW_DELAY = 10 # Can be increased to watch tests +SHORT_DELAY = 100 + + +def wait_until(test_func: Callable[[], bool], + delay=0.1, + max_retry=100, + message: str = "wait_until reached max retries"): + """ + Repeat test_func every delay seconds until it becomes true. Raises RuntimeError if max_retry is reached. + """ + for _ in range(max_retry): + if test_func(): + return True + QTest.qWait(int(delay * 1000)) + raise RuntimeError(message) + + +class GuiSystemBase(unittest.TestCase): + app: QApplication + + def setUp(self) -> None: + os.environ["START_PROCESSES"] = "False" + self.main_window = MainWindowView() + self.main_window.show() + QTest.qWait(SHORT_DELAY) + + def tearDown(self) -> None: + self.main_window.close() + wait_until(lambda: not self.main_window.isVisible(), + delay=0.05, + max_retry=60, + message="Main window did not close within 3 seconds") + del self.main_window diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py new file mode 100644 index 00000000..0e19ff47 --- /dev/null +++ b/tests/system/gui_system_loading_test.py @@ -0,0 +1,14 @@ +from tests.system.gui_system_base import GuiSystemBase + + +class TestGuiSystemLoading(GuiSystemBase): + leak_count_limit = 2 + + def setUp(self) -> None: + super().setUp() + + def tearDown(self) -> None: + super().tearDown() + + def test_load(self): + pass From 6ec42bad661897a2ee1083f98eee3d4ad50b05dd Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Thu, 20 Aug 2026 16:28:40 +0100 Subject: [PATCH 02/25] pytests differs between system and unit tests using flags --run-system-tests and --run-unit-tests --- tests/conftest.py | 37 +++++++++++++++++++++++++++++++++ tests/system/gui_system_base.py | 4 +--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 6d7760a8..d09dcaf7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import os import tempfile from pathlib import Path from unittest.mock import patch @@ -47,3 +48,39 @@ def teardown_mock_setting(): target.stop() request.addfinalizer(teardown_mock_setting) + + +def pytest_addoption(parser): + parser.addoption("--run-system-tests", action="store_true", default=False, help="Run GUI system tests offscreen") + parser.addoption("--run-system-tests-show", action="store_true", default=False, help="Run GUI system tests") + parser.addoption("--run-unit-tests", action="store_true", default=False, help="Run unit tests") + + +def pytest_configure(config): + config.addinivalue_line("markers", "system: GUI system tests") + config.addinivalue_line("markers", "unit: unit tests") + + +allowed_markers = [] +skipped_tests = [] + + +def pytest_collection_modifyitems(config, items): + if config.getoption("--run-system-tests"): + allowed_markers.append(pytest.mark.system.mark) + os.environ["QT_QPA_PLATFORM"] = "minimal" + if config.getoption("--run-system-tests-show"): + allowed_markers.append(pytest.mark.system.mark) + os.environ["QT_QPA_PLATFORM"] = "" + if config.getoption("--run-unit-tests") or len(allowed_markers) == 0: + allowed_markers.append(pytest.mark.unit.mark) + for item in items: + if "gui_system" in item.nodeid: + item.add_marker(pytest.mark.system) + else: + item.add_marker(pytest.mark.unit) + if any(mark in allowed_markers for mark in item.own_markers): + pass + else: + item.add_marker(pytest.mark.skip(reason="Test not selected")) + skipped_tests.append(item.nodeid) diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index 41007dbf..44e56ffe 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -15,9 +15,7 @@ def wait_until(test_func: Callable[[], bool], delay=0.1, max_retry=100, message: str = "wait_until reached max retries"): - """ - Repeat test_func every delay seconds until it becomes true. Raises RuntimeError if max_retry is reached. - """ + """Repeat test_func every delay seconds until it becomes true. Raises RuntimeError if max_retry is reached.""" for _ in range(max_retry): if test_func(): return True From f30421af74946df820c68fa45bca1756bf14ceeb Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Thu, 20 Aug 2026 16:32:04 +0100 Subject: [PATCH 03/25] ruff fix --- tests/system/gui_system_base.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index 44e56ffe..e708ecb3 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -11,10 +11,9 @@ SHORT_DELAY = 100 -def wait_until(test_func: Callable[[], bool], - delay=0.1, - max_retry=100, - message: str = "wait_until reached max retries"): +def wait_until( + test_func: Callable[[], bool], delay=0.1, max_retry=100, message: str = "wait_until reached max retries" +): """Repeat test_func every delay seconds until it becomes true. Raises RuntimeError if max_retry is reached.""" for _ in range(max_retry): if test_func(): @@ -34,8 +33,10 @@ def setUp(self) -> None: def tearDown(self) -> None: self.main_window.close() - wait_until(lambda: not self.main_window.isVisible(), - delay=0.05, - max_retry=60, - message="Main window did not close within 3 seconds") + wait_until( + lambda: not self.main_window.isVisible(), + delay=0.05, + max_retry=60, + message="Main window did not close within 3 seconds", + ) del self.main_window From 26e18fb7f429f857a204441234868e893241bf4f Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 21 Aug 2026 11:51:57 +0100 Subject: [PATCH 04/25] add exception handling to GuiSystemBase --- tests/system/gui_system_base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index e708ecb3..c4f223a2 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -1,4 +1,5 @@ import os +import sys import unittest from collections.abc import Callable @@ -27,11 +28,18 @@ class GuiSystemBase(unittest.TestCase): def setUp(self) -> None: os.environ["START_PROCESSES"] = "False" + self.no_exceptions = True + + sys.excepthook = self.exception_hook self.main_window = MainWindowView() self.main_window.show() QTest.qWait(SHORT_DELAY) def tearDown(self) -> None: + if not self.no_exceptions: + raise Exception("An exception occurred in a PyQt slot") + sys.excepthook = sys.__excepthook__ + self.main_window.close() wait_until( lambda: not self.main_window.isVisible(), @@ -40,3 +48,7 @@ def tearDown(self) -> None: message="Main window did not close within 3 seconds", ) del self.main_window + + def exception_hook(self, exc_type, exc_value, exc_traceback): + self.no_exceptions = False + sys.__excepthook__(exc_type, exc_value, exc_traceback) From 72e0f4d5ca9469debda46e53ca43deb041b609ac Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 21 Aug 2026 12:55:19 +0100 Subject: [PATCH 05/25] added TestGuiSystemMainWindow test class --- tests/system/gui_system_main_window_test.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/system/gui_system_main_window_test.py diff --git a/tests/system/gui_system_main_window_test.py b/tests/system/gui_system_main_window_test.py new file mode 100644 index 00000000..e6721f86 --- /dev/null +++ b/tests/system/gui_system_main_window_test.py @@ -0,0 +1,15 @@ +from tests.system.gui_system_base import GuiSystemBase + + +class TestGuiSystemMainWindow(GuiSystemBase): + + def setUp(self) -> None: + super().setUp() + + def tearDown(self) -> None: + super().tearDown() + + def test_main_window(self): + self.main_window.presenter.create_project("project", ".") + names = [win.windowTitle() for win in self.main_window.mdi.subWindowList()] + assert names == ["Fitting Controls", "Terminal", "Project", "Plots"] From f59b8e89d39bf4b646415caff994e882e20b47a5 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 21 Aug 2026 14:21:13 +0100 Subject: [PATCH 06/25] ruff fix --- tests/system/gui_system_loading_test.py | 2 -- tests/system/gui_system_main_window_test.py | 1 - 2 files changed, 3 deletions(-) diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index 0e19ff47..dc6a93d4 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -2,8 +2,6 @@ class TestGuiSystemLoading(GuiSystemBase): - leak_count_limit = 2 - def setUp(self) -> None: super().setUp() diff --git a/tests/system/gui_system_main_window_test.py b/tests/system/gui_system_main_window_test.py index e6721f86..22facd46 100644 --- a/tests/system/gui_system_main_window_test.py +++ b/tests/system/gui_system_main_window_test.py @@ -2,7 +2,6 @@ class TestGuiSystemMainWindow(GuiSystemBase): - def setUp(self) -> None: super().setUp() From c99d9e7d29835d2c219d903070d3e089b22c3e6f Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 21 Aug 2026 17:13:20 +0100 Subject: [PATCH 07/25] added TestGuiSystemLoading::test_load --- tests/system/gui_system_base.py | 2 ++ tests/system/gui_system_loading_test.py | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index c4f223a2..ebfc8721 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -27,6 +27,7 @@ class GuiSystemBase(unittest.TestCase): app: QApplication def setUp(self) -> None: + self.start_processes_old = os.getenv("START_PROCESSES") os.environ["START_PROCESSES"] = "False" self.no_exceptions = True @@ -48,6 +49,7 @@ def tearDown(self) -> None: message="Main window did not close within 3 seconds", ) del self.main_window + os.environ["START_PROCESSES"] = self.start_processes_old def exception_hook(self, exc_type, exc_value, exc_traceback): self.no_exceptions = False diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index dc6a93d4..2b228361 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -1,4 +1,11 @@ -from tests.system.gui_system_base import GuiSystemBase +import time +from pathlib import Path + +from PyQt6.QtTest import QTest +from PyQt6 import QtCore + +from tests.system.gui_system_base import GuiSystemBase, SHOW_DELAY, SHORT_DELAY +from rascal2.dialogs.startup_dialog import LoadDialog class TestGuiSystemLoading(GuiSystemBase): @@ -9,4 +16,10 @@ def tearDown(self) -> None: super().tearDown() def test_load(self): - pass + QTest.qWait(SHORT_DELAY) + self.main_window.startup_dlg.import_project_button.click() + load_dialog = self.main_window.findChild(LoadDialog) + load_dialog.tabs.setCurrentIndex(2) + load_dialog.example_list_widget.itemClicked.emit(load_dialog.example_list_widget.item(0)) + QTest.qWait(SHORT_DELAY) + assert self.main_window.presenter.model.project.name == "DSPC Standard Layers" From a3dff02c0a1ff78c8a0c22beec0c5ec41aaedc7b Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 21 Aug 2026 17:56:55 +0100 Subject: [PATCH 08/25] ruff fix --- tests/system/gui_system_loading_test.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index 2b228361..ce56062b 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -1,11 +1,7 @@ -import time -from pathlib import Path - from PyQt6.QtTest import QTest -from PyQt6 import QtCore -from tests.system.gui_system_base import GuiSystemBase, SHOW_DELAY, SHORT_DELAY from rascal2.dialogs.startup_dialog import LoadDialog +from tests.system.gui_system_base import SHORT_DELAY, GuiSystemBase class TestGuiSystemLoading(GuiSystemBase): From b4f741ef8dda8cd56341cb56391acb9a063c3a96 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Thu, 3 Sep 2026 11:06:38 +0100 Subject: [PATCH 09/25] added TestGuiSystemLoading::test_run and automatic closing of unsaved project in testing --- tests/system/gui_system_base.py | 23 ++++++++++++++++++--- tests/system/gui_system_loading_test.py | 18 +++++++++++++++- tests/system/gui_system_main_window_test.py | 14 +++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index ebfc8721..e4335938 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -3,8 +3,9 @@ import unittest from collections.abc import Callable +from PyQt6.QtCore import Qt, QTimer from PyQt6.QtTest import QTest -from PyQt6.QtWidgets import QApplication +from PyQt6.QtWidgets import QApplication, QMessageBox from rascal2.ui.view import MainWindowView @@ -24,7 +25,7 @@ def wait_until( class GuiSystemBase(unittest.TestCase): - app: QApplication + app: QApplication = QApplication.instance() def setUp(self) -> None: self.start_processes_old = os.getenv("START_PROCESSES") @@ -40,7 +41,7 @@ def tearDown(self) -> None: if not self.no_exceptions: raise Exception("An exception occurred in a PyQt slot") sys.excepthook = sys.__excepthook__ - + QTimer.singleShot(SHORT_DELAY, lambda: self._click_messagebox("Cancel")) self.main_window.close() wait_until( lambda: not self.main_window.isVisible(), @@ -54,3 +55,19 @@ def tearDown(self) -> None: def exception_hook(self, exc_type, exc_value, exc_traceback): self.no_exceptions = False sys.__excepthook__(exc_type, exc_value, exc_traceback) + + @classmethod + def _click_messagebox(cls, button_text: str): + """Needs to be queued with QTimer.singleShot before triggering the message box.""" + for widget in cls.app.topLevelWidgets(): + print(f"{widget=}") + if isinstance(widget, QMessageBox) and widget.isVisible(): + for button in widget.buttons(): + if button.text().replace("&", "") == button_text: + QTest.mouseClick(button, Qt.MouseButton.LeftButton) + return + button_texts = [button.text() for button in widget.buttons()] + raise ValueError( + f"Could not find button '{button_text}' in {button_texts}.\n" + f"Message box: {widget.windowTitle()} {widget.text()}" + ) diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index ce56062b..52f75f21 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -1,7 +1,11 @@ +from pathlib import Path + from PyQt6.QtTest import QTest from rascal2.dialogs.startup_dialog import LoadDialog -from tests.system.gui_system_base import SHORT_DELAY, GuiSystemBase +from tests.system.gui_system_base import SHORT_DELAY, GuiSystemBase, wait_until + +EXAMPLES_PATH = Path(__file__, "../../../examples").resolve().as_posix() class TestGuiSystemLoading(GuiSystemBase): @@ -19,3 +23,15 @@ def test_load(self): load_dialog.example_list_widget.itemClicked.emit(load_dialog.example_list_widget.item(0)) QTest.qWait(SHORT_DELAY) assert self.main_window.presenter.model.project.name == "DSPC Standard Layers" + + def test_run(self): + QTest.qWait(SHORT_DELAY) + self.main_window.startup_dlg.import_project_button.click() + load_dialog = self.main_window.findChild(LoadDialog) + load_dialog.tabs.setCurrentIndex(2) + load_dialog.example_list_widget.itemClicked.emit(load_dialog.example_list_widget.item(0)) + wait_until(lambda: self.main_window.controls_widget.chi_squared.text() != "") + self.main_window.controls_widget.run_button.click() + wait_until(lambda: not self.main_window.controls_widget.run_button.isChecked()) + assert self.main_window.controls_widget.chi_squared.text() == "132.939" + assert self.main_window.presenter.runner.error is None diff --git a/tests/system/gui_system_main_window_test.py b/tests/system/gui_system_main_window_test.py index 22facd46..cf11bcdc 100644 --- a/tests/system/gui_system_main_window_test.py +++ b/tests/system/gui_system_main_window_test.py @@ -1,3 +1,4 @@ +from rascal2 import RASCAL2_VERSION from tests.system.gui_system_base import GuiSystemBase @@ -12,3 +13,16 @@ def test_main_window(self): self.main_window.presenter.create_project("project", ".") names = [win.windowTitle() for win in self.main_window.mdi.subWindowList()] assert names == ["Fitting Controls", "Terminal", "Project", "Plots"] + assert ( + self.main_window.terminal_widget.text_area.toPlainText() + == f""" + ███████████ █████████ █████████ █████ +░░███░░░░░███ ███░░░░░███ ███░░░░░███ ░░███ + ░███ ░███ ██████ █████ ███ ░░░ ░███ ░███ ░███ + ░██████████ ░░░░░███ ███░░ ░███ ░███████████ ░███ + ░███░░░░░███ ███████ ░░█████ ░███ ░███░░░░░███ ░███ + ░███ ░███ ███░░███ ░░░░███░░███ ███ ░███ ░███ ░███ █ + █████ █████░░████████ ██████ ░░█████████ █████ █████ ███████████ +░░░░░ ░░░░░ ░░░░░░░░ ░░░░░░ ░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░░░ +RasCAL-2: software for neutron reflectivity calculations v{RASCAL2_VERSION}""" + ) From 1ff717f3a38ff6d720baf93d0a4d3043e948cd5e Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Thu, 3 Sep 2026 16:24:29 +0100 Subject: [PATCH 10/25] fix issue with START_PROCESSES environment variable and tests hanging due to matlab runner not exiting properly --- tests/system/gui_system_base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index e4335938..c4670988 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -60,7 +60,6 @@ def exception_hook(self, exc_type, exc_value, exc_traceback): def _click_messagebox(cls, button_text: str): """Needs to be queued with QTimer.singleShot before triggering the message box.""" for widget in cls.app.topLevelWidgets(): - print(f"{widget=}") if isinstance(widget, QMessageBox) and widget.isVisible(): for button in widget.buttons(): if button.text().replace("&", "") == button_text: From d821341c73b15a858326fff40a2ee0e88a25a510 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Wed, 9 Sep 2026 13:21:09 +0100 Subject: [PATCH 11/25] unit and system tests run by default, can skip tests with --skip_system_tests --- tests/conftest.py | 16 +++++---------- tests/test_ui.py | 51 ----------------------------------------------- 2 files changed, 5 insertions(+), 62 deletions(-) delete mode 100644 tests/test_ui.py diff --git a/tests/conftest.py b/tests/conftest.py index d09dcaf7..3c8d3e33 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ -import os import tempfile from pathlib import Path from unittest.mock import patch @@ -51,9 +50,7 @@ def teardown_mock_setting(): def pytest_addoption(parser): - parser.addoption("--run-system-tests", action="store_true", default=False, help="Run GUI system tests offscreen") - parser.addoption("--run-system-tests-show", action="store_true", default=False, help="Run GUI system tests") - parser.addoption("--run-unit-tests", action="store_true", default=False, help="Run unit tests") + parser.addoption("--skip_system_tests", action="store_true", default=False, help="Skip GUI system tests") def pytest_configure(config): @@ -66,14 +63,11 @@ def pytest_configure(config): def pytest_collection_modifyitems(config, items): - if config.getoption("--run-system-tests"): - allowed_markers.append(pytest.mark.system.mark) - os.environ["QT_QPA_PLATFORM"] = "minimal" - if config.getoption("--run-system-tests-show"): - allowed_markers.append(pytest.mark.system.mark) - os.environ["QT_QPA_PLATFORM"] = "" - if config.getoption("--run-unit-tests") or len(allowed_markers) == 0: + if config.getoption("--skip_system_tests"): allowed_markers.append(pytest.mark.unit.mark) + if len(allowed_markers) == 0: + allowed_markers.append(pytest.mark.unit.mark) + allowed_markers.append(pytest.mark.system.mark) for item in items: if "gui_system" in item.nodeid: item.add_marker(pytest.mark.system) diff --git a/tests/test_ui.py b/tests/test_ui.py deleted file mode 100644 index 88b57760..00000000 --- a/tests/test_ui.py +++ /dev/null @@ -1,51 +0,0 @@ -import sys - -import pytest - -from rascal2.ui.view import MainWindowView - - -@pytest.fixture -def make_main_window(request): - def make(): - no_exceptions = True - - def exception_hook(exc_type, exc_value, exc_traceback): - nonlocal no_exceptions - no_exceptions = False - sys.__excepthook__(exc_type, exc_value, exc_traceback) - - sys.excepthook = exception_hook - - window = MainWindowView() - - def cleanup(): - nonlocal no_exceptions - if not no_exceptions: - raise Exception("An exception occurred in a PyQt slot") - sys.excepthook = sys.__excepthook__ - - request.addfinalizer(cleanup) - return window - - return make - - -def test_integration(qt_application, make_main_window): - _ = qt_application - window = make_main_window() - window.show() - print("test_integration 1") - - window.presenter.create_project("project", ".") - print("test_integration 2") - names = [win.windowTitle() for win in window.mdi.subWindowList()] - print("test_integration 3") - # QMDIArea is first in last out hence the reversed list - assert names == ["Fitting Controls", "Terminal", "Project", "Plots"] - print("test_integration 4") - - # Work through the different sections of the UI - - window.close() - print("test_integration 5") From 9ba39a93cf25fa068905889357b3d12ef03ffb9b Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Wed, 9 Sep 2026 17:35:08 +0100 Subject: [PATCH 12/25] fix problem with logging not printing to terminal and Matlab Engine not exiting --- rascal2/core/runner.py | 7 ++++--- tests/system/gui_system_base.py | 8 +++++--- tests/system/gui_system_loading_test.py | 6 +++++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/rascal2/core/runner.py b/rascal2/core/runner.py index 502c4801..e80a5d8a 100644 --- a/rascal2/core/runner.py +++ b/rascal2/core/runner.py @@ -81,7 +81,7 @@ def create_process(self): """Create process and multiprocessing event.""" self.go_event = Event() self.exit_event = Event() - matlab_helper = MatlabHelper() + self.matlab_helper = MatlabHelper() self.process = Process( target=run, args=( @@ -91,8 +91,8 @@ def create_process(self): self.plot_queue, self.go_event, self.exit_event, - matlab_helper.ready_event, - matlab_helper.engine_output, + self.matlab_helper.ready_event, + self.matlab_helper.engine_output, ), ) @@ -199,6 +199,7 @@ def stop(self): self.process.kill() self.process = None self.clear_queues_and_events() + self.matlab_helper.close_event.set() def init_matlab_engine(engine_ready, engine_output, msg_queue): diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index c4670988..1eb45d05 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -7,6 +7,7 @@ from PyQt6.QtTest import QTest from PyQt6.QtWidgets import QApplication, QMessageBox +from rascal2.config import setup_logging from rascal2.ui.view import MainWindowView SHOW_DELAY = 10 # Can be increased to watch tests @@ -28,6 +29,7 @@ class GuiSystemBase(unittest.TestCase): app: QApplication = QApplication.instance() def setUp(self) -> None: + setup_logging() self.start_processes_old = os.getenv("START_PROCESSES") os.environ["START_PROCESSES"] = "False" self.no_exceptions = True @@ -41,13 +43,13 @@ def tearDown(self) -> None: if not self.no_exceptions: raise Exception("An exception occurred in a PyQt slot") sys.excepthook = sys.__excepthook__ - QTimer.singleShot(SHORT_DELAY, lambda: self._click_messagebox("Cancel")) + QTimer.singleShot(SHORT_DELAY, lambda: self._click_messagebox("Discard")) self.main_window.close() wait_until( lambda: not self.main_window.isVisible(), delay=0.05, - max_retry=60, - message="Main window did not close within 3 seconds", + max_retry=200, + message="Main window did not close within 10 seconds", ) del self.main_window os.environ["START_PROCESSES"] = self.start_processes_old diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index 52f75f21..e449ea05 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -1,6 +1,7 @@ from pathlib import Path from PyQt6.QtTest import QTest +from PyQt6.QtWidgets import QApplication from rascal2.dialogs.startup_dialog import LoadDialog from tests.system.gui_system_base import SHORT_DELAY, GuiSystemBase, wait_until @@ -31,7 +32,10 @@ def test_run(self): load_dialog.tabs.setCurrentIndex(2) load_dialog.example_list_widget.itemClicked.emit(load_dialog.example_list_widget.item(0)) wait_until(lambda: self.main_window.controls_widget.chi_squared.text() != "") + self.main_window.controls_widget.update_chi_squared("") + QApplication.processEvents() self.main_window.controls_widget.run_button.click() - wait_until(lambda: not self.main_window.controls_widget.run_button.isChecked()) + wait_until(lambda: "Finished RAT" in self.main_window.terminal_widget.text_area.toPlainText()) + QTest.qWait(SHORT_DELAY) assert self.main_window.controls_widget.chi_squared.text() == "132.939" assert self.main_window.presenter.runner.error is None From ad570cc49082025633e59d6a9f76ad95bff3eaae Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Thu, 10 Sep 2026 13:19:40 +0100 Subject: [PATCH 13/25] fix issue with terminal being written to after deletion due to Logging --- tests/system/gui_system_base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index 1eb45d05..7269996e 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -1,3 +1,4 @@ +import logging import os import sys import unittest @@ -43,6 +44,8 @@ def tearDown(self) -> None: if not self.no_exceptions: raise Exception("An exception occurred in a PyQt slot") sys.excepthook = sys.__excepthook__ + LOGGER = logging.getLogger("rascal2") + LOGGER.handlers.clear() QTimer.singleShot(SHORT_DELAY, lambda: self._click_messagebox("Discard")) self.main_window.close() wait_until( From 2791bf424a4862f4b16dd1cc76bcf867978f0c1e Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Thu, 10 Sep 2026 13:53:14 +0100 Subject: [PATCH 14/25] ruff fix --- tests/system/gui_system_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index 7269996e..f7c49719 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -44,8 +44,8 @@ def tearDown(self) -> None: if not self.no_exceptions: raise Exception("An exception occurred in a PyQt slot") sys.excepthook = sys.__excepthook__ - LOGGER = logging.getLogger("rascal2") - LOGGER.handlers.clear() + logger = logging.getLogger("rascal2") + logger.handlers.clear() QTimer.singleShot(SHORT_DELAY, lambda: self._click_messagebox("Discard")) self.main_window.close() wait_until( From c6d6255f6baada7e27045ffe02148241fd276cea Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Thu, 10 Sep 2026 15:26:50 +0100 Subject: [PATCH 15/25] allow mulitple possible values for _click_messagebox --- tests/system/gui_system_base.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index f7c49719..90aabe0b 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -46,7 +46,7 @@ def tearDown(self) -> None: sys.excepthook = sys.__excepthook__ logger = logging.getLogger("rascal2") logger.handlers.clear() - QTimer.singleShot(SHORT_DELAY, lambda: self._click_messagebox("Discard")) + QTimer.singleShot(SHORT_DELAY, lambda: self._click_messagebox(["Discard", "Don't Save"])) self.main_window.close() wait_until( lambda: not self.main_window.isVisible(), @@ -62,16 +62,20 @@ def exception_hook(self, exc_type, exc_value, exc_traceback): sys.__excepthook__(exc_type, exc_value, exc_traceback) @classmethod - def _click_messagebox(cls, button_text: str): + def _click_messagebox(cls, button_text_list: list[str]): """Needs to be queued with QTimer.singleShot before triggering the message box.""" + no_buttons_found = True + button_texts = [] for widget in cls.app.topLevelWidgets(): if isinstance(widget, QMessageBox) and widget.isVisible(): - for button in widget.buttons(): - if button.text().replace("&", "") == button_text: - QTest.mouseClick(button, Qt.MouseButton.LeftButton) - return - button_texts = [button.text() for button in widget.buttons()] - raise ValueError( - f"Could not find button '{button_text}' in {button_texts}.\n" - f"Message box: {widget.windowTitle()} {widget.text()}" - ) + for button_text in button_text_list: + for button in widget.buttons(): + if button.text().replace("&", "") == button_text: + QTest.mouseClick(button, Qt.MouseButton.LeftButton) + no_buttons_found = False + button_texts = [button.text() for button in widget.buttons()] + if no_buttons_found: + raise ValueError( + f"Could not find buttons '{button_text_list} in {button_texts}'.\n" + f"Message box: {widget.windowTitle()} {widget.text()}" + ) From ac1dac710a4c21295f79b26bb90cb8e3ee93bc7c Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Thu, 10 Sep 2026 16:27:26 +0100 Subject: [PATCH 16/25] change expected chi_squared to 142.346 in test_run --- tests/system/gui_system_loading_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index e449ea05..8d18a5a4 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -37,5 +37,5 @@ def test_run(self): self.main_window.controls_widget.run_button.click() wait_until(lambda: "Finished RAT" in self.main_window.terminal_widget.text_area.toPlainText()) QTest.qWait(SHORT_DELAY) - assert self.main_window.controls_widget.chi_squared.text() == "132.939" + assert self.main_window.controls_widget.chi_squared.text() == "142.346" assert self.main_window.presenter.runner.error is None From 58422e264ee7f80d28c68f046bcb12c416ba9fa9 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 11 Sep 2026 13:52:22 +0100 Subject: [PATCH 17/25] test_run now runs each procedure and compares against expected values --- tests/system/gui_system_loading_test.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index 8d18a5a4..2f94e973 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -2,6 +2,7 @@ from PyQt6.QtTest import QTest from PyQt6.QtWidgets import QApplication +from parameterized import parameterized from rascal2.dialogs.startup_dialog import LoadDialog from tests.system.gui_system_base import SHORT_DELAY, GuiSystemBase, wait_until @@ -25,7 +26,14 @@ def test_load(self): QTest.qWait(SHORT_DELAY) assert self.main_window.presenter.model.project.name == "DSPC Standard Layers" - def test_run(self): + @parameterized.expand([ + ("calculate", 142.346, 1000), + ("simplex", 13.1816, 1000), + ("de", 10.4746, 1000), + ("ns", [9, 11], 4000), + ("dream", 9.67234, 1000), + ]) + def test_run(self, procedure_name, expected_chi, test_duration): QTest.qWait(SHORT_DELAY) self.main_window.startup_dlg.import_project_button.click() load_dialog = self.main_window.findChild(LoadDialog) @@ -33,9 +41,13 @@ def test_run(self): load_dialog.example_list_widget.itemClicked.emit(load_dialog.example_list_widget.item(0)) wait_until(lambda: self.main_window.controls_widget.chi_squared.text() != "") self.main_window.controls_widget.update_chi_squared("") + self.main_window.controls_widget.procedure_dropdown.setCurrentText(procedure_name) QApplication.processEvents() self.main_window.controls_widget.run_button.click() - wait_until(lambda: "Finished RAT" in self.main_window.terminal_widget.text_area.toPlainText()) + wait_until(lambda: "Finished RAT" in self.main_window.terminal_widget.text_area.toPlainText(), max_retry=test_duration) QTest.qWait(SHORT_DELAY) - assert self.main_window.controls_widget.chi_squared.text() == "142.346" + if isinstance(expected_chi, list): + assert expected_chi[0] <= float(self.main_window.controls_widget.chi_squared.text()) <= expected_chi[1] + else: + assert self.main_window.controls_widget.chi_squared.text() == str(expected_chi) assert self.main_window.presenter.runner.error is None From 93b63970a2de756fef329eac3682420e8d839fc3 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 11 Sep 2026 13:53:08 +0100 Subject: [PATCH 18/25] ruff fixes --- tests/system/gui_system_loading_test.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index 2f94e973..2dfdb71e 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -1,8 +1,8 @@ from pathlib import Path +from parameterized import parameterized from PyQt6.QtTest import QTest from PyQt6.QtWidgets import QApplication -from parameterized import parameterized from rascal2.dialogs.startup_dialog import LoadDialog from tests.system.gui_system_base import SHORT_DELAY, GuiSystemBase, wait_until @@ -26,13 +26,15 @@ def test_load(self): QTest.qWait(SHORT_DELAY) assert self.main_window.presenter.model.project.name == "DSPC Standard Layers" - @parameterized.expand([ - ("calculate", 142.346, 1000), - ("simplex", 13.1816, 1000), - ("de", 10.4746, 1000), - ("ns", [9, 11], 4000), - ("dream", 9.67234, 1000), - ]) + @parameterized.expand( + [ + ("calculate", 142.346, 1000), + ("simplex", 13.1816, 1000), + ("de", 10.4746, 1000), + ("ns", [9, 11], 4000), + ("dream", 9.67234, 1000), + ] + ) def test_run(self, procedure_name, expected_chi, test_duration): QTest.qWait(SHORT_DELAY) self.main_window.startup_dlg.import_project_button.click() @@ -44,7 +46,9 @@ def test_run(self, procedure_name, expected_chi, test_duration): self.main_window.controls_widget.procedure_dropdown.setCurrentText(procedure_name) QApplication.processEvents() self.main_window.controls_widget.run_button.click() - wait_until(lambda: "Finished RAT" in self.main_window.terminal_widget.text_area.toPlainText(), max_retry=test_duration) + wait_until( + lambda: "Finished RAT" in self.main_window.terminal_widget.text_area.toPlainText(), max_retry=test_duration + ) QTest.qWait(SHORT_DELAY) if isinstance(expected_chi, list): assert expected_chi[0] <= float(self.main_window.controls_widget.chi_squared.text()) <= expected_chi[1] From b3c02419ecb336f572e55fa799e6d39e61f1ef24 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 11 Sep 2026 15:22:24 +0100 Subject: [PATCH 19/25] added parameterized to requirements-dev.txt --- requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8ffbdcac..ca69e882 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,3 +3,4 @@ pytest-cov ruff Sphinx pydata-sphinx-theme +parameterized From ce241d022918ee4fcbe589ffe1646517b697a62b Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 11 Sep 2026 17:36:14 +0100 Subject: [PATCH 20/25] increase timing for test_run ns and dream procedures --- tests/system/gui_system_loading_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index 2dfdb71e..2ef25008 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -31,8 +31,8 @@ def test_load(self): ("calculate", 142.346, 1000), ("simplex", 13.1816, 1000), ("de", 10.4746, 1000), - ("ns", [9, 11], 4000), - ("dream", 9.67234, 1000), + ("ns", [9, 11], 6000), + ("dream", 9.67234, 2000), ] ) def test_run(self, procedure_name, expected_chi, test_duration): From b7166b5c8954c1292ffcbf4362fb2cab13bb4e4f Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Tue, 15 Sep 2026 11:58:58 +0100 Subject: [PATCH 21/25] add --run_slow_tests flag to dynamically skip tests with 'slow' in name --- tests/conftest.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3c8d3e33..33e9d3c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,11 +51,13 @@ def teardown_mock_setting(): def pytest_addoption(parser): parser.addoption("--skip_system_tests", action="store_true", default=False, help="Skip GUI system tests") + parser.addoption("--run_slow_tests", action="store_true", default=False, help="Run slow tests") def pytest_configure(config): config.addinivalue_line("markers", "system: GUI system tests") config.addinivalue_line("markers", "unit: unit tests") + config.addinivalue_line("markers", "slow: marks slow tests which only run on review") allowed_markers = [] @@ -63,18 +65,22 @@ def pytest_configure(config): def pytest_collection_modifyitems(config, items): - if config.getoption("--skip_system_tests"): - allowed_markers.append(pytest.mark.unit.mark) - if len(allowed_markers) == 0: - allowed_markers.append(pytest.mark.unit.mark) + allowed_markers.append(pytest.mark.unit.mark) + if not config.getoption("--skip_system_tests"): allowed_markers.append(pytest.mark.system.mark) + if config.getoption("--run_slow_tests"): + allowed_markers.append(pytest.mark.slow.mark) for item in items: if "gui_system" in item.nodeid: item.add_marker(pytest.mark.system) + if "slow" in item.nodeid: + item.add_marker(pytest.mark.slow) else: item.add_marker(pytest.mark.unit) - if any(mark in allowed_markers for mark in item.own_markers): - pass - else: - item.add_marker(pytest.mark.skip(reason="Test not selected")) - skipped_tests.append(item.nodeid) + for mark in item.own_markers: + if "parametrize" in mark.name: + continue + if mark not in allowed_markers: + item.add_marker(pytest.mark.skip(reason="Test not selected")) + skipped_tests.append(item.nodeid) + break From cd8f71cd42fd7084859905dc2de2716661eaa6cb Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Tue, 15 Sep 2026 11:59:49 +0100 Subject: [PATCH 22/25] mark Nested Sampler and DREAM runs as slow --- tests/system/gui_system_loading_test.py | 27 ++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index 2ef25008..9bb3d4ed 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -31,11 +31,36 @@ def test_load(self): ("calculate", 142.346, 1000), ("simplex", 13.1816, 1000), ("de", 10.4746, 1000), + ] + ) + def test_run(self, procedure_name, expected_chi, test_duration): + QTest.qWait(SHORT_DELAY) + self.main_window.startup_dlg.import_project_button.click() + load_dialog = self.main_window.findChild(LoadDialog) + load_dialog.tabs.setCurrentIndex(2) + load_dialog.example_list_widget.itemClicked.emit(load_dialog.example_list_widget.item(0)) + wait_until(lambda: self.main_window.controls_widget.chi_squared.text() != "") + self.main_window.controls_widget.update_chi_squared("") + self.main_window.controls_widget.procedure_dropdown.setCurrentText(procedure_name) + QApplication.processEvents() + self.main_window.controls_widget.run_button.click() + wait_until( + lambda: "Finished RAT" in self.main_window.terminal_widget.text_area.toPlainText(), max_retry=test_duration + ) + QTest.qWait(SHORT_DELAY) + if isinstance(expected_chi, list): + assert expected_chi[0] <= float(self.main_window.controls_widget.chi_squared.text()) <= expected_chi[1] + else: + assert self.main_window.controls_widget.chi_squared.text() == str(expected_chi) + assert self.main_window.presenter.runner.error is None + + @parameterized.expand( + [ ("ns", [9, 11], 6000), ("dream", 9.67234, 2000), ] ) - def test_run(self, procedure_name, expected_chi, test_duration): + def test_run_slow(self, procedure_name, expected_chi, test_duration): QTest.qWait(SHORT_DELAY) self.main_window.startup_dlg.import_project_button.click() load_dialog = self.main_window.findChild(LoadDialog) From 8787984637246b6395f6e2b6e83b67656c677837 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Tue, 15 Sep 2026 13:53:17 +0100 Subject: [PATCH 23/25] set random seed to fix stochastic tests --- tests/system/gui_system_loading_test.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/system/gui_system_loading_test.py b/tests/system/gui_system_loading_test.py index 9bb3d4ed..07797159 100644 --- a/tests/system/gui_system_loading_test.py +++ b/tests/system/gui_system_loading_test.py @@ -1,3 +1,4 @@ +import random from pathlib import Path from parameterized import parameterized @@ -48,19 +49,17 @@ def test_run(self, procedure_name, expected_chi, test_duration): lambda: "Finished RAT" in self.main_window.terminal_widget.text_area.toPlainText(), max_retry=test_duration ) QTest.qWait(SHORT_DELAY) - if isinstance(expected_chi, list): - assert expected_chi[0] <= float(self.main_window.controls_widget.chi_squared.text()) <= expected_chi[1] - else: - assert self.main_window.controls_widget.chi_squared.text() == str(expected_chi) + assert self.main_window.controls_widget.chi_squared.text() == str(expected_chi) assert self.main_window.presenter.runner.error is None @parameterized.expand( [ - ("ns", [9, 11], 6000), + ("ns", 9.47214, 6000), ("dream", 9.67234, 2000), ] ) def test_run_slow(self, procedure_name, expected_chi, test_duration): + random.seed(10) QTest.qWait(SHORT_DELAY) self.main_window.startup_dlg.import_project_button.click() load_dialog = self.main_window.findChild(LoadDialog) @@ -75,8 +74,5 @@ def test_run_slow(self, procedure_name, expected_chi, test_duration): lambda: "Finished RAT" in self.main_window.terminal_widget.text_area.toPlainText(), max_retry=test_duration ) QTest.qWait(SHORT_DELAY) - if isinstance(expected_chi, list): - assert expected_chi[0] <= float(self.main_window.controls_widget.chi_squared.text()) <= expected_chi[1] - else: - assert self.main_window.controls_widget.chi_squared.text() == str(expected_chi) + assert self.main_window.controls_widget.chi_squared.text() == str(expected_chi) assert self.main_window.presenter.runner.error is None From 13d7329227c61bf8fc5ae2298e884ac719692ca5 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Tue, 15 Sep 2026 14:52:27 +0100 Subject: [PATCH 24/25] add system test information to CONTRIBUTING.md --- CONTRIBUTING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c824f819..b2942d73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,6 +41,15 @@ The coverage report can be saved to the directory htmlcov by running the tests w For information on other coverage report formats, see https://pytest-cov.readthedocs.io/en/latest/reporting.html +System tests which load the GUI and perform common workflows are run by default but can be excluded via + + pytest tests --skip_system_tests + +Some tests can be time-consuming and are only run when necessary, i.e. they are skipped by default and marked as "slow". +To include slow tests use the flag: + + pytest tests --run_slow_tests + Documentation ------------- * The documentation will be hosted on GitHub pages. From b414c8caccbdafb0e8410137e2ad0a694ea5eeeb Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Thu, 15 Oct 2026 16:46:24 +0100 Subject: [PATCH 25/25] suggested changes --- rascal2/core/runner.py | 1 - tests/system/gui_system_base.py | 20 +++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/rascal2/core/runner.py b/rascal2/core/runner.py index e80a5d8a..b30db9ca 100644 --- a/rascal2/core/runner.py +++ b/rascal2/core/runner.py @@ -199,7 +199,6 @@ def stop(self): self.process.kill() self.process = None self.clear_queues_and_events() - self.matlab_helper.close_event.set() def init_matlab_engine(engine_ready, engine_output, msg_queue): diff --git a/tests/system/gui_system_base.py b/tests/system/gui_system_base.py index 90aabe0b..a0acc00c 100644 --- a/tests/system/gui_system_base.py +++ b/tests/system/gui_system_base.py @@ -31,8 +31,6 @@ class GuiSystemBase(unittest.TestCase): def setUp(self) -> None: setup_logging() - self.start_processes_old = os.getenv("START_PROCESSES") - os.environ["START_PROCESSES"] = "False" self.no_exceptions = True sys.excepthook = self.exception_hook @@ -46,7 +44,9 @@ def tearDown(self) -> None: sys.excepthook = sys.__excepthook__ logger = logging.getLogger("rascal2") logger.handlers.clear() - QTimer.singleShot(SHORT_DELAY, lambda: self._click_messagebox(["Discard", "Don't Save"])) + if not self.main_window.presenter.runner.matlab_helper.close_event.is_set(): + self.main_window.presenter.runner.matlab_helper.close_event.set() + QTimer.singleShot(SHORT_DELAY, lambda: self._click_messagebox()) self.main_window.close() wait_until( lambda: not self.main_window.isVisible(), @@ -55,27 +55,25 @@ def tearDown(self) -> None: message="Main window did not close within 10 seconds", ) del self.main_window - os.environ["START_PROCESSES"] = self.start_processes_old def exception_hook(self, exc_type, exc_value, exc_traceback): self.no_exceptions = False sys.__excepthook__(exc_type, exc_value, exc_traceback) @classmethod - def _click_messagebox(cls, button_text_list: list[str]): + def _click_messagebox(cls): """Needs to be queued with QTimer.singleShot before triggering the message box.""" no_buttons_found = True button_texts = [] for widget in cls.app.topLevelWidgets(): if isinstance(widget, QMessageBox) and widget.isVisible(): - for button_text in button_text_list: - for button in widget.buttons(): - if button.text().replace("&", "") == button_text: - QTest.mouseClick(button, Qt.MouseButton.LeftButton) - no_buttons_found = False + for button in widget.buttons(): + if widget.buttonRole(button) == widget.ButtonRole.DestructiveRole: + QTest.mouseClick(button, Qt.MouseButton.LeftButton) + no_buttons_found = False button_texts = [button.text() for button in widget.buttons()] if no_buttons_found: raise ValueError( - f"Could not find buttons '{button_text_list} in {button_texts}'.\n" + f"Could not find buttons in {button_texts}'.\n" f"Message box: {widget.windowTitle()} {widget.text()}" )