diff --git a/docs/conf.py b/docs/conf.py index 5e18c10..327abae 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -64,7 +64,7 @@ ("py:class", "htss_rig_bluesky.devices.SampleStage"), ("py:class", "SampleStage"), ("py:class", "htss_rig_bluesky.plans.detector.Roi"), - ("py:class", "ophyd_async.epics.areadetector.aravis.AravisDetector"), + ("py:class", "ophyd_async.epics.adaravis._aravis.AravisDetector"), ("py:class", "AravisDetector"), ("py:class", "NoneType"), ("py:class", "'str'"), diff --git a/pyproject.toml b/pyproject.toml index c51960f..5aa10ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ classifiers = [ description = "Bluesky playground environment for Diamond's htss rigs" dependencies = [ "ophyd", - "ophyd-async==0.4.0", + "ophyd-async>=0.8.0", "databroker[client]==2.0.0b17", "matplotlib<3.8", "dls-dodal", @@ -50,6 +50,8 @@ dev = [ "sphinx-design", "tox-direct", "types-mock", + "blueapi>=0.7.0", + "pytest-env", ] [project.scripts] @@ -78,6 +80,9 @@ addopts = """ filterwarnings = "error" # Doctest python code in docs, python code in src docstrings, test functions in tests testpaths = "docs tests" +env= [ + "BEAMLINE=", +] [tool.coverage.run] data_file = "/tmp/htss_rig_bluesky.coverage" @@ -93,7 +98,7 @@ legacy_tox_ini = """ [tox] skipsdist=True -[testenv:{pre-commit,type-checking,tests,docs}] +[testenv:{pre-commit,type-checking,tests,docs,system-test}] # Don't create a virtualenv for the command, requires tox-direct plugin direct = True passenv = * @@ -106,8 +111,9 @@ allowlist_externals = commands = pre-commit: pre-commit run --all-files --show-diff-on-failure {posargs} type-checking: mypy src tests {posargs} - tests: pytest --cov=htss_rig_bluesky --cov-report term --cov-report xml:cov.xml {posargs} + tests: pytest tests/unit_tests --cov=htss_rig_bluesky --cov-report term --cov-report xml:cov.xml {posargs} docs: sphinx-{posargs:build -EW --keep-going} -T docs build/html + system-test: pytest tests/system_tests {posargs} """ [tool.ruff] diff --git a/src/htss_rig_bluesky/devices.py b/src/htss_rig_bluesky/devices.py index f40b232..b403586 100644 --- a/src/htss_rig_bluesky/devices.py +++ b/src/htss_rig_bluesky/devices.py @@ -8,9 +8,9 @@ Device, StaticPathProvider, ) -from ophyd_async.epics.areadetector.aravis import AravisDetector -from ophyd_async.epics.motion import Motor -from ophyd_async.epics.signal import epics_signal_rw +from ophyd_async.epics.adaravis import AravisDetector +from ophyd_async.epics.core import epics_signal_rw +from ophyd_async.epics.motor import Motor from .names import pv_prefix diff --git a/src/htss_rig_bluesky/plans/calibration.py b/src/htss_rig_bluesky/plans/calibration.py index 8d57388..b420432 100644 --- a/src/htss_rig_bluesky/plans/calibration.py +++ b/src/htss_rig_bluesky/plans/calibration.py @@ -1,3 +1,4 @@ +import asyncio from collections.abc import Generator import bluesky.plan_stubs as bps @@ -41,11 +42,14 @@ def scan_center( Yields: Plan """ - - x_range = abs(sample.x.high_limit_travel - sample.x.low_limit_travel) + limits = asyncio.gather( + sample.x.high_limit_travel.get_value(), sample.x.low_limit_travel.get_value() + ) + high, low = limits.result() + x_range = abs(high - low) limit_margin = x_range * 0.01 - min_x = min_x or sample.x.low_limit_travel + limit_margin - max_x = max_x or sample.x.high_limit_travel - limit_margin + min_x = min_x or low + limit_margin + max_x = max_x or high - limit_margin yield from bps.mv( det.drv.num_images, diff --git a/src/htss_rig_bluesky/plans/detector.py b/src/htss_rig_bluesky/plans/detector.py index f7f10ff..b21e9af 100644 --- a/src/htss_rig_bluesky/plans/detector.py +++ b/src/htss_rig_bluesky/plans/detector.py @@ -2,7 +2,7 @@ from dataclasses import dataclass import bluesky.plan_stubs as bps -from ophyd_async.epics.areadetector.aravis import AravisDetector +from ophyd_async.epics.adaravis import AravisDetector @dataclass diff --git a/tests/system_tests/__init__.py b/tests/system_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/system_tests/conftest.py b/tests/system_tests/conftest.py new file mode 100644 index 0000000..cf2ebd6 --- /dev/null +++ b/tests/system_tests/conftest.py @@ -0,0 +1,52 @@ +import pytest +from blueapi.client.client import BlueapiClient +from blueapi.config import ApplicationConfig, RestConfig, StompConfig +from blueapi.worker.task import Task +from bluesky_stomp.models import BasicAuthentication + +from htss_rig_bluesky.names import BEAMLINE + + +@pytest.fixture +def task_definition() -> dict[str, Task]: + return { + "step_scan_plan": Task( + name="step_scan_plan", + params={"detectors": "det", "motor": "sample_stage.theta"}, + ), + "fly_and_collect_plan": Task( + name="fly_and_collect_plan", + params={"panda": "panda", "diff": "det"}, + ), + "log_scan_plan": Task( + name="log_scan_plan", + params={"detectors": "det", "motor": "sample_stage.x"}, + ), + } + + +@pytest.fixture +def config() -> ApplicationConfig: + if BEAMLINE == "p46": + return ApplicationConfig( + stomp=StompConfig( + host="172.23.168.198", + auth=BasicAuthentication(username="guest", password="guest"), # type: ignore + ), + api=RestConfig( + host="p46-blueapi.diamond.ac.uk", port=443, protocol="https" + ), + ) + else: + return ApplicationConfig( + stomp=StompConfig( + host="localhost", + auth=BasicAuthentication(username="guest", password="guest"), # type: ignore + ) + ) + + +# This client will use authentication if a valid cached token is found +@pytest.fixture +def client(config: ApplicationConfig) -> BlueapiClient: + return BlueapiClient.from_config(config=config) diff --git a/tests/system_tests/test_plans.py b/tests/system_tests/test_plans.py new file mode 100644 index 0000000..af582eb --- /dev/null +++ b/tests/system_tests/test_plans.py @@ -0,0 +1,75 @@ +import pytest +from blueapi.client.client import BlueapiClient +from blueapi.client.event_bus import AnyEvent +from blueapi.core.bluesky_types import DataEvent +from blueapi.worker.event import TaskStatus, WorkerEvent, WorkerState +from blueapi.worker.task import Task + +# Please export BEAMLINE=pXX before running the tests or add it in pyproject.toml + + +def _check_all_events(all_events: list[AnyEvent]): + assert ( + isinstance(all_events[0], WorkerEvent) and all_events[0].task_status is not None + ) + task_id = all_events[0].task_status.task_id + # First event is WorkerEvent + assert all_events[0] == WorkerEvent( + state=WorkerState.RUNNING, + task_status=TaskStatus( + task_id=task_id, + task_complete=False, + task_failed=False, + ), + ) + + assert all( + isinstance(event, DataEvent) for event in all_events[1:-2] + ), "Middle elements must be DataEvents." + + # Last 2 events are WorkerEvent + assert all_events[-2:] == [ + WorkerEvent( + state=WorkerState.IDLE, + task_status=TaskStatus( + task_id=task_id, + task_complete=False, + task_failed=False, + ), + ), + WorkerEvent( + state=WorkerState.IDLE, + task_status=TaskStatus( + task_id=task_id, + task_complete=True, + task_failed=False, + ), + ), + ] + + +@pytest.mark.parametrize("device", ["sample_stage", "panda", "det"]) +def test_device_present(client: BlueapiClient, device: str): + assert client.get_device(device), f"{device} is not available" + + +@pytest.mark.parametrize( + "plan", ["step_scan_plan", "fly_and_collect_plan", "log_scan_plan"] +) +def test_spec_scan_task( + client: BlueapiClient, + task_definition: dict[str, Task], + plan: str, +): + assert client.get_plan(plan), f"In {plan} is available" + + all_events: list[AnyEvent] = [] + + def on_event(event: AnyEvent): + all_events.append(event) + + client.run_task(task_definition[plan], on_event=on_event) + + _check_all_events(all_events) + + assert client.get_state() is WorkerState.IDLE diff --git a/tests/conftest.py b/tests/unit_tests/conftest.py similarity index 100% rename from tests/conftest.py rename to tests/unit_tests/conftest.py diff --git a/tests/test_boilerplate_removed.py b/tests/unit_tests/test_boilerplate_removed.py similarity index 100% rename from tests/test_boilerplate_removed.py rename to tests/unit_tests/test_boilerplate_removed.py diff --git a/tests/test_cli.py b/tests/unit_tests/test_cli.py similarity index 100% rename from tests/test_cli.py rename to tests/unit_tests/test_cli.py diff --git a/tests/test_imports.py b/tests/unit_tests/test_imports.py similarity index 100% rename from tests/test_imports.py rename to tests/unit_tests/test_imports.py