diff --git a/src/ophyd_async/core/device.py b/src/ophyd_async/core/device.py index b68c213089..4733a1bc2c 100644 --- a/src/ophyd_async/core/device.py +++ b/src/ophyd_async/core/device.py @@ -18,7 +18,7 @@ from bluesky.protocols import HasName from bluesky.run_engine import call_in_bluesky_event_loop -from .utils import DEFAULT_TIMEOUT, wait_for_connection +from .utils import DEFAULT_TIMEOUT, NotConnected, wait_for_connection class Device(HasName): @@ -173,4 +173,12 @@ async def __aexit__(self, type, value, traceback): def __exit__(self, type_, value, traceback): self._objects_on_exit = self._caller_locals() - return call_in_bluesky_event_loop(self._on_exit()) + try: + fut = call_in_bluesky_event_loop(self._on_exit()) + except RuntimeError: + raise NotConnected( + "Could not connect devices. Is the bluesky event loop running? See " + "https://blueskyproject.io/ophyd-async/main/" + "user/explanations/event-loop-choice.html for more info." + ) + return fut diff --git a/tests/core/test_device_collector.py b/tests/core/test_device_collector.py index 271fc3aed1..5f1f3bc177 100644 --- a/tests/core/test_device_collector.py +++ b/tests/core/test_device_collector.py @@ -3,16 +3,26 @@ from ophyd_async.core import DEFAULT_TIMEOUT, Device, DeviceCollector, NotConnected -class Dummy(Device): +class FailingDevice(Device): async def connect(self, sim: bool = False, timeout=DEFAULT_TIMEOUT): raise AttributeError() -def test_device_collector_handles_top_level_errors(RE, caplog): +class WorkingDevice(Device): + connected = False + + async def connect(self, sim: bool = True, timeout=DEFAULT_TIMEOUT): + self.connected = True + return await super().connect(sim=True) + + async def set(self, new_position: float): ... + + +async def test_device_collector_handles_top_level_errors(caplog): caplog.set_level(10) with pytest.raises(NotConnected) as exc: - with DeviceCollector(): - _ = Dummy("somename") + async with DeviceCollector(): + _ = FailingDevice("somename") assert not exc.value.__cause__ @@ -25,3 +35,40 @@ def test_device_collector_handles_top_level_errors(RE, caplog): assert len(device_log) == 1 device_log[0].levelname == "ERROR" + + +def test_device_connector_sync_no_run_engine_raises_error(): + with pytest.raises(NotConnected) as e: + with DeviceCollector(): + working_device = WorkingDevice("somename") + assert e.value._errors == ( + "Could not connect devices. Is the bluesky event loop running? See " + "https://blueskyproject.io/ophyd-async/main/" + "user/explanations/event-loop-choice.html for more info." + ) + assert not working_device.connected + + +def test_device_connector_sync_run_engine_created_connects(RE): + with DeviceCollector(): + working_device = WorkingDevice("somename") + + assert working_device.connected + + +""" +# TODO: Once passing a loop into the run-engine selector works, this should pass +async def test_device_connector_async_run_engine_same_event_loop(): + async with DeviceCollector(sim=True): + sim_motor = motor.Motor("BLxxI-MO-TABLE-01:X") + + RE = RunEngine(loop=asyncio.get_running_loop()) + + def my_plan(): + sim_motor.move(3.14) + return + + RE(my_plan()) + + assert await sim_motor.readback.get_value() == 3.14 +"""