1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-05-04 05:44:23 +02:00

tests: update tests

This commit is contained in:
2026-04-09 14:35:09 +02:00
parent b8f3860255
commit 5c52789664
7 changed files with 136 additions and 483 deletions
+1 -1
View File
@@ -62,4 +62,4 @@ runs:
uv pip install --system -e ./ophyd_devices
uv pip install --system -e ./bec/bec_lib[dev]
uv pip install --system -e ./bec/bec_ipython_client
uv pip install --system -e ./bec_widgets[dev,pyside6]
uv pip install --system -e ./bec_widgets[dev,qtermwidget]
@@ -96,7 +96,7 @@ class BecConsoleRegistry:
console_id, terminal_id = console.console_id, console.terminal_id
if console_id in self._consoles:
del self._consoles[console_id]
if (term_info := self._terminal_registry.get(console_id)) is None:
if (term_info := self._terminal_registry.get(terminal_id)) is None:
return
if console_id in term_info.registered_console_ids:
term_info.registered_console_ids.remove(console_id)
@@ -230,13 +230,11 @@ class BecConsole(BECWidget, QWidget):
client=None,
gui_id=None,
startup_cmd: str | None = None,
is_bec_shell: bool = False,
terminal_id: str | None = None,
**kwargs,
):
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
self._mode = ConsoleMode.INACTIVE
self._is_bec_shell = is_bec_shell
self._startup_cmd = startup_cmd
self._is_initialized = False
self.terminal_id = terminal_id or str(uuid4())
@@ -278,7 +276,7 @@ class BecConsole(BECWidget, QWidget):
self.term = _bec_console_registry.try_get_term(self)
if self.term:
self._set_mode(ConsoleMode.ACTIVE)
elif self.isHidden:
elif self.isHidden():
self._set_mode(ConsoleMode.HIDDEN)
else:
self._set_mode(ConsoleMode.INACTIVE)
@@ -22,7 +22,7 @@ class BECShellPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
def createWidget(self, parent):
if parent is None:
return QWidget()
return QWidget()
t = BECShell(parent)
return t
@@ -22,8 +22,8 @@ def _forward(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
target = getattr(self, "_main_widget")
method = getattr(target, func.__name__[1:])
if QTermWidget:
method = getattr(target, func.__name__[1:])
return method(*args, **kwargs)
else:
...
+3
View File
@@ -55,6 +55,9 @@ dev = [
"watchdog~=6.0",
"pre_commit~=4.2",
]
qtermwidget = [
"pyside6_qtermwidget",
]
[build-system]
requires = ["hatchling"]
+128
View File
@@ -0,0 +1,128 @@
from unittest import mock
import pytest
from qtpy.QtCore import Qt
from qtpy.QtGui import QHideEvent, QShowEvent
from qtpy.QtTest import QTest
from bec_widgets.widgets.editors.bec_console.bec_console import (
BecConsole,
BECShell,
ConsoleMode,
_bec_console_registry,
)
from .client_mocks import mocked_client
@pytest.fixture
def console_widget(qtbot):
"""Create a BecConsole widget."""
widget = BecConsole(client=mocked_client, gui_id="test_console", terminal_id="test_terminal")
qtbot.addWidget(widget)
return widget
@pytest.fixture
def two_console_widgets_same_terminal(qtbot):
widget1 = BecConsole(client=mocked_client, gui_id="console_1", terminal_id="shared_terminal")
widget2 = BecConsole(client=mocked_client, gui_id="console_2", terminal_id="shared_terminal")
qtbot.addWidget(widget1)
qtbot.addWidget(widget2)
return widget1, widget2
def test_bec_console_initialization(console_widget: BecConsole):
assert console_widget.console_id == "test_console"
assert console_widget.terminal_id == "test_terminal"
assert console_widget._mode == ConsoleMode.ACTIVE
assert console_widget.term is not None
assert console_widget._overlay.isHidden()
console_widget.show()
assert console_widget.isVisible()
assert _bec_console_registry.owner_is_visible(console_widget.terminal_id)
def test_bec_console_yield_terminal_ownership(console_widget):
console_widget.show()
console_widget.take_terminal_ownership()
console_widget.yield_ownership()
assert console_widget.term is None
assert console_widget._mode == ConsoleMode.INACTIVE
def test_bec_console_hide_event_yields_ownership(console_widget):
console_widget.take_terminal_ownership()
console_widget.hideEvent(QHideEvent())
assert console_widget.term is None
assert console_widget._mode == ConsoleMode.HIDDEN
def test_bec_console_show_event_takes_ownership(console_widget):
console_widget.yield_ownership()
console_widget.showEvent(QShowEvent())
assert console_widget.term is not None
assert console_widget._mode == ConsoleMode.ACTIVE
def test_bec_console_overlay_click_takes_ownership(qtbot, console_widget):
console_widget.yield_ownership()
assert console_widget._mode == ConsoleMode.HIDDEN
QTest.mouseClick(console_widget._overlay, Qt.LeftButton)
assert console_widget.term is not None
assert console_widget._mode == ConsoleMode.ACTIVE
assert not console_widget._overlay.isVisible()
def test_two_consoles_shared_terminal(two_console_widgets_same_terminal):
widget1, widget2 = two_console_widgets_same_terminal
# Widget1 takes ownership
widget1.take_terminal_ownership()
assert widget1.term is not None
assert widget1._mode == ConsoleMode.ACTIVE
assert widget2.term is None
assert widget2._mode == ConsoleMode.HIDDEN
# Widget2 takes ownership
widget2.take_terminal_ownership()
assert widget2.term is not None
assert widget2._mode == ConsoleMode.ACTIVE
assert widget1.term is None
assert widget1._mode == ConsoleMode.HIDDEN
def test_bec_console_registry_cleanup(console_widget: BecConsole):
console_widget.take_terminal_ownership()
terminal_id = console_widget.terminal_id
assert terminal_id in _bec_console_registry._terminal_registry
_bec_console_registry.unregister(console_widget)
assert terminal_id not in _bec_console_registry._terminal_registry
def test_bec_shell_initialization(qtbot):
widget = BECShell(gui_id="bec_shell")
qtbot.addWidget(widget)
assert widget.console_id == "bec_shell"
assert widget.terminal_id == "bec_shell"
assert widget.startup_cmd is not None
def test_bec_console_write(console_widget):
console_widget.take_terminal_ownership()
with mock.patch.object(console_widget.term, "write") as mock_write:
console_widget.write("test command")
mock_write.assert_called_once_with("test command", True)
def test_is_owner(console_widget: BecConsole):
assert _bec_console_registry.is_owner(console_widget)
mock_console = mock.MagicMock()
mock_console.console_id = "fake_console"
_bec_console_registry._consoles["fake_console"] = mock_console
assert not _bec_console_registry.is_owner(mock_console)
mock_console.terminal_id = console_widget.terminal_id
assert not _bec_console_registry.is_owner(mock_console)
-476
View File
@@ -1,476 +0,0 @@
from unittest import mock
import pytest
from qtpy.QtCore import Qt
from qtpy.QtGui import QHideEvent
from qtpy.QtNetwork import QAuthenticator
from bec_widgets.widgets.editors.bec_console.bec_console import (
BecConsole,
BECShell,
ConsoleMode,
_bec_console_registry,
)
from .client_mocks import mocked_client
@pytest.fixture
def mocked_server_startup():
"""Mock the web console server startup process."""
with mock.patch(
"bec_widgets.widgets.editors.bec_console.bec_console.subprocess"
) as mock_subprocess:
with mock.patch.object(_bec_console_registry, "_wait_for_server_port"):
_bec_console_registry._server_port = 12345
yield mock_subprocess
def static_console(qtbot, client, unique_id: str | None = None):
"""Fixture to provide a static unique_id for BecConsole tests."""
if unique_id is None:
widget = BecConsole(client=client)
else:
widget = BecConsole(client=client, terminal_id=unique_id)
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
return widget
@pytest.fixture
def console_widget(qtbot, mocked_client, mocked_server_startup):
"""Create a BecConsole widget with mocked server startup."""
yield static_console(qtbot, mocked_client)
@pytest.fixture
def bec_shell_widget(qtbot, mocked_client, mocked_server_startup):
"""Create a BECShell widget with mocked server startup."""
widget = BECShell(client=mocked_client)
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
yield widget
@pytest.fixture
def console_widget_with_static_id(qtbot, mocked_client, mocked_server_startup):
"""Create a BecConsole widget with a static unique ID."""
yield static_console(qtbot, mocked_client, unique_id="test_console")
@pytest.fixture
def two_console_widgets_same_id(qtbot, mocked_client, mocked_server_startup):
"""Create two BecConsole widgets sharing the same unique ID."""
widget1 = static_console(qtbot, mocked_client, unique_id="shared_console")
widget2 = static_console(qtbot, mocked_client, unique_id="shared_console")
yield widget1, widget2
def test_bec_console_widget_initialization(console_widget):
assert (
console_widget.page.url().toString()
== f"http://localhost:{_bec_console_registry._server_port}"
)
def test_bec_console_write(console_widget):
# Test the write method
with mock.patch.object(console_widget.page, "runJavaScript") as mock_run_js:
console_widget.write("Hello, World!")
assert mock.call('window.term.paste("Hello, World!");') in mock_run_js.mock_calls
def test_bec_console_write_no_return(console_widget):
# Test the write method with send_return=False
with mock.patch.object(console_widget.page, "runJavaScript") as mock_run_js:
console_widget.write("Hello, World!", send_return=False)
assert mock.call('window.term.paste("Hello, World!");') in mock_run_js.mock_calls
assert mock_run_js.call_count == 1
def test_bec_console_send_return(console_widget):
# Test the send_return method
with mock.patch.object(console_widget.page, "runJavaScript") as mock_run_js:
console_widget.send_return()
script = mock_run_js.call_args[0][0]
assert "new KeyboardEvent('keypress', {charCode: 13})" in script
assert mock_run_js.call_count == 1
def test_bec_console_send_ctrl_c(console_widget):
# Test the send_ctrl_c method
with mock.patch.object(console_widget.page, "runJavaScript") as mock_run_js:
console_widget.send_ctrl_c()
script = mock_run_js.call_args[0][0]
assert "new KeyboardEvent('keypress', {charCode: 3})" in script
assert mock_run_js.call_count == 1
def test_bec_console_authenticate(console_widget):
# Test the _authenticate method
token = _bec_console_registry._token
mock_auth = mock.MagicMock(spec=QAuthenticator)
console_widget._authenticate(None, mock_auth)
mock_auth.setUser.assert_called_once_with("user")
mock_auth.setPassword.assert_called_once_with(token)
def test_bec_console_registry_wait_for_server_port():
# Test the _wait_for_server_port method
with mock.patch.object(_bec_console_registry, "_server_process") as mock_subprocess:
mock_subprocess.stderr.readline.side_effect = [b"Starting", b"Listening on port: 12345"]
_bec_console_registry._wait_for_server_port()
assert _bec_console_registry._server_port == 12345
def test_bec_console_registry_wait_for_server_port_timeout():
# Test the _wait_for_server_port method with timeout
with mock.patch.object(_bec_console_registry, "_server_process") as mock_subprocess:
with pytest.raises(TimeoutError):
_bec_console_registry._wait_for_server_port(timeout=0.1)
def test_bec_console_startup_command_execution(console_widget, qtbot):
"""Test that the startup command is triggered after successful initialization."""
# Set a custom startup command
console_widget.startup_cmd = "test startup command"
assert console_widget.startup_cmd == "test startup command"
# Generator to simulate JS initialization sequence
def js_readiness_sequence():
yield False # First call: not ready yet
while True:
yield True # Any subsequent calls: ready
readiness_gen = js_readiness_sequence()
def mock_run_js(script, callback=None):
# Check if this is the initialization check call
if "window.term !== undefined" in script and callback:
ready = next(readiness_gen)
callback(ready)
else:
# For other JavaScript calls (like paste), just call the callback
if callback:
callback(True)
with mock.patch.object(
console_widget.page, "runJavaScript", side_effect=mock_run_js
) as mock_run_js_method:
# Reset initialization state and start the timer
console_widget._is_initialized = False
console_widget._startup_timer.start()
# Wait for the initialization to complete
qtbot.waitUntil(lambda: console_widget._is_initialized, timeout=3000)
# Verify that the startup command was executed
startup_calls = [
call
for call in mock_run_js_method.call_args_list
if "test startup command" in str(call)
]
assert len(startup_calls) > 0, "Startup command should have been executed"
# Verify the initialized signal was emitted
assert console_widget._is_initialized is True
assert not console_widget._startup_timer.isActive()
def test_bec_shell_startup_contains_gui_id(bec_shell_widget):
"""Test that the BEC shell startup command includes the GUI ID."""
bec_shell = bec_shell_widget
assert bec_shell._is_bec_shell
assert bec_shell._unique_id == "bec_shell"
with mock.patch.object(bec_shell.bec_dispatcher, "cli_server", None):
assert bec_shell.startup_cmd == "bec --nogui"
with mock.patch.object(bec_shell.bec_dispatcher.cli_server, "gui_id", "test_gui_id"):
assert bec_shell.startup_cmd == "bec --gui-id test_gui_id"
def test_bec_console_set_readonly(console_widget):
# Test the set_readonly method
console_widget.set_readonly(True)
assert not console_widget.isEnabled()
console_widget.set_readonly(False)
assert console_widget.isEnabled()
def test_bec_console_with_unique_id(console_widget_with_static_id):
"""Test creating a BecConsole with a unique_id."""
widget = console_widget_with_static_id
assert widget._unique_id == "test_console"
assert widget._unique_id in _bec_console_registry._page_registry
page_info = _bec_console_registry.get_page_info("test_console")
assert page_info is not None
assert page_info.owner_gui_id == widget.gui_id
assert widget.gui_id in page_info.widget_ids
def test_bec_console_page_sharing(two_console_widgets_same_id):
"""Test that two widgets can share the same page using unique_id."""
widget1, widget2 = two_console_widgets_same_id
# Both should reference the same page in the registry
page_info = _bec_console_registry.get_page_info("shared_console")
assert page_info is not None
assert widget1.gui_id in page_info.widget_ids
assert widget2.gui_id in page_info.widget_ids
assert widget1.page == widget2.page
def test_bec_console_has_ownership(console_widget_with_static_id):
"""Test the has_ownership method."""
widget = console_widget_with_static_id
# Widget should have ownership by default
assert widget.has_ownership()
def test_bec_console_yield_ownership(console_widget_with_static_id):
"""Test yielding ownership of a page."""
widget = console_widget_with_static_id
assert widget.has_ownership()
# Yield ownership
widget.yield_ownership()
# Widget should no longer have ownership
assert not widget.has_ownership()
page_info = _bec_console_registry.get_page_info("test_console")
assert page_info.owner_gui_id is None
# Overlay should be shown
assert widget._mode == ConsoleMode.INACTIVE
def test_bec_console_take_page_ownership(two_console_widgets_same_id):
"""Test taking ownership of a page."""
widget1, widget2 = two_console_widgets_same_id
# Widget1 should have ownership initially
assert widget1.has_ownership()
assert not widget2.has_ownership()
# Widget2 takes ownership
widget2.take_page_ownership()
# Now widget2 should have ownership
assert not widget1.has_ownership()
assert widget2.has_ownership()
assert widget2._mode == ConsoleMode.ACTIVE
assert widget1._mode == ConsoleMode.INACTIVE
def test_bec_console_hide_event_yields_ownership(qtbot, console_widget_with_static_id):
"""Test that hideEvent yields ownership."""
widget = console_widget_with_static_id
assert widget.has_ownership()
# Hide the widget. Note that we cannot call widget.hide() directly
# because it doesn't trigger the hideEvent in tests as widgets are
# not visible in the test environment.
widget.hideEvent(QHideEvent())
qtbot.wait(100) # Allow event processing
# Widget should have yielded ownership
assert not widget.has_ownership()
page_info = _bec_console_registry.get_page_info("test_console")
assert page_info.owner_gui_id is None
def test_bec_console_show_event_takes_ownership(console_widget_with_static_id):
"""Test that showEvent takes ownership when page has no owner."""
widget = console_widget_with_static_id
# Yield ownership
widget.yield_ownership()
assert not widget.has_ownership()
# Show the widget again
widget.show()
# Widget should have reclaimed ownership
assert widget.has_ownership()
assert widget.browser.isVisible()
assert not widget.overlay.isVisible()
def test_bec_console_mouse_press_takes_ownership(qtbot, two_console_widgets_same_id):
"""Test that clicking on overlay takes ownership."""
widget1, widget2 = two_console_widgets_same_id
widget1.show()
widget2.show()
# Widget1 has ownership, widget2 doesn't
assert widget1.has_ownership()
assert not widget2.has_ownership()
assert widget1.isVisible()
assert widget1._mode == ConsoleMode.ACTIVE
assert widget2._mode == ConsoleMode.INACTIVE
qtbot.mouseClick(widget2, Qt.MouseButton.LeftButton)
# Widget2 should now have ownership
assert widget2.has_ownership()
assert not widget1.has_ownership()
def test_bec_console_registry_cleanup_removes_page(console_widget_with_static_id):
"""Test that the registry cleans up pages when all widgets are removed."""
widget = console_widget_with_static_id
assert widget._unique_id in _bec_console_registry._page_registry
# Cleanup the widget
widget.cleanup()
# Page should be removed from registry
assert widget._unique_id not in _bec_console_registry._page_registry
def test_bec_console_without_unique_id_no_page_sharing(console_widget):
"""Test that widgets without unique_id don't participate in page sharing."""
widget = console_widget
# Widget should not be in the page registry
assert widget._unique_id is None
assert not widget.has_ownership() # Should return False for non-unique widgets
def test_bec_console_registry_get_page_info_nonexistent(qtbot, mocked_client):
"""Test getting page info for a non-existent page."""
page_info = _bec_console_registry.get_page_info("nonexistent")
assert page_info is None
def test_bec_console_take_ownership_without_unique_id(console_widget):
"""Test that take_page_ownership fails gracefully without unique_id."""
widget = console_widget
# Should not crash when taking ownership without unique_id
widget.take_page_ownership()
def test_bec_console_yield_ownership_without_unique_id(console_widget):
"""Test that yield_ownership fails gracefully without unique_id."""
widget = console_widget
# Should not crash when yielding ownership without unique_id
widget.yield_ownership()
def test_registry_yield_ownership_gui_id_not_in_instances():
"""Test registry yield_ownership returns False when gui_id not in instances."""
result = _bec_console_registry.yield_ownership("nonexistent_gui_id")
assert result is False
def test_registry_yield_ownership_instance_is_none(console_widget_with_static_id):
"""Test registry yield_ownership returns False when instance weakref is dead."""
widget = console_widget_with_static_id
gui_id = widget.gui_id
# Store the gui_id and simulate the weakref being dead
_bec_console_registry._consoles[gui_id] = lambda: None
result = _bec_console_registry.yield_ownership(gui_id)
assert result is False
def test_registry_yield_ownership_unique_id_none(console_widget_with_static_id):
"""Test registry yield_ownership returns False when page info's unique_id is None."""
widget = console_widget_with_static_id
gui_id = widget.gui_id
unique_id = widget._unique_id
widget._unique_id = None
result = _bec_console_registry.yield_ownership(gui_id)
assert result is False
widget._unique_id = unique_id # Restore for cleanup
def test_registry_yield_ownership_unique_id_not_in_page_registry(console_widget_with_static_id):
"""Test registry yield_ownership returns False when unique_id not in page registry."""
widget = console_widget_with_static_id
gui_id = widget.gui_id
unique_id = widget._unique_id
widget._unique_id = "nonexistent_unique_id"
result = _bec_console_registry.yield_ownership(gui_id)
assert result is False
widget._unique_id = unique_id # Restore for cleanup
def test_registry_owner_is_visible_page_info_none():
"""Test owner_is_visible returns False when page info doesn't exist."""
result = _bec_console_registry.owner_is_visible("nonexistent_page")
assert result is False
def test_registry_owner_is_visible_no_owner(console_widget_with_static_id):
"""Test owner_is_visible returns False when page has no owner."""
widget = console_widget_with_static_id
# Yield ownership so there's no owner
widget.yield_ownership()
page_info = _bec_console_registry.get_page_info(widget._unique_id)
assert page_info.owner_gui_id is None
result = _bec_console_registry.owner_is_visible(widget._unique_id)
assert result is False
def test_registry_owner_is_visible_owner_ref_none(console_widget_with_static_id):
"""Test owner_is_visible returns False when owner ref doesn't exist in instances."""
widget = console_widget_with_static_id
unique_id = widget._unique_id
# Remove owner from instances dict
del _bec_console_registry._consoles[widget.gui_id]
result = _bec_console_registry.owner_is_visible(unique_id)
assert result is False
def test_registry_owner_is_visible_owner_instance_none(console_widget_with_static_id):
"""Test owner_is_visible returns False when owner instance weakref is dead."""
widget = console_widget_with_static_id
unique_id = widget._unique_id
gui_id = widget.gui_id
# Simulate dead weakref
_bec_console_registry._consoles[gui_id] = lambda: None
result = _bec_console_registry.owner_is_visible(unique_id)
assert result is False
def test_registry_owner_is_visible_owner_visible(console_widget_with_static_id):
"""Test owner_is_visible returns True when owner is visible."""
widget = console_widget_with_static_id
widget.show()
result = _bec_console_registry.owner_is_visible(widget._unique_id)
assert result is True
def test_registry_owner_is_visible_owner_not_visible(console_widget_with_static_id):
"""Test owner_is_visible returns False when owner is not visible."""
widget = console_widget_with_static_id
widget.hide()
result = _bec_console_registry.owner_is_visible(widget._unique_id)
assert result is False