Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

199 test helpers for assering value reading and configuration #226

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9f845db
fix strange doc test problem
Relm-Arrowny Apr 16, 2024
51ba268
added assert_value, assert_reading and assert_configuration and verif…
Relm-Arrowny Apr 17, 2024
a9f5bb3
added test for assert_value, reading and configuration both async and…
Relm-Arrowny Apr 17, 2024
d35c499
moved assert_emitted to signal together with all the helper functions
Relm-Arrowny Apr 17, 2024
a68d0d3
Merge branch 'main' into 199-test-helpers-for-assering-value-reading-…
Relm-Arrowny Apr 17, 2024
94d5016
adding docstrings
Relm-Arrowny Apr 17, 2024
c1124b6
adding docstrings
Relm-Arrowny Apr 17, 2024
c9e73aa
adding docstrings
Relm-Arrowny Apr 17, 2024
a47f96b
change Dict to Mapping and fix all the mypy issue
Relm-Arrowny Apr 17, 2024
cac9b01
fixing all the docstrings
Relm-Arrowny Apr 18, 2024
d383374
change demo to use helper function so it update the doc
Relm-Arrowny Apr 18, 2024
2e2f816
correct contamination from an other branch
Relm-Arrowny Apr 18, 2024
c02962d
Added a line and a link in how_to_test
Relm-Arrowny Apr 18, 2024
53d7e64
fixed a typo
Relm-Arrowny Apr 18, 2024
1181420
Merge branch 'main' into 199-test-helpers-for-assering-value-reading-…
Relm-Arrowny Apr 23, 2024
3f754aa
Change readable and configurable to async version.
Relm-Arrowny Apr 23, 2024
fe4c267
removed the check for async function in _verify_ready and remove tes…
Relm-Arrowny Apr 23, 2024
37ffc0c
added test_sensor_in_plan in demo.py and edited documentation to match
Relm-Arrowny Apr 23, 2024
d1afe9b
Update write-tests-for-devices.rst
Relm-Arrowny Apr 23, 2024
50c40cd
Fixed English in doc
Relm-Arrowny Apr 24, 2024
9921f85
Apply suggestions from code review
Relm-Arrowny Apr 24, 2024
5470bea
Fix not having enough ____ for write-tests-for-devices.rst
Relm-Arrowny Apr 24, 2024
c0cab3d
removed verify_reading
Relm-Arrowny Apr 24, 2024
6fdea0f
Update src/ophyd_async/core/signal.py changing DocmumentType back to …
Relm-Arrowny Apr 26, 2024
c232bf4
removed assert_emitted in test_hdf_panda.
Relm-Arrowny Apr 26, 2024
d12f783
Merge branch 'main' into 199-test-helpers-for-assering-value-reading-…
Relm-Arrowny Apr 26, 2024
261427d
fix deprecation aftering resolving conflict with main
Relm-Arrowny Apr 26, 2024
5e1cdc3
fix auto correct
Relm-Arrowny Apr 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
# docs in the python documentation.
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"bluesky": ("https://blueskyproject.io/bluesky/", None),
"bluesky": ("https://blueskyproject.io/bluesky/main", None),
"numpy": ("https://numpy.org/devdocs/", None),
"databroker": ("https://blueskyproject.io/databroker/", None),
"event-model": ("https://blueskyproject.io/event-model/main", None),
Expand Down
2 changes: 2 additions & 0 deletions docs/user/how-to/write-tests-for-devices.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Sim Utility Functions

Sim signals behave as simply as possible, holding a sensible default value when initialized and retaining any value (in memory) to which they are set. This model breaks down in the case of read-only signals, which cannot be set because there is an expectation of some external device setting them in the real world. There is a utility function, ``set_sim_value``, to mock-set values for sim signals, including read-only ones.

In addition this example also utilizes helper functions like ``assert_reading`` and ``assert_value`` to ensure the validity of device readings and values. For more information see: :doc:`API.core<../generated/ophyd_async.core>`

.. literalinclude:: ../../../tests/epics/demo/test_demo.py
:pyobject: test_sensor_reading_shows_value

Expand Down
8 changes: 8 additions & 0 deletions src/ophyd_async/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
SignalRW,
SignalW,
SignalX,
assert_configuration,
assert_emitted,
assert_reading,
assert_value,
observe_value,
set_and_wait_for_value,
set_sim_callback,
Expand Down Expand Up @@ -99,4 +103,8 @@
"walk_rw_signals",
"load_device",
"save_device",
"assert_reading",
"assert_value",
"assert_configuration",
"assert_emitted",
]
131 changes: 130 additions & 1 deletion src/ophyd_async/core/signal.py
coretl marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@

import asyncio
import functools
from typing import AsyncGenerator, Callable, Dict, Generic, Optional, Union
from typing import (
Any,
AsyncGenerator,
Awaitable,
Callable,
Dict,
Generic,
Mapping,
Optional,
Union,
)

from bluesky.protocols import (
Configurable,
coretl marked this conversation as resolved.
Show resolved Hide resolved
Descriptor,
Locatable,
Location,
Expand All @@ -14,6 +25,7 @@
Stageable,
Subscribable,
)
from event_model.documents import DocumentType

from .async_status import AsyncStatus
from .device import Device
Expand Down Expand Up @@ -253,6 +265,123 @@ def set_sim_callback(signal: Signal[T], callback: ReadingValueCallback[T]) -> No
return _sim_backends[signal].set_callback(callback)


async def _verify_readings(
func: Callable[[], Mapping[str, Reading] | Awaitable[Mapping[str, Reading]]],
expectation: Mapping[str, Reading],
) -> None:
"""Take a read/read_configuration function that return a dictionary and
compare it with the expected result (expectation)

Parameters
----------
func:
read/read_configuration function.

expectation:
The expected value from the readable/Configurable.

Notes
-----
Example usage::
await _verify_readings(readable.read, reading)
Or::
await _verify_readings(configurable.read_configuration, configuration)
Future::
func will only be Waitable in the future and the if loop can be removed:

result = func()

"""
if asyncio.iscoroutinefunction(func):
result = await func()
else:
result = func()
coretl marked this conversation as resolved.
Show resolved Hide resolved
assert result == expectation


async def assert_value(signal: SignalR[T], value: Any) -> None:
"""Assert a signal's value and compare it an expected signal.

Parameters
----------
signal:
signal with get_value.
value:
The expected value from the signal.

Notes
-----
Example usage::
await assert_value(signal, value)

"""
assert await signal.get_value() == value


async def assert_reading(readable: Readable, reading: Mapping[str, Reading]) -> None:
"""Assert readings from readable.

Parameters
----------
readable:
Callable with readable.read function that generate readings.

reading:
The expected readings from the readable.

Notes
-----
Example usage::
await assert_reading(readable, reading)

"""
await _verify_readings(readable.read, reading)
coretl marked this conversation as resolved.
Show resolved Hide resolved


async def assert_configuration(
configurable: Configurable,
coretl marked this conversation as resolved.
Show resolved Hide resolved
configuration: Mapping[str, Reading],
) -> None:
"""Assert readings from Configurable.

Parameters
----------
configurable:
Configurable with Configurable.read function that generate readings.

configuration:
The expected readings from configurable.

Notes
-----
Example usage::
await assert_configuration(configurable configuration)

"""
await _verify_readings(configurable.read_configuration, configuration)
coretl marked this conversation as resolved.
Show resolved Hide resolved


def assert_emitted(docs: Mapping[str, list[DocumentType]], **numbers: int):
coretl marked this conversation as resolved.
Show resolved Hide resolved
"""Assert emitted document generated by running a Bluesky plan

Parameters
----------
Doc:
A dictionary

numbers:
expected emission in kwarg from

Notes
-----
Example usage::
assert_emitted(docs, start=1, descriptor=1,
resource=1, datum=1, event=1, stop=1)
"""
assert list(docs) == list(numbers)
assert {name: len(d) for name, d in docs.items()} == numbers


async def observe_value(signal: SignalR[T], timeout=None) -> AsyncGenerator[T, None]:
"""Subscribe to the value of a signal so it can be iterated from.

Expand Down
86 changes: 86 additions & 0 deletions tests/core/test_signal.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import asyncio
import re
import time
from typing import Mapping
from unittest.mock import ANY

import pytest
from bluesky.protocols import Reading

from ophyd_async.core import (
DeviceCollector,
Signal,
SignalRW,
SimSignalBackend,
StandardReadable,
assert_configuration,
assert_reading,
assert_value,
set_and_wait_for_value,
set_sim_put_proceeds,
set_sim_value,
wait_for_value,
)
from ophyd_async.core.utils import DEFAULT_TIMEOUT
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw


class MySignal(Signal):
Expand Down Expand Up @@ -119,3 +128,80 @@ async def test_set_and_wait_for_value():
assert not st.done
set_sim_put_proceeds(sim_signal, True)
assert await time_taken_by(st) < 0.1


@pytest.fixture
async def sim_signal():
sim_signal = SignalRW(SimSignalBackend(int, "test"))
sim_signal.set_name("sim_signal")
await sim_signal.connect(sim=True)
yield sim_signal


async def test_assert_value(sim_signal: SignalRW):
set_sim_value(sim_signal, 168)
await assert_value(sim_signal, 168)


async def test_assert_reaading(sim_signal: SignalRW):
set_sim_value(sim_signal, 888)
dummy_reading = {
"sim_signal": Reading({"alarm_severity": 0, "timestamp": ANY, "value": 888})
}
await assert_reading(sim_signal, dummy_reading)


class DummyReadable(StandardReadable):
"""A demo Readable to produce read and config signal"""

def __init__(self, prefix: str, name="") -> None:
# Define some signals
self.value = epics_signal_r(float, prefix + "Value")
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()
self.set_readable_signals(
read=[self.value],
config=[self.mode, self.mode2],
)
super().__init__(name=name)


@pytest.fixture
async def sim_readable():
async with DeviceCollector(sim=True):
sim_readable = DummyReadable("SIM:READABLE:")
# Signals connected here
assert sim_readable.name == "sim_readable"
yield sim_readable


async def test_assert_configuration(sim_readable: DummyReadable):
set_sim_value(sim_readable.value, 123)
set_sim_value(sim_readable.mode, "super mode")
set_sim_value(sim_readable.mode2, "slow mode")
dummy_config_reading: Mapping[str, Reading] = {
"sim_readable-mode": (
{
"alarm_severity": 0,
"timestamp": ANY,
"value": "super mode",
}
),
"sim_readable-mode2": {
"alarm_severity": 0,
"timestamp": ANY,
"value": "slow mode",
},
}
await assert_configuration(sim_readable, dummy_config_reading)
# test for none awaitable part of verify
from ophyd.sim import DetWithConf

something = DetWithConf(name="det")
dummy_config_reading1: Mapping[str, Reading] = {
"det_c": {"value": 3, "timestamp": ANY},
"det_d": {"value": 4, "timestamp": ANY},
}

await assert_configuration(something, dummy_config_reading1)
Loading
Loading