mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 11:41:49 +02:00
test: test_motor_map.py added
This commit is contained in:
241
tests/test_motor_map.py
Normal file
241
tests/test_motor_map.py
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from bec_widgets.widgets import MotorMap
|
||||||
|
|
||||||
|
CONFIG_DEFAULT = {
|
||||||
|
"plot_settings": {
|
||||||
|
"colormap": "Greys",
|
||||||
|
"scatter_size": 5,
|
||||||
|
"max_points": 1000,
|
||||||
|
"num_dim_points": 100,
|
||||||
|
"precision": 2,
|
||||||
|
"num_columns": 1,
|
||||||
|
"background_value": 25,
|
||||||
|
},
|
||||||
|
"motors": [
|
||||||
|
{
|
||||||
|
"plot_name": "Motor Map",
|
||||||
|
"x_label": "Motor X",
|
||||||
|
"y_label": "Motor Y",
|
||||||
|
"signals": {
|
||||||
|
"x": [{"name": "samx", "entry": "samx"}],
|
||||||
|
"y": [{"name": "samy", "entry": "samy"}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"plot_name": "Motor Map 2 ",
|
||||||
|
"x_label": "Motor X",
|
||||||
|
"y_label": "Motor Y",
|
||||||
|
"signals": {
|
||||||
|
"x": [{"name": "aptrx", "entry": "aptrx"}],
|
||||||
|
"y": [{"name": "aptry", "entry": "aptry"}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_ONE_DEVICE = {
|
||||||
|
"plot_settings": {
|
||||||
|
"colormap": "Greys",
|
||||||
|
"scatter_size": 5,
|
||||||
|
"max_points": 1000,
|
||||||
|
"num_dim_points": 100,
|
||||||
|
"precision": 2,
|
||||||
|
"num_columns": 1,
|
||||||
|
"background_value": 25,
|
||||||
|
},
|
||||||
|
"motors": [
|
||||||
|
{
|
||||||
|
"plot_name": "Motor Map",
|
||||||
|
"x_label": "Motor X",
|
||||||
|
"y_label": "Motor Y",
|
||||||
|
"signals": {
|
||||||
|
"x": [{"name": "samx", "entry": "samx"}],
|
||||||
|
"y": [{"name": "samy", "entry": "samy"}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FakeDevice:
|
||||||
|
"""Fake minimal positioner class for testing."""
|
||||||
|
|
||||||
|
def __init__(self, name, enabled=True, limits=None, read_value=1.0):
|
||||||
|
self.name = name
|
||||||
|
self.enabled = enabled
|
||||||
|
self.signals = {self.name: {"value": 1.0}}
|
||||||
|
self.description = {self.name: {"source": self.name}}
|
||||||
|
self.limits = limits if limits is not None else [0, 0]
|
||||||
|
self.read_value = read_value
|
||||||
|
|
||||||
|
def set_read_value(self, value):
|
||||||
|
self.read_value = value
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
return {self.name: {"value": self.read_value}}
|
||||||
|
|
||||||
|
def set_limits(self, limits):
|
||||||
|
self.limits = limits
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return item == self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _hints(self):
|
||||||
|
return [self.name]
|
||||||
|
|
||||||
|
def set_value(self, fake_value: float = 1.0) -> None:
|
||||||
|
"""
|
||||||
|
Setup fake value for device readout
|
||||||
|
Args:
|
||||||
|
fake_value(float): Desired fake value
|
||||||
|
"""
|
||||||
|
self.signals[self.name]["value"] = fake_value
|
||||||
|
|
||||||
|
def describe(self) -> dict:
|
||||||
|
"""
|
||||||
|
Get the description of the device
|
||||||
|
Returns:
|
||||||
|
dict: Description of the device
|
||||||
|
"""
|
||||||
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mocked_client():
|
||||||
|
client = MagicMock()
|
||||||
|
|
||||||
|
# Mocking specific motors with their limits
|
||||||
|
motors = {
|
||||||
|
"samx": FakeDevice("samx", limits=[-10, 10], read_value=2.0),
|
||||||
|
"samy": FakeDevice("samy", limits=[-5, 5], read_value=3.0),
|
||||||
|
"aptrx": FakeDevice("aptrx", read_value=4.0),
|
||||||
|
"aptry": FakeDevice("aptry", read_value=5.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
client.device_manager.devices = MagicMock()
|
||||||
|
client.device_manager.devices.__getitem__.side_effect = lambda x: motors.get(x, FakeDevice(x))
|
||||||
|
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def motor_map(qtbot, mocked_client):
|
||||||
|
widget = MotorMap(client=mocked_client)
|
||||||
|
qtbot.addWidget(widget)
|
||||||
|
qtbot.waitExposed(widget)
|
||||||
|
yield widget
|
||||||
|
|
||||||
|
|
||||||
|
def test_motor_limits_initialization(motor_map):
|
||||||
|
# Example test to check if motor limits are correctly initialized
|
||||||
|
expected_limits = {
|
||||||
|
"samx": [-10, 10],
|
||||||
|
"samy": [-5, 5],
|
||||||
|
}
|
||||||
|
for motor_name, expected_limit in expected_limits.items():
|
||||||
|
actual_limit = motor_map._get_motor_limit(motor_name)
|
||||||
|
assert actual_limit == expected_limit
|
||||||
|
|
||||||
|
|
||||||
|
def test_motor_initial_position(motor_map):
|
||||||
|
motor_map.precision = 2
|
||||||
|
# Example test to check if motor initial positions are correctly initialized
|
||||||
|
expected_positions = {
|
||||||
|
("samx", "samx"): 2.0,
|
||||||
|
("samy", "samy"): 3.0,
|
||||||
|
("aptrx", "aptrx"): 4.0,
|
||||||
|
("aptry", "aptry"): 5.0,
|
||||||
|
}
|
||||||
|
for (motor_name, entry), expected_position in expected_positions.items():
|
||||||
|
actual_position = motor_map._get_motor_init_position(motor_name, entry)
|
||||||
|
assert actual_position == expected_position
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"config, number_of_plots",
|
||||||
|
[
|
||||||
|
(CONFIG_DEFAULT, 2),
|
||||||
|
(CONFIG_ONE_DEVICE, 1),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_initialization(motor_map, config, number_of_plots):
|
||||||
|
config_load = config
|
||||||
|
motor_map.on_config_update(config_load)
|
||||||
|
assert isinstance(motor_map, MotorMap)
|
||||||
|
assert motor_map.client is not None
|
||||||
|
assert motor_map.config == config_load
|
||||||
|
assert len(motor_map.plot_data) == number_of_plots
|
||||||
|
|
||||||
|
|
||||||
|
def test_motor_movement_updates_position_and_database(motor_map):
|
||||||
|
motor_map.on_config_update(CONFIG_DEFAULT)
|
||||||
|
|
||||||
|
# Initial positions
|
||||||
|
initial_position_samx = 2.0
|
||||||
|
initial_position_samy = 3.0
|
||||||
|
|
||||||
|
# Set initial positions in the mocked database
|
||||||
|
motor_map.database["samx"]["samx"] = [initial_position_samx]
|
||||||
|
motor_map.database["samy"]["samy"] = [initial_position_samy]
|
||||||
|
|
||||||
|
# Simulate motor movement for 'samx' only
|
||||||
|
new_position_samx = 4.0
|
||||||
|
motor_map.on_device_readback({"signals": {"samx": {"value": new_position_samx}}})
|
||||||
|
|
||||||
|
# Verify database update for 'samx'
|
||||||
|
assert motor_map.database["samx"]["samx"] == [
|
||||||
|
initial_position_samx,
|
||||||
|
new_position_samx,
|
||||||
|
]
|
||||||
|
|
||||||
|
# Verify 'samy' retains its last known position
|
||||||
|
assert motor_map.database["samy"]["samy"] == [
|
||||||
|
initial_position_samy,
|
||||||
|
initial_position_samy,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_scatter_plot_rendering(motor_map):
|
||||||
|
motor_map.on_config_update(CONFIG_DEFAULT)
|
||||||
|
# Set initial positions
|
||||||
|
initial_position_samx = 2.0
|
||||||
|
initial_position_samy = 3.0
|
||||||
|
motor_map.database["samx"]["samx"] = [initial_position_samx]
|
||||||
|
motor_map.database["samy"]["samy"] = [initial_position_samy]
|
||||||
|
|
||||||
|
# Simulate motor movement for 'samx' only
|
||||||
|
new_position_samx = 4.0
|
||||||
|
motor_map.on_device_readback({"signals": {"samx": {"value": new_position_samx}}})
|
||||||
|
motor_map._update_plots()
|
||||||
|
|
||||||
|
# Get the scatter plot item
|
||||||
|
plot_name = "Motor Map" # Update as per your actual plot name
|
||||||
|
scatter_plot_item = motor_map.curves_data[plot_name]["pos"]
|
||||||
|
|
||||||
|
# Check the scatter plot item properties
|
||||||
|
assert len(scatter_plot_item.data) > 0, "Scatter plot data is empty"
|
||||||
|
x_data = scatter_plot_item.data["x"]
|
||||||
|
y_data = scatter_plot_item.data["y"]
|
||||||
|
assert x_data[-1] == new_position_samx, "Scatter plot X data not updated correctly"
|
||||||
|
assert (
|
||||||
|
y_data[-1] == initial_position_samy
|
||||||
|
), "Scatter plot Y data should retain last known position"
|
||||||
|
|
||||||
|
|
||||||
|
def test_plot_visualization_consistency(motor_map):
|
||||||
|
motor_map.on_config_update(CONFIG_DEFAULT)
|
||||||
|
# Simulate updating the plot with new data
|
||||||
|
motor_map.on_device_readback({"signals": {"samx": {"value": 5}}})
|
||||||
|
motor_map.on_device_readback({"signals": {"samy": {"value": 9}}})
|
||||||
|
motor_map._update_plots()
|
||||||
|
|
||||||
|
plot_name = "Motor Map"
|
||||||
|
scatter_plot_item = motor_map.curves_data[plot_name]["pos"]
|
||||||
|
|
||||||
|
# Check if the scatter plot reflects the new data correctly
|
||||||
|
assert (
|
||||||
|
scatter_plot_item.data["x"][-1] == 5 and scatter_plot_item.data["y"][-1] == 9
|
||||||
|
), "Plot not updated correctly with new data"
|
Reference in New Issue
Block a user