From 302597c5eea4b0d03b62a668b5a1aff979feedab Mon Sep 17 00:00:00 2001 From: "Tom C (DLS)" <101418278+coretl@users.noreply.github.com> Date: Fri, 17 May 2024 16:30:28 +0100 Subject: [PATCH] Improve performance of MockSignalBackend (#316) * Put timing back * Make MockSignalBackend more performant * Put back motor timing too --- src/ophyd_async/core/mock_signal_backend.py | 29 ++++++++++----------- src/ophyd_async/core/mock_signal_utils.py | 14 +++++----- tests/core/test_mock_signal_backend.py | 6 +---- tests/epics/demo/test_demo.py | 6 ++--- tests/epics/motion/test_motor.py | 10 +++---- 5 files changed, 27 insertions(+), 38 deletions(-) diff --git a/src/ophyd_async/core/mock_signal_backend.py b/src/ophyd_async/core/mock_signal_backend.py index a00fd62967..77f5fdc2ae 100644 --- a/src/ophyd_async/core/mock_signal_backend.py +++ b/src/ophyd_async/core/mock_signal_backend.py @@ -1,6 +1,7 @@ import asyncio +from functools import cached_property from typing import Optional, Type -from unittest.mock import MagicMock +from unittest.mock import Mock from bluesky.protocols import Descriptor, Reading @@ -36,51 +37,49 @@ def __init__( else: self.soft_backend = initial_backend - self.mock = MagicMock() - - self.put_proceeds = asyncio.Event() - self.put_proceeds.set() - def source(self, name: str) -> str: - self.mock.source(name) if self.initial_backend: return f"mock+{self.initial_backend.source(name)}" return f"mock+{name}" async def connect(self, timeout: float = DEFAULT_TIMEOUT) -> None: - self.mock.connect(timeout=timeout) + pass + + @cached_property + def put_mock(self) -> Mock: + return Mock(name="put") + + @cached_property + def put_proceeds(self) -> asyncio.Event: + put_proceeds = asyncio.Event() + put_proceeds.set() + return put_proceeds async def put(self, value: Optional[T], wait=True, timeout=None): - self.mock.put(value, wait=wait, timeout=timeout) + self.put_mock(value, wait=wait, timeout=timeout) await self.soft_backend.put(value, wait=wait, timeout=timeout) if wait: await asyncio.wait_for(self.put_proceeds.wait(), timeout=timeout) def set_value(self, value: T): - self.mock.set_value(value) self.soft_backend.set_value(value) async def get_descriptor(self, source: str) -> Descriptor: - self.mock.get_descriptor(source) return await self.soft_backend.get_descriptor(source) async def get_reading(self) -> Reading: - self.mock.get_reading() return await self.soft_backend.get_reading() async def get_value(self) -> T: - self.mock.get_value() return await self.soft_backend.get_value() async def get_setpoint(self) -> T: """For a soft signal, the setpoint and readback values are the same.""" - self.mock.get_setpoint() return await self.soft_backend.get_setpoint() async def get_datakey(self, source: str) -> Descriptor: return await self.soft_backend.get_datakey(source) def set_callback(self, callback: Optional[ReadingValueCallback[T]]) -> None: - self.mock.set_callback(callback) self.soft_backend.set_callback(callback) diff --git a/src/ophyd_async/core/mock_signal_utils.py b/src/ophyd_async/core/mock_signal_utils.py index bf93187e28..6580838425 100644 --- a/src/ophyd_async/core/mock_signal_utils.py +++ b/src/ophyd_async/core/mock_signal_utils.py @@ -1,6 +1,6 @@ from contextlib import asynccontextmanager, contextmanager from typing import Any, Callable, Generator, Iterable, Iterator, List -from unittest.mock import ANY +from unittest.mock import ANY, Mock from ophyd_async.core.signal import Signal from ophyd_async.core.utils import T @@ -43,12 +43,12 @@ async def mock_puts_blocked(*signals: List[Signal]): def assert_mock_put_called_with(signal: Signal, value: Any, wait=ANY, timeout=ANY): backend = _get_mock_signal_backend(signal) - backend.mock.put.assert_called_with(value, wait=wait, timeout=timeout) + backend.put_mock.assert_called_with(value, wait=wait, timeout=timeout) def reset_mock_put_calls(signal: Signal): backend = _get_mock_signal_backend(signal) - backend.mock.put.reset_mock() + backend.put_mock.reset_mock() class _SetValuesIterator: @@ -122,9 +122,9 @@ def set_mock_values( @contextmanager -def _unset_side_effect_cm(mock): +def _unset_side_effect_cm(put_mock: Mock): yield - mock.put.side_effect = None + put_mock.side_effect = None # linting isn't smart enought to realize @contextmanager will give use a @@ -145,5 +145,5 @@ def callback_on_mock_put( The callback to call when the backend is put to during the context. """ backend = _get_mock_signal_backend(signal) - backend.mock.put.side_effect = callback - return _unset_side_effect_cm(backend.mock) + backend.put_mock.side_effect = callback + return _unset_side_effect_cm(backend.put_mock) diff --git a/tests/core/test_mock_signal_backend.py b/tests/core/test_mock_signal_backend.py index fae2ad81b2..36658b43a1 100644 --- a/tests/core/test_mock_signal_backend.py +++ b/tests/core/test_mock_signal_backend.py @@ -35,11 +35,7 @@ async def test_mock_signal_backend(connect_mock_mode): assert await mock_signal._backend.get_value() == "" await mock_signal._backend.put("test") assert await mock_signal._backend.get_value() == "test" - - mock_signal._backend.mock.get_value.assert_called_once - - mock_signal._backend.mock["get_value"].assert_called_once - assert mock_signal._backend.mock.put.call_args_list == [ + assert mock_signal._backend.put_mock.call_args_list == [ call("test", wait=True, timeout=None), ] diff --git a/tests/epics/demo/test_demo.py b/tests/epics/demo/test_demo.py index 00ab878e3a..f8e8303d25 100644 --- a/tests/epics/demo/test_demo.py +++ b/tests/epics/demo/test_demo.py @@ -121,7 +121,7 @@ async def test_mover_moving_well(mock_mover: demo.Mover) -> None: target=0.55, unit="mm", precision=3, - time_elapsed=ANY, # Test is flaky in slow CI + time_elapsed=pytest.approx(0.0, abs=0.05), ) await assert_value(mock_mover.setpoint, 0.55) @@ -136,9 +136,7 @@ async def test_mover_moving_well(mock_mover: demo.Mover) -> None: target=0.55, unit="mm", precision=3, - # this issue is being tracked by https://github.com/bluesky/ophyd-async/issues/312 - # time_elapsed=pytest.approx(0.1, abs=0.05), - time_elapsed=ANY, + time_elapsed=pytest.approx(0.1, abs=0.05), ) set_mock_value(mock_mover.readback, 0.5499999) await asyncio.sleep(A_WHILE) diff --git a/tests/epics/motion/test_motor.py b/tests/epics/motion/test_motor.py index 962cc9bbcb..4b45ba66d1 100644 --- a/tests/epics/motion/test_motor.py +++ b/tests/epics/motion/test_motor.py @@ -1,7 +1,7 @@ import asyncio import time from typing import Dict -from unittest.mock import ANY, Mock, call +from unittest.mock import Mock, call import pytest from bluesky.protocols import Reading @@ -15,7 +15,7 @@ # Long enough for multiple asyncio event loop cycles to run so # all the tasks have a chance to run -A_BIT = 0.01 +A_BIT = 0.001 @pytest.fixture @@ -94,8 +94,6 @@ async def test_motor_moving_well_2(sim_motor: motor.Motor) -> None: target=0.55, unit="mm", precision=3, - # this issue is being tracked by https://github.com/bluesky/ophyd-async/issues/312 - # time_elapsed=pytest.approx(0.0, abs=0.05), time_elapsed=pytest.approx(0.0, abs=0.05), ) watcher.reset_mock() @@ -112,9 +110,7 @@ async def test_motor_moving_well_2(sim_motor: motor.Motor) -> None: target=0.55, unit="mm", precision=3, - # this issue is being tracked by https://github.com/bluesky/ophyd-async/issues/312 - # time_elapsed=pytest.approx(0.1, abs=0.05), - time_elapsed=ANY, + time_elapsed=pytest.approx(0.1, abs=0.05), ) set_mock_put_proceeds(sim_motor.user_setpoint, True) await asyncio.sleep(A_BIT)