Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for EigerHandler and EigerConfigHandler #53

Merged
merged 8 commits into from
Dec 3, 2024
Next Next commit
Add tests for EigerHandler and EigerConfigHandler
jsouter committed Nov 15, 2024
commit d2a86eb1db2aa0d46ea2f61e9c564583fa9fb943
63 changes: 63 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -146,5 +146,68 @@ async def _connection_get(uri):
# dummy response
return {"access_mode": "rw", "value": 0.0, "value_type": "float"}

async def _connection_put(uri: str, _):
# copied from tickit sim
key = uri.split("/", 4)[-1]
match key:
case "auto_summation":
return ["auto_summation", "frame_count_time"]
case "count_time" | "frame_time":
return [
"bit_depth_image",
"bit_depth_readout",
"count_time",
"countrate_correction_count_cutoff",
"frame_count_time",
"frame_time",
]
case "flatfield":
return ["flatfield", "threshold/1/flatfield"]
case "incident_energy" | "photon_energy":
return [
"element",
"flatfield",
"incident_energy",
"photon_energy",
"threshold/1/energy",
"threshold/1/flatfield",
"threshold/2/energy",
"threshold/2/flatfield",
"threshold_energy",
"wavelength",
]
case "pixel_mask":
return ["pixel_mask", "threshold/1/pixel_mask"]
case "threshold/1/flatfield":
return ["flatfield", "threshold/1/flatfield"]
case "roi_mode":
return ["count_time", "frame_time", "roi_mode"]
case "threshold_energy" | "threshold/1/energy":
return [
"flatfield",
"threshold/1/energy",
"threshold/1/flatfield",
"threshold/2/flatfield",
"threshold_energy",
]
case "threshold/2/energy":
return [
"flatfield",
"threshold/1/flatfield",
"threshold/2/energy",
"threshold/2/flatfield",
]
case "threshold/1/mode":
return ["threshold/1/mode", "threshold/difference/mode"]
case "threshold/2/mode":
return ["threshold/2/mode", "threshold/difference/mode"]
case "threshold/1/pixel_mask":
return ["pixel_mask", "threshold/1/pixel_mask"]
case "threshold/difference/mode":
return ["difference_mode"] # replicating API inconsistency
case _:
return [key]

connection.get.side_effect = _connection_get
connection.put.side_effect = _connection_put
return connection
122 changes: 122 additions & 0 deletions tests/test_controller.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
from unittest import mock

import pytest
from fastcs.attributes import Attribute
@@ -7,8 +8,10 @@
from fastcs_eiger.eiger_controller import (
IGNORED_KEYS,
MISSING_KEYS,
EigerConfigHandler,
EigerController,
EigerDetectorController,
EigerHandler,
EigerMonitorController,
EigerStreamController,
)
@@ -82,3 +85,122 @@ async def test_eiger_controller_initialises(mocker: MockerFixture, mock_connecti
connection.get.assert_any_call("detector/api/1.8.0/status/state")
connection.get.assert_any_call("stream/api/1.8.0/status/state")
connection.get.assert_any_call("monitor/api/1.8.0/status/state")


@pytest.mark.asyncio
async def test_EigerHandler_after_put(mock_connection):
subsystem_controller = EigerDetectorController(mock_connection, _lock)
await subsystem_controller.initialise()
attr = subsystem_controller.humidity
handler = attr.sender

assert type(handler) is EigerHandler
assert not subsystem_controller.stale_parameters.get()
await handler.put(subsystem_controller, attr, 0.1)
# eiger API does not return a list of updated parameters when we set status keys
# so _parameter_updates set to default case where we only update the key we changed
assert subsystem_controller._parameter_updates == {"humidity"}
# humidity is really read-only but given here for demonstration
assert subsystem_controller.stale_parameters.get()

# parameters with EigerHandler handlers do not get updated when
# controller update is called

subsystem_controller.humidity.updater.update = mock.AsyncMock()

await subsystem_controller.update()
assert subsystem_controller.stale_parameters.get()
subsystem_controller.humidity.updater.update.assert_not_called()

await subsystem_controller.update()
# stale does not get set False unless there are no stale parameters at start of
# update call
assert not subsystem_controller.stale_parameters.get()
assert not subsystem_controller._parameter_updates


@pytest.mark.asyncio
async def test_EigerHandler_update_updates_value(mock_connection):
subsystem_controller = EigerDetectorController(mock_connection, _lock)
await subsystem_controller.initialise()

async def _get_1_as_value(*args, **kwargs):
return {"access_mode": "r", "value": 1, "value_type": "int"}

assert type(subsystem_controller.state.updater) is EigerHandler
assert subsystem_controller.state.get() == 0

mock_connection.get = (
_get_1_as_value # show that value changes after update is awaited
)
await subsystem_controller.state.updater.update(
subsystem_controller, subsystem_controller.state
)
assert subsystem_controller.state.get() == 1


@pytest.mark.asyncio
async def test_EigerConfigHandler(mock_connection):
subsystem_controller = EigerDetectorController(mock_connection, _lock)
await subsystem_controller.initialise()
attr = subsystem_controller.threshold_1_energy
handler = attr.sender
assert isinstance(handler, EigerConfigHandler)
assert not subsystem_controller.stale_parameters.get()
await handler.put(subsystem_controller, attr, 100.0)
expected_changed_params = [
"flatfield",
"threshold/1/energy",
"threshold/1/flatfield",
"threshold/2/flatfield",
"threshold_energy",
]
assert subsystem_controller._parameter_updates == set(expected_changed_params)
assert subsystem_controller.stale_parameters.get()

# flatfields are ignored keys
subsystem_controller.threshold_energy.updater.config_update = mock.AsyncMock()

await subsystem_controller.update()
assert subsystem_controller.stale_parameters.get()
assert subsystem_controller.threshold_energy.updater.config_update.mock_calls == [
mock.call.config_update(
subsystem_controller, subsystem_controller.threshold_energy
)
]

await subsystem_controller.update()
# stale does not get set False unless there are no stale parameters at start of
# update call
assert not subsystem_controller.stale_parameters.get()
assert not subsystem_controller._parameter_updates


@pytest.mark.asyncio
async def test_stale_parameter_propagates_to_top_controller(mock_connection):
top_controller = EigerController("127.0.0.1", 80)
top_controller.connection = mock_connection
await top_controller.initialise()
detector_controller = top_controller.get_sub_controllers()["Detector"]
attr = detector_controller.threshold_energy

assert not detector_controller.stale_parameters.get()
assert not top_controller.stale_parameters.get()

await attr.sender.put(detector_controller, attr, 100.0)
assert detector_controller.stale_parameters.get()
# top controller not stale until update called
assert not top_controller.stale_parameters.get()
await top_controller.update()
assert top_controller.stale_parameters.get()

# need to update again to make detector controller update its
# stale parameter attribute
await top_controller.update()
assert not detector_controller.stale_parameters.get()
assert top_controller.stale_parameters.get()

# top controller needs to update another final time so that the
# detector controller stale attribute returning to False propagates to top
await top_controller.update()
assert not top_controller.stale_parameters.get()