From b7acf9153c60f2bcdfb30fe96b4c80f709c34a6a Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Thu, 19 Sep 2024 18:04:45 -0400 Subject: [PATCH] Overhaul sim detector tests to use ad standard det factory, improve factory with mock put callback --- .../adsimdetector/test_adsim_controller.py | 32 --- tests/epics/adsimdetector/test_sim.py | 186 +++++++++--------- tests/epics/conftest.py | 24 ++- 3 files changed, 113 insertions(+), 129 deletions(-) delete mode 100644 tests/epics/adsimdetector/test_adsim_controller.py diff --git a/tests/epics/adsimdetector/test_adsim_controller.py b/tests/epics/adsimdetector/test_adsim_controller.py deleted file mode 100644 index 64d53ce590..0000000000 --- a/tests/epics/adsimdetector/test_adsim_controller.py +++ /dev/null @@ -1,32 +0,0 @@ -from unittest.mock import patch - -import pytest - -from ophyd_async.core import DeviceCollector -from ophyd_async.core._detector import DetectorTrigger, TriggerInfo -from ophyd_async.epics import adcore, adsimdetector - - -@pytest.fixture -async def ad(RE) -> adsimdetector.SimController: - async with DeviceCollector(mock=True): - drv = adcore.ADBaseIO("DRIVER:") - controller = adsimdetector.SimController(drv) - - return controller - - -async def test_ad_controller(RE, ad: adsimdetector.SimController): - with patch("ophyd_async.core._signal.wait_for_value", return_value=None): - await ad.prepare(TriggerInfo(number=1, trigger=DetectorTrigger.internal)) - await ad.arm() - await ad.wait_for_idle() - - driver = ad.driver - assert await driver.num_images.get_value() == 1 - assert await driver.image_mode.get_value() == adcore.ImageMode.multiple - assert await driver.acquire.get_value() is True - - await ad.disarm() - - assert await driver.acquire.get_value() is False diff --git a/tests/epics/adsimdetector/test_sim.py b/tests/epics/adsimdetector/test_sim.py index fd99fd0497..637c3c6ed7 100644 --- a/tests/epics/adsimdetector/test_sim.py +++ b/tests/epics/adsimdetector/test_sim.py @@ -4,46 +4,40 @@ from collections import defaultdict from pathlib import Path from typing import cast +from unittest.mock import patch import bluesky.plan_stubs as bps import bluesky.preprocessors as bpp import pytest -from bluesky import RunEngine -from bluesky.utils import new_uid +from bluesky.run_engine import RunEngine import ophyd_async.plan_stubs as ops from ophyd_async.core import ( AsyncStatus, DetectorTrigger, - DeviceCollector, - StandardDetector, StaticFilenameProvider, StaticPathProvider, TriggerInfo, assert_emitted, - callback_on_mock_put, set_mock_value, ) from ophyd_async.epics import adcore, adsimdetector -async def make_detector(prefix: str, name: str, tmp_path: Path): - fp = StaticFilenameProvider(f"test-{new_uid()}") - dp = StaticPathProvider(fp, tmp_path) - - async with DeviceCollector(mock=True): - det = adsimdetector.SimDetector(prefix, dp, name=name) - det._config_sigs = [det.drv.acquire_time, det.drv.acquire] +@pytest.fixture +def test_adsimdetector(ad_standard_det_factory): + return ad_standard_det_factory(adsimdetector.SimDetector) - def _set_full_file_name(val, *args, **kwargs): - set_mock_value(det.hdf.full_file_name, str(tmp_path / val)) - callback_on_mock_put(det.hdf.file_name, _set_full_file_name) +@pytest.fixture +def two_test_adsimdetectors(ad_standard_det_factory): + deta = ad_standard_det_factory(adsimdetector.SimDetector) + detb = ad_standard_det_factory(adsimdetector.SimDetector, number=2) - return det + return deta, detb -def count_sim(dets: list[StandardDetector], times: int = 1): +def count_sim(dets: list[adsimdetector.SimDetector], times: int = 1): """Test plan to do the equivalent of bp.count for a sim detector.""" yield from bps.stage_all(*dets) @@ -79,38 +73,8 @@ def count_sim(dets: list[StandardDetector], times: int = 1): yield from bps.unstage_all(*dets) -@pytest.fixture -async def single_detector(RE: RunEngine, tmp_path: Path) -> StandardDetector: - detector = await make_detector(prefix="TEST:", name="test", tmp_path=tmp_path) - - set_mock_value(detector._controller.driver.array_size_x, 10) - set_mock_value(detector._controller.driver.array_size_y, 20) - return detector - - -@pytest.fixture -async def two_detectors(tmp_path: Path): - deta = await make_detector(prefix="PREFIX1:", name="testa", tmp_path=tmp_path) - detb = await make_detector(prefix="PREFIX2:", name="testb", tmp_path=tmp_path) - - # Simulate backend IOCs being in slightly different states - for i, det in enumerate((deta, detb)): - # accessing the hidden objects just for neat typing - controller = det._controller - writer = det._writer - - set_mock_value(controller.driver.acquire_time, 0.8 + i) - set_mock_value(controller.driver.image_mode, adcore.ImageMode.continuous) - set_mock_value(writer.hdf.num_capture, 1000) - set_mock_value(writer.hdf.num_captured, 0) - set_mock_value(writer.hdf.file_path_exists, True) - set_mock_value(controller.driver.array_size_x, 1024 + i) - set_mock_value(controller.driver.array_size_y, 768 + i) - yield deta, detb - - async def test_two_detectors_fly_different_rate( - two_detectors: list[adsimdetector.SimDetector], RE: RunEngine + two_test_adsimdetectors: list[adsimdetector.SimDetector], RE: RunEngine ): trigger_info = TriggerInfo( number=15, @@ -131,37 +95,37 @@ def assert_n_stream_datums( assert seq_nums["start"] == start assert seq_nums["stop"] == stop - @bpp.stage_decorator(two_detectors) + @bpp.stage_decorator(two_test_adsimdetectors) @bpp.run_decorator() def fly_plan(): - for det in two_detectors: + for det in two_test_adsimdetectors: yield from bps.prepare(det, trigger_info, wait=True, group="prepare") - yield from bps.declare_stream(*two_detectors, name="primary") + yield from bps.declare_stream(*two_test_adsimdetectors, name="primary") - for det in two_detectors: + for det in two_test_adsimdetectors: yield from bps.trigger(det, wait=False, group="trigger_cleanup") # det[0] captures 5 frames, but we do not emit a StreamDatum as det[1] has not - set_mock_value(two_detectors[0].hdf.num_captured, 5) + set_mock_value(two_test_adsimdetectors[0].hdf.num_captured, 5) - yield from bps.collect(*two_detectors) + yield from bps.collect(*two_test_adsimdetectors) assert_n_stream_datums(0) # det[0] captures 10 frames, but we do not emit a StreamDatum as det[1] has not - set_mock_value(two_detectors[0].hdf.num_captured, 10) - yield from bps.collect(*two_detectors) + set_mock_value(two_test_adsimdetectors[0].hdf.num_captured, 10) + yield from bps.collect(*two_test_adsimdetectors) assert_n_stream_datums(0) # det[1] has caught up to first 7 frames, emit streamDatum for seq_num {1,7} - set_mock_value(two_detectors[1].hdf.num_captured, 7) - yield from bps.collect(*two_detectors) + set_mock_value(two_test_adsimdetectors[1].hdf.num_captured, 7) + yield from bps.collect(*two_test_adsimdetectors) assert_n_stream_datums(2, 1, 8) - for det in two_detectors: + for det in two_test_adsimdetectors: set_mock_value(det.hdf.num_captured, 15) # emits stream datum for seq_num {8, 15} - yield from bps.collect(*two_detectors) + yield from bps.collect(*two_test_adsimdetectors) assert_n_stream_datums(4, 8, 16) # Trigger has complete as all expected frames written @@ -174,7 +138,7 @@ def fly_plan(): async def test_two_detectors_step( - two_detectors: list[StandardDetector], + two_test_adsimdetectors: list[adsimdetector.SimDetector], RE: RunEngine, ): names = [] @@ -183,20 +147,22 @@ async def test_two_detectors_step( RE.subscribe(lambda _, doc: docs.append(doc)) [ set_mock_value(cast(adcore.ADHDFWriter, det._writer).hdf.file_path_exists, True) - for det in two_detectors + for det in two_test_adsimdetectors ] - controller_a = cast(adsimdetector.SimController, two_detectors[0].controller) - writer_a = cast(adcore.ADHDFWriter, two_detectors[0].writer) - writer_b = cast(adcore.ADHDFWriter, two_detectors[1].writer) - info_a = writer_a._path_provider(device_name=writer_a.hdf.name) - info_b = writer_b._path_provider(device_name=writer_b.hdf.name) + controller_a = cast( + adsimdetector.SimController, two_test_adsimdetectors[0].controller + ) + writer_a = cast(adcore.ADHDFWriter, two_test_adsimdetectors[0].writer) + writer_b = cast(adcore.ADHDFWriter, two_test_adsimdetectors[1].writer) + info_a = writer_a._path_provider(device_name=writer_a._name_provider()) + info_b = writer_b._path_provider(device_name=writer_b._name_provider()) file_name_a = None file_name_b = None def plan(): nonlocal file_name_a, file_name_b - yield from count_sim(two_detectors, times=1) + yield from count_sim(two_test_adsimdetectors, times=1) drv = controller_a.driver assert False is (yield from bps.rd(drv.acquire)) @@ -229,38 +195,51 @@ def plan(): ] _, descriptor, sra, sda, srb, sdb, event, _ = docs - assert descriptor["configuration"]["testa"]["data"]["testa-drv-acquire_time"] == 0.8 - assert descriptor["configuration"]["testb"]["data"]["testb-drv-acquire_time"] == 1.8 - assert descriptor["data_keys"]["testa"]["shape"] == (768, 1024) - assert descriptor["data_keys"]["testb"]["shape"] == (769, 1025) + assert descriptor["configuration"]["test_adsim1"]["data"][ + "test_adsim1-drv-acquire_time" + ] == pytest.approx(0.8) + assert descriptor["configuration"]["test_adsim2"]["data"][ + "test_adsim2-drv-acquire_time" + ] == pytest.approx(1.8) + assert descriptor["data_keys"]["test_adsim1"]["shape"] == (10, 10) + assert descriptor["data_keys"]["test_adsim2"]["shape"] == (11, 11) assert sda["stream_resource"] == sra["uid"] assert sdb["stream_resource"] == srb["uid"] - assert srb["uri"] == "file://localhost" + str(info_b.directory_path / file_name_b) - assert sra["uri"] == "file://localhost" + str(info_a.directory_path / file_name_a) + assert ( + srb["uri"] + == "file://localhost" + str(info_b.directory_path / info_b.filename) + ".h5" + ) + assert ( + sra["uri"] + == "file://localhost" + str(info_a.directory_path / info_a.filename) + ".h5" + ) assert event["data"] == {} async def test_detector_writes_to_file( - RE: RunEngine, single_detector: StandardDetector, tmp_path: Path + RE: RunEngine, test_adsimdetector: adsimdetector.SimDetector, tmp_path: Path ): names = [] docs = [] RE.subscribe(lambda name, _: names.append(name)) RE.subscribe(lambda _, doc: docs.append(doc)) set_mock_value( - cast(adcore.ADHDFWriter, single_detector._writer).hdf.file_path_exists, True + cast(adcore.ADHDFWriter, test_adsimdetector._writer).hdf.file_path_exists, True ) - RE(count_sim([single_detector], times=3)) + RE(count_sim([test_adsimdetector], times=3)) assert await cast( - adcore.ADHDFWriter, single_detector.writer + adcore.ADHDFWriter, test_adsimdetector.writer ).hdf.file_path.get_value() == str(tmp_path) descriptor_index = names.index("descriptor") - assert docs[descriptor_index].get("data_keys").get("test").get("shape") == (20, 10) + assert docs[descriptor_index].get("data_keys").get("test_adsim1").get("shape") == ( + 10, + 10, + ) assert names == [ "start", "descriptor", @@ -275,39 +254,41 @@ async def test_detector_writes_to_file( ] -async def test_read_and_describe_detector(single_detector: StandardDetector): - describe = await single_detector.describe_configuration() - read = await single_detector.read_configuration() +async def test_read_and_describe_detector( + test_adsimdetector: adsimdetector.SimDetector, +): + describe = await test_adsimdetector.describe_configuration() + read = await test_adsimdetector.read_configuration() assert describe == { - "test-drv-acquire_time": { - "source": "mock+ca://TEST:cam1:AcquireTime_RBV", + "test_adsim1-drv-acquire_time": { + "source": "mock+ca://SIM1:cam1:AcquireTime_RBV", "dtype": "number", "dtype_numpy": "