From e8e0e81f62a898fd734d470ca6dcd2d9a8f641b4 Mon Sep 17 00:00:00 2001 From: appel_c Date: Thu, 5 Mar 2026 13:37:30 +0100 Subject: [PATCH] test(panda-box): Add tests for data readout loop and context manager --- tests/test_panda.py | 129 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/tests/test_panda.py b/tests/test_panda.py index d864beb..e69b946 100644 --- a/tests/test_panda.py +++ b/tests/test_panda.py @@ -1,12 +1,22 @@ # skip-file +import socket from unittest import mock import numpy as np import pytest from ophyd import Staged +from pandablocks.connections import NeedMoreDataError from ophyd_devices import StatusBase -from ophyd_devices.devices.panda_box.panda_box import FrameData, PandaBox, PandaState +from ophyd_devices.devices.panda_box.panda_box import ( + EndData, + FrameData, + PandaBox, + PandaBoxDataConnection, + PandaState, + ReadyData, + StartData, +) from ophyd_devices.devices.panda_box.utils import ( PANDA_AVAIL_PCAP_BLOCKS, PANDA_AVAIL_PCAP_CAPTURE_FIELDS, @@ -336,3 +346,120 @@ def test_panda_on_pre_scan(panda_box): status.wait(timeout=1) assert status.done, "Status should be done" assert status.success, "Status should be successful" + + +def test_data_loop(): + """Test that the data loop correctly handles receiving data and exiting on EndData.""" + # Mock the exact class lookup used inside PandaBox._run_data_readout + with mock.patch( + "ophyd_devices.devices.panda_box.panda_box.PandaBoxDataConnection" + ) as mock_data_connection_cls: + panda_box = PandaBox( + name="panda_box", host="localhost", signal_alias={"FMC_IN.VAL1.Value": "my_signal_1"} + ) + + mock_data_connection = mock.MagicMock() + mock_socket_connection = mock.MagicMock() + mock_data_connection_cls.return_value.__enter__.return_value = ( + mock_socket_connection, + mock_data_connection, + ) + + recv_items = [b"chunk1", b"chunk2", socket.timeout(), b"chunk3", b"chunk4", b"chunk5"] + + def recv_side_effect(_buffer_size): + item = recv_items.pop(0) + if isinstance(item, Exception): + raise item + return item + + def receive_bytes_side_effect(chunk): + if chunk == b"chunk1": + return [ + StartData( + fields=["FMC_IN.VAL1.Value", "COUNTER2.OUT.Value"], + missed=0, + process="Scaled", + format="Framed", + sample_bytes=16, + arm_time=None, + start_time=None, + hw_time_offset_ns=None, + ) + ] + if chunk == b"chunk2": + return [ReadyData()] + if chunk == b"chunk3": + return [ + FrameData( + np.array( + [(3, 4)], + dtype=[("FMC_IN.VAL1.Value", "