From 58de902a065e7ea47ae1a16676f887362aa1b09a Mon Sep 17 00:00:00 2001 From: Relm-Arrowny Date: Mon, 9 Dec 2024 16:27:53 +0000 Subject: [PATCH 1/4] add pytest.approx to assert reading --- src/ophyd_async/testing/_assert.py | 16 +++- tests/core/test_signal.py | 123 +++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/src/ophyd_async/testing/_assert.py b/src/ophyd_async/testing/_assert.py index 25d38d12b6..2e6f2c98e4 100644 --- a/src/ophyd_async/testing/_assert.py +++ b/src/ophyd_async/testing/_assert.py @@ -1,6 +1,7 @@ from collections.abc import Mapping from typing import Any +import pytest from bluesky.protocols import Reading from ophyd_async.core import AsyncConfigurable, AsyncReadable, SignalDatatypeT, SignalR @@ -41,6 +42,13 @@ async def assert_value(signal: SignalR[SignalDatatypeT], value: Any) -> None: ) +def _approx_readable_value(reading: Mapping[str, Reading]) -> Mapping[str, Reading]: + """Change Reading value to pytest.approx(value)""" + for i in reading: + reading[i]["value"] = pytest.approx(reading[i]["value"]) + return reading + + async def assert_reading( readable: AsyncReadable, expected_reading: Mapping[str, Reading] ) -> None: @@ -61,7 +69,9 @@ async def assert_reading( """ actual_reading = await readable.read() - assert expected_reading == actual_reading, _generate_assert_error_msg( + assert expected_reading == _approx_readable_value( + actual_reading + ), _generate_assert_error_msg( name=readable.name, expected_result=expected_reading, actual_result=actual_reading, @@ -89,7 +99,9 @@ async def assert_configuration( """ actual_configurable = await configurable.read_configuration() - assert configuration == actual_configurable, _generate_assert_error_msg( + assert configuration == _approx_readable_value( + actual_configurable + ), _generate_assert_error_msg( name=configurable.name, expected_result=configuration, actual_result=actual_configurable, diff --git a/tests/core/test_signal.py b/tests/core/test_signal.py index 8f88c653a8..3f291f7703 100644 --- a/tests/core/test_signal.py +++ b/tests/core/test_signal.py @@ -11,6 +11,7 @@ from bluesky.protocols import Reading from ophyd_async.core import ( + Array1D, DeviceCollector, SignalR, SignalRW, @@ -257,6 +258,15 @@ async def mock_signal(): yield mock_signal +@pytest.fixture +async def mock_signal_array(): + mock_signal_array = epics_signal_rw( + Array1D[np.int8], "pva://mock_signal", name="mock_signal" + ) + await mock_signal_array.connect(mock=True) + yield mock_signal_array + + async def test_assert_value(mock_signal: SignalRW): set_mock_value(mock_signal, 168) await assert_value(mock_signal, 168) @@ -279,6 +289,119 @@ async def test_failed_assert_reading(mock_signal: SignalRW): await assert_reading(mock_signal, dummy_reading) +class DummyReadableArray(StandardReadable): + """A demo Readable to produce read and config signal""" + + def __init__(self, prefix: str, name="") -> None: + # Define some signals + with self.add_children_as_readables(Format.HINTED_SIGNAL): + self.value = epics_signal_r(Array1D[np.int8], prefix + "Value") + self.value2 = epics_signal_r(Array1D[np.float32], prefix + "Value") + # Set name and signals for read() and read_configuration() + with self.add_children_as_readables(Format.CONFIG_SIGNAL): + self.mode = epics_signal_rw(Array1D[np.int8], prefix + "array1") + self.mode2 = epics_signal_rw(Array1D[np.float64], prefix + "array2") + super().__init__(name=name) + + +@pytest.fixture +async def mock_readable_array(): + async with DeviceCollector(mock=True): + mock_readable_array = DummyReadableArray("SIM:READABLE:", name="mock_readable") + + yield mock_readable_array + + +async def test_assert_reading_array(mock_readable_array: DummyReadableArray): + set_mock_value(mock_readable_array.value, np.array([1, 2, 4, 6])) + set_mock_value(mock_readable_array.value2, np.array([1, 2, 4, 7])) + dummy_reading = { + "mock_readable-value": Reading( + { + "alarm_severity": 0, + "timestamp": ANY, + "value": [1, 2, 4, 6], + } + ), + "mock_readable-value2": Reading( + { + "alarm_severity": 0, + "timestamp": ANY, + "value": [1, 2, 4, 7], + } + ), + } + await assert_reading(mock_readable_array, dummy_reading) + + +async def test_assert_reading_array_fail(mock_readable_array: DummyReadableArray): + set_mock_value(mock_readable_array.value, np.array([1, 2, 4, 6])) + set_mock_value(mock_readable_array.value2, np.array([1, 2, 4, 7])) + dummy_reading = { + "mock_readable-value": Reading( + { + "alarm_severity": 0, + "timestamp": ANY, + "value": [1, 2, 4, 6], + } + ), + "mock_readable-value2": Reading( + { + "alarm_severity": 0, + "timestamp": ANY, + "value": [1, 2, 4, 7.1], + } + ), + } + with pytest.raises(AssertionError): + await assert_reading(mock_readable_array, dummy_reading) + + +async def test_assert_configuraion_array(mock_readable_array: DummyReadableArray): + set_mock_value(mock_readable_array.mode, np.array([1, 2, 4, 6])) + set_mock_value(mock_readable_array.mode2, np.array([1, 2, 4, 7])) + dummy_reading = { + "mock_readable-mode": Reading( + { + "alarm_severity": 0, + "timestamp": ANY, + "value": [1, 2, 4, 6], + } + ), + "mock_readable-mode2": Reading( + { + "alarm_severity": 0, + "timestamp": ANY, + "value": [1, 2, 4, 7], + } + ), + } + await assert_configuration(mock_readable_array, dummy_reading) + + +async def test_assert_configuraion_array_fail(mock_readable_array: DummyReadableArray): + set_mock_value(mock_readable_array.mode, np.array([1, 2, 4, 6])) + set_mock_value(mock_readable_array.mode2, np.array([1, 2, 4, 7])) + dummy_reading = { + "mock_readable-mode": Reading( + { + "alarm_severity": 0, + "timestamp": ANY, + "value": [1, 2, 4, 6], + } + ), + "mock_readable-mode2": Reading( + { + "alarm_severity": 0, + "timestamp": ANY, + "value": [1, 2, 0.4, 7], + } + ), + } + with pytest.raises(AssertionError): + await assert_configuration(mock_readable_array, dummy_reading) + + class DummyReadable(StandardReadable): """A demo Readable to produce read and config signal""" From b9f42378727bfdcb9b04114f00feafd56a78ecec Mon Sep 17 00:00:00 2001 From: Relm-Arrowny Date: Mon, 9 Dec 2024 17:01:39 +0000 Subject: [PATCH 2/4] remove unused test --- tests/core/test_signal.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/core/test_signal.py b/tests/core/test_signal.py index 3f291f7703..77810dc536 100644 --- a/tests/core/test_signal.py +++ b/tests/core/test_signal.py @@ -258,15 +258,6 @@ async def mock_signal(): yield mock_signal -@pytest.fixture -async def mock_signal_array(): - mock_signal_array = epics_signal_rw( - Array1D[np.int8], "pva://mock_signal", name="mock_signal" - ) - await mock_signal_array.connect(mock=True) - yield mock_signal_array - - async def test_assert_value(mock_signal: SignalRW): set_mock_value(mock_signal, 168) await assert_value(mock_signal, 168) From d436c18b49ba2bc28bbc8c1b52c1452424ad7e2f Mon Sep 17 00:00:00 2001 From: Relm-Arrowny Date: Tue, 10 Dec 2024 09:30:49 +0000 Subject: [PATCH 3/4] consolidate test --- tests/core/test_signal.py | 190 +++++++++++++++----------------------- 1 file changed, 75 insertions(+), 115 deletions(-) diff --git a/tests/core/test_signal.py b/tests/core/test_signal.py index 77810dc536..c88597ec12 100644 --- a/tests/core/test_signal.py +++ b/tests/core/test_signal.py @@ -17,6 +17,7 @@ SignalRW, SoftSignalBackend, StandardReadable, + StrictEnum, set_and_wait_for_other_value, set_and_wait_for_value, soft_signal_r_and_setter, @@ -251,33 +252,9 @@ async def test_create_soft_signal(signal_method, signal_class): assert (await signal.get_value()) == INITIAL_VALUE -@pytest.fixture -async def mock_signal(): - mock_signal = epics_signal_rw(int, "pva://mock_signal", name="mock_signal") - await mock_signal.connect(mock=True) - yield mock_signal - - -async def test_assert_value(mock_signal: SignalRW): - set_mock_value(mock_signal, 168) - await assert_value(mock_signal, 168) - - -async def test_assert_reaading(mock_signal: SignalRW): - set_mock_value(mock_signal, 888) - dummy_reading = { - "mock_signal": Reading({"alarm_severity": 0, "timestamp": ANY, "value": 888}) - } - await assert_reading(mock_signal, dummy_reading) - - -async def test_failed_assert_reading(mock_signal: SignalRW): - set_mock_value(mock_signal, 888) - dummy_reading = { - "mock_signal": Reading({"alarm_severity": 0, "timestamp": ANY, "value": 88}) - } - with pytest.raises(AssertionError): - await assert_reading(mock_signal, dummy_reading) +class MockEnum(StrictEnum): + GOOD = "Good" + OK = "Ok" class DummyReadableArray(StandardReadable): @@ -286,154 +263,137 @@ class DummyReadableArray(StandardReadable): def __init__(self, prefix: str, name="") -> None: # Define some signals with self.add_children_as_readables(Format.HINTED_SIGNAL): - self.value = epics_signal_r(Array1D[np.int8], prefix + "Value") - self.value2 = epics_signal_r(Array1D[np.float32], prefix + "Value") + self.int_value = epics_signal_r(int, prefix + "int") + self.int_array = epics_signal_r(Array1D[np.int8], prefix + "Value") + self.float_array = epics_signal_r(Array1D[np.float32], prefix + "Value") # Set name and signals for read() and read_configuration() with self.add_children_as_readables(Format.CONFIG_SIGNAL): - self.mode = epics_signal_rw(Array1D[np.int8], prefix + "array1") - self.mode2 = epics_signal_rw(Array1D[np.float64], prefix + "array2") + self.str_value = epics_signal_rw(str, prefix + "Value") + self.strictEnum_value = epics_signal_rw(MockEnum, prefix + "array2") super().__init__(name=name) @pytest.fixture -async def mock_readable_array(): +async def mock_readable(): async with DeviceCollector(mock=True): - mock_readable_array = DummyReadableArray("SIM:READABLE:", name="mock_readable") + mock_readable = DummyReadableArray("SIM:READABLE:", name="mock_readable") + yield mock_readable + + +async def test_assert_value(mock_readable: DummyReadableArray): + set_mock_value(mock_readable.int_value, 168) + await assert_value(mock_readable.int_value, 168) - yield mock_readable_array +async def test_assert_reaading(mock_readable: DummyReadableArray): + set_mock_value(mock_readable.int_value, 188) + set_mock_value(mock_readable.int_array, np.array([1, 2, 4, 7])) + set_mock_value(mock_readable.float_array, np.array([1.1231, -2.3, 451.15, 6.6233])) -async def test_assert_reading_array(mock_readable_array: DummyReadableArray): - set_mock_value(mock_readable_array.value, np.array([1, 2, 4, 6])) - set_mock_value(mock_readable_array.value2, np.array([1, 2, 4, 7])) dummy_reading = { - "mock_readable-value": Reading( - { - "alarm_severity": 0, - "timestamp": ANY, - "value": [1, 2, 4, 6], - } + "mock_readable-int_value": Reading( + {"alarm_severity": 0, "timestamp": ANY, "value": 188} ), - "mock_readable-value2": Reading( + "mock_readable-int_array": Reading( + {"alarm_severity": 0, "timestamp": ANY, "value": [1, 2, 4, 7]} + ), + "mock_readable-float_array": Reading( { "alarm_severity": 0, "timestamp": ANY, - "value": [1, 2, 4, 7], + "value": [1.1231, -2.3, 451.15, 6.6233], } ), } - await assert_reading(mock_readable_array, dummy_reading) + await assert_reading(mock_readable, dummy_reading) -async def test_assert_reading_array_fail(mock_readable_array: DummyReadableArray): - set_mock_value(mock_readable_array.value, np.array([1, 2, 4, 6])) - set_mock_value(mock_readable_array.value2, np.array([1, 2, 4, 7])) +@pytest.mark.parametrize( + "int_value, int_array, float_array", + [ + ([128, np.array([1, 2, 4, 7]), np.array([1.1231, -2.3, 451.15, 6.6233])]), + ([188, np.array([-5, 2, 4, 7]), np.array([1.1231, -2.3, 451.15, 6.6233])]), + ([188, np.array([1, 2, 4, 7]), np.array([1.231, -2.3, 451.15, 6.6233])]), + ], +) +async def test_failed_assert_reading( + mock_readable: DummyReadableArray, int_value, int_array, float_array +): + set_mock_value(mock_readable.int_value, 188) + set_mock_value(mock_readable.int_array, np.array([1, 2, 4, 7])) + set_mock_value(mock_readable.float_array, np.array([1.1231, -2.3, 451.15, 6.6233])) + dummy_reading = { - "mock_readable-value": Reading( - { - "alarm_severity": 0, - "timestamp": ANY, - "value": [1, 2, 4, 6], - } + "mock_readable-int_value": Reading( + {"alarm_severity": 0, "timestamp": ANY, "value": int_value} + ), + "mock_readable-int_array": Reading( + {"alarm_severity": 0, "timestamp": ANY, "value": int_array} ), - "mock_readable-value2": Reading( + "mock_readable-float_array": Reading( { "alarm_severity": 0, "timestamp": ANY, - "value": [1, 2, 4, 7.1], + "value": float_array, } ), } with pytest.raises(AssertionError): - await assert_reading(mock_readable_array, dummy_reading) + await assert_reading(mock_readable, dummy_reading) -async def test_assert_configuraion_array(mock_readable_array: DummyReadableArray): - set_mock_value(mock_readable_array.mode, np.array([1, 2, 4, 6])) - set_mock_value(mock_readable_array.mode2, np.array([1, 2, 4, 7])) +async def test_assert_configuraion(mock_readable: DummyReadableArray): + set_mock_value(mock_readable.str_value, "haha") + set_mock_value(mock_readable.strictEnum_value, MockEnum.GOOD) dummy_reading = { - "mock_readable-mode": Reading( + "mock_readable-str_value": Reading( { "alarm_severity": 0, "timestamp": ANY, - "value": [1, 2, 4, 6], + "value": "haha", } ), - "mock_readable-mode2": Reading( + "mock_readable-strictEnum_value": Reading( { "alarm_severity": 0, "timestamp": ANY, - "value": [1, 2, 4, 7], + "value": MockEnum.GOOD, } ), } - await assert_configuration(mock_readable_array, dummy_reading) + await assert_configuration(mock_readable, dummy_reading) -async def test_assert_configuraion_array_fail(mock_readable_array: DummyReadableArray): - set_mock_value(mock_readable_array.mode, np.array([1, 2, 4, 6])) - set_mock_value(mock_readable_array.mode2, np.array([1, 2, 4, 7])) +@pytest.mark.parametrize( + "str_value, enum_value", + [ + ("ha", MockEnum.OK), + ("not funny", MockEnum.GOOD), + ], +) +async def test_assert_configuraion_fail( + mock_readable: DummyReadableArray, str_value, enum_value +): + set_mock_value(mock_readable.str_value, "haha") + set_mock_value(mock_readable.strictEnum_value, MockEnum.GOOD) dummy_reading = { "mock_readable-mode": Reading( { "alarm_severity": 0, "timestamp": ANY, - "value": [1, 2, 4, 6], + "value": str_value, } ), "mock_readable-mode2": Reading( { "alarm_severity": 0, "timestamp": ANY, - "value": [1, 2, 0.4, 7], + "value": enum_value, } ), } with pytest.raises(AssertionError): - await assert_configuration(mock_readable_array, dummy_reading) - - -class DummyReadable(StandardReadable): - """A demo Readable to produce read and config signal""" - - def __init__(self, prefix: str, name="") -> None: - # Define some signals - with self.add_children_as_readables(Format.HINTED_SIGNAL): - self.value = epics_signal_r(float, prefix + "Value") - with self.add_children_as_readables(Format.CONFIG_SIGNAL): - self.mode = epics_signal_rw(str, prefix + "Mode") - self.mode2 = epics_signal_rw(str, prefix + "Mode2") - # Set name and signals for read() and read_configuration() - super().__init__(name=name) - - -@pytest.fixture -async def mock_readable(): - async with DeviceCollector(mock=True): - mock_readable = DummyReadable("SIM:READABLE:", name="mock_readable") - - yield mock_readable - - -async def test_assert_configuration(mock_readable: DummyReadable): - set_mock_value(mock_readable.value, 123) - set_mock_value(mock_readable.mode, "super mode") - set_mock_value(mock_readable.mode2, "slow mode") - dummy_config_reading = { - "mock_readable-mode": ( - { - "alarm_severity": 0, - "timestamp": ANY, - "value": "super mode", - } - ), - "mock_readable-mode2": { - "alarm_severity": 0, - "timestamp": ANY, - "value": "slow mode", - }, - } - await assert_configuration(mock_readable, dummy_config_reading) + await assert_configuration(mock_readable, dummy_reading) async def test_signal_get_and_set_logging(caplog): From 8ba46d2af4b05a9490fbdfda2a2e8a116e91d5d9 Mon Sep 17 00:00:00 2001 From: Relm-Arrowny Date: Tue, 10 Dec 2024 09:48:49 +0000 Subject: [PATCH 4/4] use approx on expected instead of actual --- src/ophyd_async/testing/_assert.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ophyd_async/testing/_assert.py b/src/ophyd_async/testing/_assert.py index 2e6f2c98e4..849c1a74ec 100644 --- a/src/ophyd_async/testing/_assert.py +++ b/src/ophyd_async/testing/_assert.py @@ -69,8 +69,8 @@ async def assert_reading( """ actual_reading = await readable.read() - assert expected_reading == _approx_readable_value( - actual_reading + assert ( + _approx_readable_value(expected_reading) == actual_reading ), _generate_assert_error_msg( name=readable.name, expected_result=expected_reading, @@ -99,8 +99,8 @@ async def assert_configuration( """ actual_configurable = await configurable.read_configuration() - assert configuration == _approx_readable_value( - actual_configurable + assert ( + _approx_readable_value(configuration) == actual_configurable ), _generate_assert_error_msg( name=configurable.name, expected_result=configuration,