Repository navigation
Adding GUI System Tests #249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
5cb1014
a188fd4
cf509af
b7e2081
4a35d01
d411a58
ee63dfa
ee978cf
1db513a
45e8a0f
705ff87
51f75fa
ab51d2c
baf416f
bf6b3a8
35418a5
54e7014
3278fc2
4295333
bf0b13f
9182bfb
a133f95
2dc1c80
a3a63ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,3 +3,4 @@ pytest-cov | |
| ruff | ||
| Sphinx | ||
| pydata-sphinx-theme | ||
| parameterized | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| import logging | ||
| import os | ||
| import sys | ||
| import unittest | ||
| from collections.abc import Callable | ||
|
|
||
| from PyQt6.QtCore import Qt, QTimer | ||
| 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 | ||
| 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 = QApplication.instance() | ||
|
|
||
| def setUp(self) -> None: | ||
| setup_logging() | ||
| self.start_processes_old = os.getenv("START_PROCESSES") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the START_PROCESS env variable still needed? |
||
| 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__ | ||
| logger = logging.getLogger("rascal2") | ||
| logger.handlers.clear() | ||
| QTimer.singleShot(SHORT_DELAY, lambda: self._click_messagebox(["Discard", "Don't Save"])) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't work on Linux because the button name is different, maybe try using the button role https://doc.qt.io/qt-6/qmessagebox.html#ButtonRole-enum
or you can clean the undo stack to prevent the msgbox from showing |
||
| self.main_window.close() | ||
| wait_until( | ||
| lambda: not self.main_window.isVisible(), | ||
| delay=0.05, | ||
| max_retry=200, | ||
| 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]): | ||
| """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 | ||
| 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()}" | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import random | ||
| from pathlib import Path | ||
|
|
||
| from parameterized import parameterized | ||
| 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 | ||
|
|
||
| EXAMPLES_PATH = Path(__file__, "../../../examples").resolve().as_posix() | ||
|
|
||
|
|
||
| class TestGuiSystemLoading(GuiSystemBase): | ||
| def setUp(self) -> None: | ||
| super().setUp() | ||
|
|
||
| def tearDown(self) -> None: | ||
| super().tearDown() | ||
|
|
||
| def test_load(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)) | ||
| 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), | ||
| ] | ||
| ) | ||
| 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) | ||
| 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.47214, 6000), | ||
| ("dream", 9.67234, 2000), | ||
| ] | ||
| ) | ||
| def test_run_slow(self, procedure_name, expected_chi, test_duration): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a weird issue on my Linux machine where the slow test doesn't complete for some reason. It gets stuck on 99% for dream and finish is never printed for NS. Please can you investigate on a Vm or IDAaas |
||
| random.seed(10) | ||
| 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) | ||
| assert self.main_window.controls_widget.chi_squared.text() == str(expected_chi) | ||
| assert self.main_window.presenter.runner.error is None | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| from rascal2 import RASCAL2_VERSION | ||
| 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): | ||
|
MikeSullivan7 marked this conversation as resolved.
|
||
| 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}""" | ||
| ) | ||
This file was deleted.

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
matlab helper is already stopped in the
start_appfunction, if you are trying to stop matlab heleper for the tests it might be better to add it to the test cleanup function