mirror of
https://github.com/ivan-usov-org/bec.git
synced 2025-04-22 02:20:02 +02:00
To have a better separation of concern between messages and how they are conveyed in connectors. BECMessage can be simple dataclasses, leaving the serialization to the connector which transport those. The serialization module itself can be isolated, and in the case of msgpack it can be extended to understand how to encode/decode BECMessage, to simplify writing code like with BundleMessage or to be able to automatically encode numpy or BECStatus objects. Finally, client objects (producers and consumers) can receive BECMessage objects instead of having to dump or load themselves.
204 lines
7.3 KiB
Python
204 lines
7.3 KiB
Python
import os
|
|
import shutil
|
|
from unittest import mock
|
|
|
|
import msgpack
|
|
import pytest
|
|
import yaml
|
|
|
|
import bec_lib
|
|
from bec_lib import messages
|
|
from bec_lib.bec_errors import DeviceConfigError
|
|
from bec_lib.config_helper import ConfigHelper
|
|
|
|
dir_path = os.path.dirname(bec_lib.__file__)
|
|
|
|
|
|
def test_load_demo_config():
|
|
connector = mock.MagicMock()
|
|
config_helper = ConfigHelper(connector)
|
|
with mock.patch.object(config_helper, "update_session_with_file") as mock_update:
|
|
config_helper.load_demo_config()
|
|
dirpath = os.path.dirname(bec_lib.__file__)
|
|
fpath = os.path.join(dirpath, "configs/demo_config.yaml")
|
|
mock_update.assert_called_once_with(fpath)
|
|
|
|
|
|
def test_config_helper_update_session_with_file():
|
|
connector = mock.MagicMock()
|
|
config_helper = ConfigHelper(connector)
|
|
with mock.patch.object(config_helper, "send_config_request") as mock_send_config_request:
|
|
with mock.patch.object(
|
|
config_helper, "_load_config_from_file"
|
|
) as mock_load_config_from_file:
|
|
mock_load_config_from_file.return_value = {"test": "test"}
|
|
config_helper.update_session_with_file("test.yaml")
|
|
mock_send_config_request.assert_called_once_with(action="set", config={"test": "test"})
|
|
|
|
|
|
@pytest.mark.parametrize("config_file", ["test.yaml", "test.yml"])
|
|
def test_config_helper_load_config_from_file(config_file, tmp_path):
|
|
orig_cfg_file = f"{dir_path}/tests/test_config.yaml"
|
|
test_cfg_file = tmp_path / config_file
|
|
shutil.copyfile(orig_cfg_file, test_cfg_file)
|
|
connector = mock.MagicMock()
|
|
config_helper = ConfigHelper(connector)
|
|
config = config_helper._load_config_from_file(test_cfg_file)
|
|
|
|
|
|
def test_config_helper_save_current_session():
|
|
connector = mock.MagicMock()
|
|
|
|
config_helper = ConfigHelper(connector)
|
|
connector.producer().get.return_value = msgpack.dumps(
|
|
[
|
|
{
|
|
"id": "648c817f67d3c7cd6a354e8e",
|
|
"createdAt": "2023-06-16T15:36:31.215Z",
|
|
"createdBy": "unknown user",
|
|
"name": "pinz",
|
|
"sessionId": "648c817d67d3c7cd6a354df2",
|
|
"enabled": True,
|
|
"readOnly": False,
|
|
"deviceClass": "SimPositioner",
|
|
"deviceTags": ["user motors"],
|
|
"deviceConfig": {
|
|
"delay": 1,
|
|
"labels": "pinz",
|
|
"limits": [-50, 50],
|
|
"name": "pinz",
|
|
"speed": 100,
|
|
"tolerance": 0.01,
|
|
"update_frequency": 400,
|
|
},
|
|
"readoutPriority": "baseline",
|
|
"onFailure": "retry",
|
|
},
|
|
{
|
|
"id": "648c817f67d3c7cd6a354ec5",
|
|
"createdAt": "2023-06-16T15:36:31.764Z",
|
|
"createdBy": "unknown user",
|
|
"name": "transd",
|
|
"sessionId": "648c817d67d3c7cd6a354df2",
|
|
"enabled": True,
|
|
"readOnly": False,
|
|
"deviceClass": "SynAxisMonitor",
|
|
"deviceTags": ["beamline"],
|
|
"deviceConfig": {"labels": "transd", "name": "transd", "tolerance": 0.5},
|
|
"readoutPriority": "monitored",
|
|
"onFailure": "retry",
|
|
},
|
|
]
|
|
)
|
|
with mock.patch("builtins.open", mock.mock_open()) as mock_open:
|
|
config_helper.save_current_session("test.yaml")
|
|
out_data = {
|
|
"pinz": {
|
|
"deviceClass": "SimPositioner",
|
|
"deviceTags": ["user motors"],
|
|
"enabled": True,
|
|
"readOnly": False,
|
|
"deviceConfig": {
|
|
"delay": 1,
|
|
"labels": "pinz",
|
|
"limits": [-50, 50],
|
|
"name": "pinz",
|
|
"speed": 100,
|
|
"tolerance": 0.01,
|
|
"update_frequency": 400,
|
|
},
|
|
"readoutPriority": "baseline",
|
|
"onFailure": "retry",
|
|
},
|
|
"transd": {
|
|
"deviceClass": "SynAxisMonitor",
|
|
"deviceTags": ["beamline"],
|
|
"enabled": True,
|
|
"readOnly": False,
|
|
"deviceConfig": {"labels": "transd", "name": "transd", "tolerance": 0.5},
|
|
"readoutPriority": "monitored",
|
|
"onFailure": "retry",
|
|
},
|
|
}
|
|
mock_open().write.assert_called_once_with(yaml.dump(out_data))
|
|
|
|
|
|
@pytest.fixture
|
|
def config_helper():
|
|
connector = mock.MagicMock()
|
|
config_helper_inst = ConfigHelper(connector)
|
|
with mock.patch.object(config_helper_inst, "wait_for_config_reply"):
|
|
with mock.patch.object(config_helper_inst, "wait_for_service_response"):
|
|
yield config_helper_inst
|
|
|
|
|
|
def test_send_config_request_raises_with_empty_config(config_helper):
|
|
with pytest.raises(DeviceConfigError):
|
|
config_helper.send_config_request(action="update")
|
|
config_helper.wait_for_config_reply.assert_called_once_with(mock.ANY)
|
|
|
|
|
|
def test_send_config_request(config_helper):
|
|
config_helper.send_config_request(action="update", config={"test": "test"})
|
|
config_helper.wait_for_config_reply.return_value = messages.RequestResponseMessage(
|
|
accepted=True, message="test"
|
|
)
|
|
config_helper.wait_for_config_reply.assert_called_once_with(mock.ANY)
|
|
config_helper.wait_for_service_response.assert_called_once_with(mock.ANY)
|
|
|
|
|
|
def test_send_config_request_raises_for_rejected_update(config_helper):
|
|
config_helper.wait_for_config_reply.return_value = messages.RequestResponseMessage(
|
|
accepted=False, message="test"
|
|
)
|
|
with pytest.raises(DeviceConfigError):
|
|
config_helper.send_config_request(action="update", config={"test": "test"})
|
|
config_helper.wait_for_config_reply.assert_called_once_with(mock.ANY)
|
|
|
|
|
|
def test_wait_for_config_reply():
|
|
connector = mock.MagicMock()
|
|
config_helper = ConfigHelper(connector)
|
|
connector.producer().get.return_value = messages.RequestResponseMessage(
|
|
accepted=True, message="test"
|
|
)
|
|
|
|
res = config_helper.wait_for_config_reply("test")
|
|
assert res == messages.RequestResponseMessage(accepted=True, message="test")
|
|
|
|
|
|
def test_wait_for_config_raises_timeout():
|
|
connector = mock.MagicMock()
|
|
config_helper = ConfigHelper(connector)
|
|
connector.producer().get.return_value = None
|
|
|
|
with pytest.raises(DeviceConfigError):
|
|
config_helper.wait_for_config_reply("test", timeout=0.3)
|
|
|
|
|
|
def test_wait_for_service_response():
|
|
connector = mock.MagicMock()
|
|
config_helper = ConfigHelper(connector)
|
|
connector.producer().lrange.side_effect = [
|
|
[],
|
|
[
|
|
messages.ServiceResponseMessage(
|
|
response={"service": "DeviceServer"}, metadata={"RID": "test"}
|
|
),
|
|
messages.ServiceResponseMessage(
|
|
response={"service": "ScanServer"}, metadata={"RID": "test"}
|
|
),
|
|
],
|
|
]
|
|
|
|
config_helper.wait_for_service_response("test", timeout=0.3)
|
|
|
|
|
|
def test_wait_for_service_response_raises_timeout():
|
|
connector = mock.MagicMock()
|
|
config_helper = ConfigHelper(connector)
|
|
connector.producer().lrange.return_value = []
|
|
|
|
with pytest.raises(DeviceConfigError):
|
|
config_helper.wait_for_service_response("test", timeout=0.3)
|