109 lines
3.4 KiB
Python
109 lines
3.4 KiB
Python
"""Tests for the AcsSignal config-read cache and change-only value callbacks."""
|
|
|
|
import time
|
|
from unittest import mock
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from ophyd import Component as Cpt
|
|
from ophyd import Device
|
|
|
|
from debye_bec.devices.mo1_bragg.acs import AcsSignal
|
|
|
|
# pylint: disable=protected-access
|
|
|
|
|
|
class _AcsDevice(Device):
|
|
"""Minimal host device providing the controller attribute AcsSignal expects."""
|
|
|
|
cached = Cpt(AcsSignal, tag=100, prec=3, kind="config", cache_ttl=30.0)
|
|
uncached = Cpt(AcsSignal, tag=200, prec=3, kind="config")
|
|
array = Cpt(AcsSignal, tag=300, prec=3, num_el=3, kind="config", cache_ttl=30.0)
|
|
|
|
def __init__(self, *args, controller=None, **kwargs):
|
|
self.controller = controller
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
@pytest.fixture
|
|
def acs_device():
|
|
controller = mock.MagicMock()
|
|
controller.get_var.return_value = 1.5
|
|
yield _AcsDevice(name="acs", controller=controller)
|
|
|
|
|
|
def test_cached_get_reads_hardware_once(acs_device):
|
|
assert acs_device.cached.get() == 1.5
|
|
assert acs_device.cached.get() == 1.5
|
|
acs_device.controller.get_var.assert_called_once_with(100, 3)
|
|
|
|
|
|
def test_uncached_get_reads_hardware_every_time(acs_device):
|
|
acs_device.uncached.get()
|
|
acs_device.uncached.get()
|
|
assert acs_device.controller.get_var.call_count == 2
|
|
|
|
|
|
def test_cache_expires_after_ttl(acs_device):
|
|
acs_device.cached.cache_ttl = 0.05
|
|
acs_device.cached.get()
|
|
time.sleep(0.06)
|
|
acs_device.cached.get()
|
|
assert acs_device.controller.get_var.call_count == 2
|
|
|
|
|
|
def test_put_invalidates_cache(acs_device):
|
|
acs_device.cached.get()
|
|
acs_device.cached.put(2.0)
|
|
acs_device.controller.set_var.assert_called_once_with(100, 2.0, 3)
|
|
acs_device.controller.get_var.return_value = 2.0
|
|
assert acs_device.cached.get() == 2.0
|
|
assert acs_device.controller.get_var.call_count == 2
|
|
|
|
|
|
def test_invalidate_cache_forces_fresh_read(acs_device):
|
|
acs_device.cached.get()
|
|
acs_device.cached.invalidate_cache()
|
|
acs_device.cached.get()
|
|
assert acs_device.controller.get_var.call_count == 2
|
|
|
|
|
|
def test_value_callbacks_fire_only_on_change(acs_device):
|
|
events = []
|
|
acs_device.cached.subscribe(lambda **kwargs: events.append(kwargs), run=False)
|
|
|
|
acs_device.cached.get() # None -> 1.5: fires
|
|
assert len(events) == 1
|
|
|
|
acs_device.cached.invalidate_cache()
|
|
acs_device.cached.get() # 1.5 -> 1.5: hardware read, but no callback
|
|
assert acs_device.controller.get_var.call_count == 2
|
|
assert len(events) == 1
|
|
|
|
acs_device.controller.get_var.return_value = 2.5
|
|
acs_device.cached.invalidate_cache()
|
|
acs_device.cached.get() # 1.5 -> 2.5: fires
|
|
assert len(events) == 2
|
|
assert events[-1]["value"] == 2.5
|
|
|
|
|
|
def test_array_signal_caches_and_compares_by_content(acs_device):
|
|
acs_device.controller.get_var.side_effect = lambda tag, prec, idx=None: float(idx)
|
|
|
|
events = []
|
|
acs_device.array.subscribe(lambda **kwargs: events.append(kwargs), run=False)
|
|
|
|
assert np.array_equal(acs_device.array.get(), np.array([0.0, 1.0, 2.0]))
|
|
assert acs_device.controller.get_var.call_count == 3 # one call per element
|
|
assert len(events) == 1
|
|
|
|
# cache hit: no additional hardware reads
|
|
acs_device.array.get()
|
|
assert acs_device.controller.get_var.call_count == 3
|
|
|
|
# fresh read with identical content: no callback
|
|
acs_device.array.invalidate_cache()
|
|
acs_device.array.get()
|
|
assert acs_device.controller.get_var.call_count == 6
|
|
assert len(events) == 1
|