120 lines
3.5 KiB
Python
120 lines
3.5 KiB
Python
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from bec_lib.endpoints import EndpointInfo
|
|
|
|
from aare_bec_proto.actor_manager import AareActorManager
|
|
from aare_bec_proto.actors import all_actor_classes
|
|
from aare_bec_proto.actors.core import MXAutomationActor
|
|
from aare_bec_proto.actors.mx_automation import (
|
|
OnOpticalCenteringDoneRunXrayCentering,
|
|
OnSampleMountedRunOpticalCentering,
|
|
)
|
|
from aare_bec_proto.messages import MXMeasurementSequenceMessage
|
|
|
|
|
|
class FakeConfigValue:
|
|
"""Stand-in for RedisConfigValue: a bool with subscribers, no Redis."""
|
|
|
|
initial = False # value "found in Redis" on construction
|
|
|
|
def __init__(self, connector, endpoint, wait_for_writes=True):
|
|
self.endpoint = endpoint
|
|
self._value = self.initial
|
|
self._cbs = []
|
|
self.unregistered = False
|
|
|
|
@property
|
|
def value(self):
|
|
return self._value
|
|
|
|
@value.setter
|
|
def value(self, value):
|
|
self._value = value
|
|
for cb in self._cbs:
|
|
cb(value)
|
|
|
|
def subscribe(self, cb):
|
|
self._cbs.append(cb)
|
|
|
|
def unregister_all(self):
|
|
self.unregistered = True
|
|
|
|
|
|
class DummyActor(MXAutomationActor):
|
|
config_key = "dummy"
|
|
|
|
def default_monitor_endpoints(self) -> set[EndpointInfo[type[MXMeasurementSequenceMessage]]]:
|
|
return set()
|
|
|
|
def condition(self, client) -> bool:
|
|
return True
|
|
|
|
def action(self, client) -> None:
|
|
pass
|
|
|
|
|
|
@pytest.fixture
|
|
def manager():
|
|
with patch("aare_bec_proto.actor_manager.RedisConfigValue", FakeConfigValue):
|
|
client = MagicMock()
|
|
mgr = AareActorManager(client, actor_classes=[DummyActor])
|
|
yield mgr, client
|
|
mgr.shutdown()
|
|
|
|
|
|
def test_all_actor_classes_finds_concrete_subclasses():
|
|
classes = all_actor_classes()
|
|
assert OnSampleMountedRunOpticalCentering in classes
|
|
assert OnOpticalCenteringDoneRunXrayCentering in classes
|
|
assert MXAutomationActor not in classes
|
|
assert len({c.config_key for c in classes}) == len(classes), "config keys must be unique"
|
|
|
|
|
|
def test_default_manager_manages_all_actors():
|
|
with patch("aare_bec_proto.actor_manager.RedisConfigValue", FakeConfigValue):
|
|
mgr = AareActorManager(MagicMock())
|
|
assert mgr.actor_classes == all_actor_classes()
|
|
assert mgr.running == []
|
|
|
|
|
|
def test_disabled_actor_is_not_started(manager):
|
|
mgr, _ = manager
|
|
assert not mgr.is_enabled(DummyActor)
|
|
assert mgr.running == []
|
|
|
|
|
|
def test_enabling_starts_and_disabling_stops_actor(manager):
|
|
mgr, client = manager
|
|
mgr.set_enabled(DummyActor, True)
|
|
assert mgr.running == [DummyActor]
|
|
actor, thread, _ = mgr._actors_threads_and_stops[DummyActor]
|
|
assert isinstance(actor, DummyActor)
|
|
assert thread.is_alive()
|
|
assert client.connector.send.called # clients were pinged
|
|
|
|
mgr.set_enabled(DummyActor, False)
|
|
assert mgr.running == []
|
|
assert not thread.is_alive()
|
|
|
|
|
|
def test_actor_enabled_at_startup_is_started():
|
|
with (
|
|
patch("aare_bec_proto.actor_manager.RedisConfigValue", FakeConfigValue),
|
|
patch.object(FakeConfigValue, "initial", True),
|
|
):
|
|
mgr = AareActorManager(MagicMock(), actor_classes=[DummyActor])
|
|
try:
|
|
assert mgr.running == [DummyActor]
|
|
finally:
|
|
mgr.shutdown()
|
|
assert mgr.running == []
|
|
|
|
|
|
def test_shutdown_unregisters_flags(manager):
|
|
mgr, _ = manager
|
|
mgr.set_enabled(DummyActor, True)
|
|
mgr.shutdown()
|
|
assert mgr.running == []
|
|
assert all(flag.unregistered for flag in mgr._enabled_flags.values())
|