mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-03-04 16:02:51 +01:00
260 lines
9.0 KiB
Python
260 lines
9.0 KiB
Python
from contextlib import contextmanager
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
from bec_widgets.cli.client import BECDockArea
|
|
from bec_widgets.cli.client_utils import BECGuiClient, _start_plot_process
|
|
|
|
|
|
@pytest.fixture
|
|
def cli_dock_area():
|
|
dock_area = BECDockArea(gui_id="test")
|
|
with mock.patch.object(dock_area, "_run_rpc") as mock_rpc_call:
|
|
with mock.patch.object(dock_area, "_gui_is_alive", return_value=True):
|
|
yield dock_area, mock_rpc_call
|
|
|
|
|
|
def test_rpc_call_new_dock(cli_dock_area):
|
|
dock_area, mock_rpc_call = cli_dock_area
|
|
dock_area.new(name="test")
|
|
mock_rpc_call.assert_called_with("new", name="test")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"config, call_config",
|
|
[
|
|
(None, None),
|
|
("/path/to/config.yml", "/path/to/config.yml"),
|
|
({"key": "value"}, '{"key": "value"}'),
|
|
],
|
|
)
|
|
def test_client_utils_start_plot_process(config, call_config):
|
|
with mock.patch("bec_widgets.cli.client_utils.subprocess.Popen") as mock_popen:
|
|
_start_plot_process("gui_id", "bec", config, gui_class="AdvancedDockArea")
|
|
command = [
|
|
"bec-gui-server",
|
|
"--id",
|
|
"gui_id",
|
|
"--gui_class",
|
|
"AdvancedDockArea",
|
|
"--gui_class_id",
|
|
"bec",
|
|
"--hide",
|
|
]
|
|
if call_config:
|
|
command.extend(["--config", call_config])
|
|
mock_popen.assert_called_once_with(
|
|
command,
|
|
text=True,
|
|
start_new_session=True,
|
|
stdout=mock.ANY,
|
|
stderr=mock.ANY,
|
|
env=mock.ANY,
|
|
)
|
|
|
|
|
|
def test_client_utils_passes_client_config_to_server(bec_dispatcher):
|
|
"""
|
|
Test that the client config is passed to the server. This ensures that
|
|
changes to the client config (either through config files or plugins) are
|
|
reflected in the server.
|
|
"""
|
|
|
|
@contextmanager
|
|
def bec_client_mixin():
|
|
mixin = BECGuiClient()
|
|
mixin._client = bec_dispatcher.client
|
|
mixin._gui_id = "gui_id"
|
|
mixin._gui_is_alive = mock.MagicMock()
|
|
mixin._gui_is_alive.side_effect = [False, False, True]
|
|
|
|
try:
|
|
yield mixin
|
|
finally:
|
|
mixin.kill_server()
|
|
|
|
with bec_client_mixin() as mixin:
|
|
with mock.patch("bec_widgets.cli.client_utils._start_plot_process") as mock_start_plot:
|
|
mock_start_plot.return_value = [mock.MagicMock(), mock.MagicMock()]
|
|
mixin._start_server(
|
|
wait=False
|
|
) # the started event will not be set, wait=True would block forever
|
|
mock_start_plot.assert_called_once_with(
|
|
"gui_id",
|
|
gui_class_id="bec",
|
|
config=mixin._client._service_config.config,
|
|
logger=mock.ANY,
|
|
)
|
|
|
|
|
|
@contextmanager
|
|
def _no_wait_for_server(_client):
|
|
yield
|
|
|
|
|
|
@pytest.mark.parametrize("theme", ["light", "dark"])
|
|
def test_client_utils_apply_theme_explicit(theme):
|
|
gui = BECGuiClient()
|
|
launcher = mock.MagicMock()
|
|
|
|
with mock.patch.object(
|
|
BECGuiClient, "launcher", new_callable=mock.PropertyMock
|
|
) as launcher_prop:
|
|
launcher_prop.return_value = launcher
|
|
with mock.patch("bec_widgets.cli.client_utils.wait_for_server", _no_wait_for_server):
|
|
with mock.patch.object(gui, "_check_if_server_is_alive", return_value=True):
|
|
gui.change_theme(theme)
|
|
|
|
launcher._run_rpc.assert_called_once_with("change_theme", theme=theme)
|
|
|
|
|
|
@pytest.mark.parametrize("current_theme, expected_theme", [("light", "dark"), ("dark", "light")])
|
|
def test_client_utils_apply_theme_toggles_when_none(current_theme, expected_theme):
|
|
gui = BECGuiClient()
|
|
launcher = mock.MagicMock()
|
|
launcher._run_rpc.side_effect = [current_theme, None]
|
|
|
|
with mock.patch.object(
|
|
BECGuiClient, "launcher", new_callable=mock.PropertyMock
|
|
) as launcher_prop:
|
|
launcher_prop.return_value = launcher
|
|
with mock.patch("bec_widgets.cli.client_utils.wait_for_server", _no_wait_for_server):
|
|
with mock.patch.object(gui, "_check_if_server_is_alive", return_value=True):
|
|
gui.change_theme(None)
|
|
|
|
assert launcher._run_rpc.call_args_list == [
|
|
mock.call("fetch_theme"),
|
|
mock.call("change_theme", theme=expected_theme),
|
|
]
|
|
|
|
|
|
def test_client_utils_new_passes_startup_profile():
|
|
gui = BECGuiClient()
|
|
launcher = mock.MagicMock()
|
|
|
|
with mock.patch.object(
|
|
BECGuiClient, "launcher", new_callable=mock.PropertyMock
|
|
) as launcher_prop:
|
|
launcher_prop.return_value = launcher
|
|
with mock.patch("bec_widgets.cli.client_utils.wait_for_server", _no_wait_for_server):
|
|
with mock.patch.object(gui, "_check_if_server_is_alive", return_value=True):
|
|
gui.new(startup_profile="saved_profile")
|
|
|
|
launcher._run_rpc.assert_called_once_with(
|
|
"system.launch_dock_area", name=None, geometry=None, startup_profile="saved_profile"
|
|
)
|
|
|
|
|
|
def test_client_utils_new_defaults_to_empty_startup_profile():
|
|
gui = BECGuiClient()
|
|
launcher = mock.MagicMock()
|
|
|
|
with mock.patch.object(
|
|
BECGuiClient, "launcher", new_callable=mock.PropertyMock
|
|
) as launcher_prop:
|
|
launcher_prop.return_value = launcher
|
|
with mock.patch("bec_widgets.cli.client_utils.wait_for_server", _no_wait_for_server):
|
|
with mock.patch.object(gui, "_check_if_server_is_alive", return_value=True):
|
|
gui.new()
|
|
|
|
launcher._run_rpc.assert_called_once_with(
|
|
"system.launch_dock_area", name=None, geometry=None, startup_profile=None
|
|
)
|
|
|
|
|
|
def test_client_utils_new_rejects_legacy_profile_kwargs():
|
|
gui = BECGuiClient()
|
|
with pytest.raises(TypeError, match="startup_profile"):
|
|
gui.new(profile="saved_profile")
|
|
|
|
|
|
def test_client_utils_new_falls_back_when_system_rpc_not_supported():
|
|
gui = BECGuiClient()
|
|
launcher = mock.MagicMock()
|
|
launcher._run_rpc.side_effect = [
|
|
ValueError("Unknown system RPC method: system.launch_dock_area"),
|
|
"fallback_widget",
|
|
]
|
|
|
|
with mock.patch.object(
|
|
BECGuiClient, "launcher", new_callable=mock.PropertyMock
|
|
) as launcher_prop:
|
|
launcher_prop.return_value = launcher
|
|
with mock.patch("bec_widgets.cli.client_utils.wait_for_server", _no_wait_for_server):
|
|
with mock.patch.object(gui, "_check_if_server_is_alive", return_value=True):
|
|
result = gui.new(startup_profile="restore")
|
|
|
|
assert result == "fallback_widget"
|
|
assert launcher._run_rpc.call_args_list == [
|
|
mock.call("system.launch_dock_area", name=None, geometry=None, startup_profile="restore"),
|
|
mock.call(
|
|
"launch", launch_script="dock_area", name=None, geometry=None, startup_profile="restore"
|
|
),
|
|
]
|
|
|
|
|
|
def test_client_utils_new_reraises_unexpected_system_rpc_error():
|
|
gui = BECGuiClient()
|
|
launcher = mock.MagicMock()
|
|
launcher._run_rpc.side_effect = ValueError("Some other RPC error")
|
|
|
|
with mock.patch.object(
|
|
BECGuiClient, "launcher", new_callable=mock.PropertyMock
|
|
) as launcher_prop:
|
|
launcher_prop.return_value = launcher
|
|
with mock.patch("bec_widgets.cli.client_utils.wait_for_server", _no_wait_for_server):
|
|
with mock.patch.object(gui, "_check_if_server_is_alive", return_value=True):
|
|
with pytest.raises(ValueError, match="Some other RPC error"):
|
|
gui.new(startup_profile="restore")
|
|
|
|
|
|
def test_client_utils_new_starts_server_when_not_alive():
|
|
gui = BECGuiClient()
|
|
launcher = mock.MagicMock()
|
|
|
|
with mock.patch.object(
|
|
BECGuiClient, "launcher", new_callable=mock.PropertyMock
|
|
) as launcher_prop:
|
|
launcher_prop.return_value = launcher
|
|
with mock.patch("bec_widgets.cli.client_utils.wait_for_server", _no_wait_for_server):
|
|
with (
|
|
mock.patch.object(gui, "_check_if_server_is_alive", return_value=False),
|
|
mock.patch.object(gui, "start") as mock_start,
|
|
):
|
|
gui.new(wait=False, startup_profile=None)
|
|
|
|
mock_start.assert_called_once_with(wait=True)
|
|
|
|
|
|
def test_client_utils_delete_uses_container_proxy():
|
|
gui = BECGuiClient()
|
|
widget = mock.MagicMock()
|
|
widget._gui_id = "widget-id"
|
|
|
|
with (
|
|
mock.patch.object(BECGuiClient, "windows", new_callable=mock.PropertyMock) as windows_prop,
|
|
mock.patch.dict(
|
|
gui._server_registry, {"widget-id": {"container_proxy": "container-id"}}, clear=True
|
|
),
|
|
):
|
|
windows_prop.return_value = {"dock": widget}
|
|
gui.delete("dock")
|
|
|
|
widget._run_rpc.assert_called_once_with("close", gui_id="container-id")
|
|
|
|
|
|
def test_client_utils_delete_falls_back_to_direct_close():
|
|
gui = BECGuiClient()
|
|
widget = mock.MagicMock()
|
|
widget._gui_id = "widget-id"
|
|
|
|
with (
|
|
mock.patch.object(BECGuiClient, "windows", new_callable=mock.PropertyMock) as windows_prop,
|
|
mock.patch.dict(gui._server_registry, {"widget-id": {"container_proxy": None}}, clear=True),
|
|
):
|
|
windows_prop.return_value = {"dock": widget}
|
|
gui.delete("dock")
|
|
|
|
widget._run_rpc.assert_called_once_with("close")
|