mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 11:41:49 +02:00
test(error_popups): SafeSlot tests adjusted; tests extended to cover SafeProperty
This commit is contained in:
@ -1,10 +1,33 @@
|
|||||||
|
import sys
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytestqt
|
import pytestqt
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
from qtpy.QtCore import QObject
|
||||||
from qtpy.QtWidgets import QMessageBox
|
from qtpy.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from bec_widgets.qt_utils.error_popups import ErrorPopupUtility, ExampleWidget
|
from bec_widgets.qt_utils.error_popups import ErrorPopupUtility, ExampleWidget, SafeProperty
|
||||||
|
|
||||||
|
|
||||||
|
class TestSafePropertyClass(QObject):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._my_value = 10 # internal store
|
||||||
|
|
||||||
|
@SafeProperty(int, default=-1)
|
||||||
|
def my_value(self) -> int:
|
||||||
|
# artificially raise if it's 999 for testing
|
||||||
|
if self._my_value == 999:
|
||||||
|
raise ValueError("Invalid internal state in getter!")
|
||||||
|
return self._my_value
|
||||||
|
|
||||||
|
@my_value.setter
|
||||||
|
def my_value(self, val: int):
|
||||||
|
# artificially raise if user sets -999 for testing
|
||||||
|
if val == -999:
|
||||||
|
raise ValueError("Invalid user input in setter!")
|
||||||
|
self._my_value = val
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -18,46 +41,109 @@ def widget(qtbot):
|
|||||||
|
|
||||||
@patch.object(QMessageBox, "exec_", return_value=QMessageBox.Ok)
|
@patch.object(QMessageBox, "exec_", return_value=QMessageBox.Ok)
|
||||||
def test_show_error_message_global(mock_exec, widget, qtbot):
|
def test_show_error_message_global(mock_exec, widget, qtbot):
|
||||||
|
"""
|
||||||
|
Test that an error popup is shown if global error popups are enabled
|
||||||
|
and the error_occurred signal is emitted manually.
|
||||||
|
"""
|
||||||
error_utility = ErrorPopupUtility()
|
error_utility = ErrorPopupUtility()
|
||||||
error_utility.enable_global_error_popups(True)
|
error_utility.enable_global_error_popups(True)
|
||||||
|
|
||||||
with qtbot.waitSignal(error_utility.error_occurred, timeout=1000) as blocker:
|
with qtbot.waitSignal(error_utility.error_occurred, timeout=1000) as blocker:
|
||||||
error_utility.error_occurred.emit("Test Error", "This is a test error message.", widget)
|
error_utility.error_occurred.emit("Test Error", "This is a test error message.", widget)
|
||||||
|
|
||||||
assert mock_exec.called
|
|
||||||
assert blocker.signal_triggered
|
assert blocker.signal_triggered
|
||||||
|
assert mock_exec.called
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("global_pop", [False, True])
|
@pytest.mark.parametrize("global_pop", [False, True])
|
||||||
@patch.object(QMessageBox, "exec_", return_value=QMessageBox.Ok)
|
@patch.object(QMessageBox, "exec_", return_value=QMessageBox.Ok)
|
||||||
def test_slot_with_popup_on_error(mock_exec, widget, qtbot, global_pop):
|
def test_slot_with_popup_on_error(mock_exec, widget, qtbot, global_pop):
|
||||||
|
"""
|
||||||
|
If the slot is decorated with @SafeSlot(popup_error=True),
|
||||||
|
we always expect a popup on error (and a signal) even if global popups are off.
|
||||||
|
"""
|
||||||
error_utility = ErrorPopupUtility()
|
error_utility = ErrorPopupUtility()
|
||||||
error_utility.enable_global_error_popups(global_pop)
|
error_utility.enable_global_error_popups(global_pop)
|
||||||
|
|
||||||
with qtbot.waitSignal(error_utility.error_occurred, timeout=200) as blocker:
|
with qtbot.waitSignal(error_utility.error_occurred, timeout=500) as blocker:
|
||||||
widget.method_with_error_handling()
|
widget.method_with_error_handling()
|
||||||
|
|
||||||
assert blocker.signal_triggered
|
assert blocker.signal_triggered
|
||||||
assert mock_exec.called
|
assert mock_exec.called # Because popup_error=True forces popup
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("global_pop", [False, True])
|
@pytest.mark.parametrize("global_pop", [False, True])
|
||||||
|
@patch.object(bec_logger.logger, "error")
|
||||||
@patch.object(QMessageBox, "exec_", return_value=QMessageBox.Ok)
|
@patch.object(QMessageBox, "exec_", return_value=QMessageBox.Ok)
|
||||||
def test_slot_no_popup_by_default_on_error(mock_exec, widget, qtbot, capsys, global_pop):
|
def test_slot_no_popup_by_default_on_error(mock_exec, mock_log_error, widget, qtbot, global_pop):
|
||||||
|
"""
|
||||||
|
If the slot is decorated with @SafeSlot() (no popup_error=True),
|
||||||
|
we never show a popup, even if global popups are on,
|
||||||
|
because the code does not check 'enable_error_popup' for normal slots.
|
||||||
|
"""
|
||||||
error_utility = ErrorPopupUtility()
|
error_utility = ErrorPopupUtility()
|
||||||
error_utility.enable_global_error_popups(global_pop)
|
error_utility.enable_global_error_popups(global_pop)
|
||||||
|
|
||||||
try:
|
# We do NOT expect a popup or signal in either case, since code only logs
|
||||||
with qtbot.waitSignal(error_utility.error_occurred, timeout=200) as blocker:
|
with qtbot.assertNotEmitted(error_utility.error_occurred):
|
||||||
widget.method_without_error_handling()
|
widget.method_without_error_handling()
|
||||||
except pytestqt.exceptions.TimeoutError:
|
|
||||||
assert not global_pop
|
|
||||||
|
|
||||||
if global_pop:
|
|
||||||
assert blocker.signal_triggered
|
|
||||||
assert mock_exec.called
|
|
||||||
else:
|
|
||||||
assert not blocker.signal_triggered
|
|
||||||
assert not mock_exec.called
|
assert not mock_exec.called
|
||||||
stdout, stderr = capsys.readouterr()
|
|
||||||
assert "ValueError" in stderr
|
# Confirm logger.error(...) was called
|
||||||
|
mock_log_error.assert_called_once()
|
||||||
|
logged_msg = mock_log_error.call_args[0][0]
|
||||||
|
assert "ValueError" in logged_msg
|
||||||
|
assert "SafeSlot error in slot" in logged_msg
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("global_pop", [False, True])
|
||||||
|
@patch.object(bec_logger.logger, "error")
|
||||||
|
@patch.object(QMessageBox, "exec_", return_value=QMessageBox.Ok)
|
||||||
|
def test_safe_property_getter_error(mock_exec, mock_log_error, qtbot, global_pop):
|
||||||
|
"""
|
||||||
|
If a property getter raises an error, we log it by default.
|
||||||
|
(No popup is shown unless code specifically calls it.)
|
||||||
|
"""
|
||||||
|
error_utility = ErrorPopupUtility()
|
||||||
|
error_utility.enable_global_error_popups(global_pop)
|
||||||
|
|
||||||
|
test_obj = TestSafePropertyClass()
|
||||||
|
test_obj._my_value = 999 # triggers ValueError in getter => logs => returns default (-1)
|
||||||
|
|
||||||
|
val = test_obj.my_value
|
||||||
|
assert val == -1
|
||||||
|
|
||||||
|
# No popup => mock_exec not called
|
||||||
|
assert not mock_exec.called
|
||||||
|
|
||||||
|
# logger.error(...) is called once
|
||||||
|
mock_log_error.assert_called_once()
|
||||||
|
logged_msg = mock_log_error.call_args[0][0]
|
||||||
|
assert "SafeProperty error in GETTER" in logged_msg
|
||||||
|
assert "ValueError" in logged_msg
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("global_pop", [False, True])
|
||||||
|
@patch.object(bec_logger.logger, "error")
|
||||||
|
@patch.object(QMessageBox, "exec_", return_value=QMessageBox.Ok)
|
||||||
|
def test_safe_property_setter_error(mock_exec, mock_log_error, qtbot, global_pop):
|
||||||
|
"""
|
||||||
|
If a property setter raises an error, we log it by default.
|
||||||
|
(No popup is shown unless code specifically calls it.)
|
||||||
|
"""
|
||||||
|
error_utility = ErrorPopupUtility()
|
||||||
|
error_utility.enable_global_error_popups(global_pop)
|
||||||
|
|
||||||
|
test_obj = TestSafePropertyClass()
|
||||||
|
# Setting to -999 triggers setter error => logs => property returns None
|
||||||
|
test_obj.my_value = -999
|
||||||
|
|
||||||
|
# No popup => mock_exec not called
|
||||||
|
assert not mock_exec.called
|
||||||
|
|
||||||
|
# logger.error(...) is called once
|
||||||
|
mock_log_error.assert_called_once()
|
||||||
|
logged_msg = mock_log_error.call_args[0][0]
|
||||||
|
assert "SafeProperty error in SETTER" in logged_msg
|
||||||
|
assert "ValueError" in logged_msg
|
||||||
|
Reference in New Issue
Block a user