Skip to content

Commit 5e564f0

Browse files
authored
Don't return completed_status() in set_and_wait_for_value (#805)
* Don't return completed_status() in set_and_wait_for_value * Add support for callable match_value in set_and_wait_for_value * explicitly use asyncio.TimeoutError in set_and_wait_for_other_value
1 parent ee56ab7 commit 5e564f0

File tree

3 files changed

+88
-9
lines changed

3 files changed

+88
-9
lines changed

src/ophyd_async/core/_signal.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from ._protocol import AsyncReadable, AsyncStageable
2424
from ._signal_backend import SignalBackend, SignalDatatypeT, SignalDatatypeV
2525
from ._soft_signal_backend import SoftSignalBackend
26-
from ._status import AsyncStatus, completed_status
26+
from ._status import AsyncStatus
2727
from ._utils import (
2828
CALCULATE_TIMEOUT,
2929
DEFAULT_TIMEOUT,
@@ -579,25 +579,34 @@ async def set_and_wait_for_other_value(
579579

580580
status = set_signal.set(set_value, timeout=set_timeout)
581581

582+
if callable(match_value):
583+
matcher: Callable[[SignalDatatypeV], bool] = match_value # type: ignore
584+
else:
585+
586+
def matcher(value):
587+
return value == match_value
588+
589+
matcher.__name__ = f"equals_{match_value}"
590+
582591
# If the value was the same as before no need to wait for it to change
583-
if current_value != match_value:
592+
if not matcher(current_value):
584593

585594
async def _wait_for_value():
586595
async for value in values_gen:
587-
if value == match_value:
596+
if matcher(value):
588597
break
589598

590599
try:
591600
await asyncio.wait_for(_wait_for_value(), timeout)
592601
if wait_for_set_completion:
593602
await status
594-
return status
595603
except asyncio.TimeoutError as e:
596-
raise TimeoutError(
597-
f"{match_signal.name} didn't match {match_value} in {timeout}s"
604+
raise asyncio.TimeoutError(
605+
f"{match_signal.name} value didn't match value from"
606+
f" {matcher.__name__}() in {timeout}s"
598607
) from e
599608

600-
return completed_status()
609+
return status
601610

602611

603612
async def set_and_wait_for_value(

tests/core/test_signal.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,79 @@ async def check_set_and_wait():
239239
)
240240
)
241241

242-
with pytest.raises(TimeoutError):
242+
with pytest.raises(asyncio.TimeoutError):
243243
await asyncio.gather(wait_and_set_read(), check_set_and_wait())
244244

245245

246+
async def test_status_of_set_and_wait_for_value():
247+
set_signal = epics_signal_rw(int, "pva://signal")
248+
match_signal = epics_signal_rw(int, "pva://match_signal")
249+
250+
async def set_match_signal_after_delay(value: Any, **kwargs):
251+
await asyncio.sleep(0.3)
252+
await match_signal.set(value / 2)
253+
254+
await set_signal.connect(mock=True)
255+
await match_signal.connect(mock=True)
256+
callback_on_mock_put(set_signal, set_match_signal_after_delay) # type: ignore
257+
258+
status = await set_and_wait_for_value(set_signal, 2)
259+
assert status.done
260+
261+
with pytest.raises(asyncio.TimeoutError):
262+
status = await set_and_wait_for_value(set_signal, 4, timeout=0.1)
263+
264+
with pytest.raises(asyncio.TimeoutError):
265+
status = await set_and_wait_for_value(
266+
set_signal, 8, timeout=0.1, wait_for_set_completion=False
267+
)
268+
269+
status = await set_and_wait_for_other_value(set_signal, 32, match_signal, 16)
270+
assert status.done
271+
272+
status = await set_and_wait_for_other_value(
273+
set_signal, 30, match_signal, 15, wait_for_set_completion=False
274+
)
275+
assert not status.done
276+
await status
277+
278+
with pytest.raises(asyncio.TimeoutError):
279+
status = await set_and_wait_for_other_value(
280+
set_signal, 30, match_signal, -1, timeout=0.5
281+
)
282+
283+
284+
async def test_callable_match_value_set_and_wait_for_value():
285+
set_signal = epics_signal_rw(int, "pva://signal")
286+
match_signal = epics_signal_rw(int, "pva://match_signal")
287+
288+
async def set_match_signal_after_delay(value: Any, **kwargs):
289+
await asyncio.sleep(0.3)
290+
await match_signal.set(value / 2)
291+
292+
await set_signal.connect(mock=True)
293+
await match_signal.connect(mock=True)
294+
callback_on_mock_put(set_signal, set_match_signal_after_delay) # type: ignore
295+
296+
def _equals_x(value, x):
297+
return value == x
298+
299+
status = await set_and_wait_for_value(
300+
set_signal, 10, lambda val: _equals_x(val, 10)
301+
)
302+
assert status.done
303+
304+
status = await set_and_wait_for_other_value(
305+
set_signal, 20, match_signal, lambda val: _equals_x(val, 10)
306+
)
307+
assert status.done
308+
309+
with pytest.raises(asyncio.TimeoutError):
310+
status = await set_and_wait_for_other_value(
311+
set_signal, 30, match_signal, lambda val: _equals_x(val, -1), timeout=0.5
312+
)
313+
314+
246315
async def test_wait_for_value_with_value():
247316
signal = epics_signal_rw(str, read_pv="pva://signal", name="signal")
248317
await signal.connect(mock=True)

tests/epics/eiger/test_eiger_controller.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
from unittest.mock import ANY, patch
23

34
from pytest import fixture, raises
@@ -76,7 +77,7 @@ async def test_given_detector_fails_to_go_ready_when_arm_called_then_fails(
7677
eiger_driver_and_controller_no_arm: DriverAndController,
7778
):
7879
_, controller = eiger_driver_and_controller_no_arm
79-
with raises(TimeoutError):
80+
with raises(asyncio.TimeoutError):
8081
await controller.prepare(TriggerInfo(number_of_triggers=10))
8182
await controller.arm()
8283
await controller.wait_for_idle()

0 commit comments

Comments
 (0)