# 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 ( EndData, FrameData, PandaBox, PandaBoxDataConnection, PandaState, ReadyData, StartData, ) from ophyd_devices.devices.panda_box.utils import ( PANDA_AVAIL_PCAP_BLOCKS, PANDA_AVAIL_PCAP_CAPTURE_FIELDS, block_name_mapping, get_pcap_capture_fields, ) @pytest.fixture def _signal_aliases(): return {"FMC_IN.VAL1.Value": "my_signal_1", "FMC_IN.VAL2.Mean": "my_signal_2"} @pytest.fixture def panda_box(_signal_aliases): return PandaBox(name="panda_box", host="localhost", signal_alias=_signal_aliases) def test_panda_box_init_invalid_signal_alias(): """Test that providing invalid signal aliases raises an error.""" with pytest.raises( ValueError, match="Invalid signal name in signal_alias: 'Invalid.Signal.Name'" ): PandaBox( name="panda_box", host="localhost", signal_alias={"FMC_IN.VAL1.Value": "Invalid.Signal.Name"}, ) def test_panda_box_init(panda_box, _signal_aliases): """Test initialization of PandaBox, including default signal aliases.""" assert panda_box.name == "panda_box" assert panda_box.host == "localhost" all_signal_names = [name for name, _ in panda_box.data.signals] for block in PANDA_AVAIL_PCAP_BLOCKS: for field in PANDA_AVAIL_PCAP_CAPTURE_FIELDS: signal_name = f"{block}.{field}" if signal_name in ("FMC_IN.VAL1.Value", "FMC_IN.VAL2.Mean"): # These signals should be renamed assert _signal_aliases[signal_name] in all_signal_names continue assert ( block_name_mapping(signal_name) in all_signal_names ), f"Missing signal: {signal_name}" def test_panda_wait_for_connection(panda_box): """Test that wait_for_connection can be called without error.""" with mock.patch.object(panda_box, "send_raw") as mock_send_raw: mock_send_raw.return_value = "OK" panda_box.wait_for_connection(timeout=1) mock_send_raw.assert_called_with("*IDN?") def test_panda_on_connected(panda_box): """Test that on_connected sets the connected flag.""" with mock.patch.object(panda_box, "data_thread") as mock_data_thread: mock_data_thread.is_alive.return_value = False panda_box.on_connected() mock_data_thread.start.assert_called_once() assert len(panda_box._data_callbacks) == 1 cb_id = list(panda_box._data_callbacks.keys())[0] assert panda_box._data_callbacks[cb_id]["callback"] == panda_box._receive_frame_data assert panda_box._data_callbacks[cb_id]["data_type"] == PandaState.FRAME # Remove callback panda_box.remove_data_callback(cb_id) assert len(panda_box._data_callbacks) == 0, "Data callback was not removed" # Call on_connected again, should add the callback again mock_data_thread.reset_mock() mock_data_thread.is_alive.return_value = True panda_box.on_connected() mock_data_thread.start.assert_not_called() def test_panda_add_status_callback(panda_box): """Test that add_status_callback adds proper status callbacks, and resolves them correctly.""" assert panda_box.panda_state == PandaState.DISARMED, "Initial PandaBox state should be DISARMED" status = StatusBase(obj=panda_box) # I. Resolve immediately, should be successful panda_box.add_status_callback(status=status, success=PandaState.DISARMED, failure=[]) status.wait(timeout=1) assert status.done, "Status should be done" assert status.success, "Status should be successful" assert len(panda_box._status_callbacks) == 0, "Status callback should never be added" # II. Resolve immediately, but with failure state, should be unsuccessful status = StatusBase(obj=panda_box) panda_box.add_status_callback(status=status, success=[], failure=PandaState.DISARMED) with pytest.raises(RuntimeError): status.wait(timeout=1) assert status.done, "Status should be done" assert not status.success, "Status should be unsuccessful" assert len(panda_box._status_callbacks) == 0, "Status callback should never be added" # III. Resolve status in success status = StatusBase(obj=panda_box) panda_box.add_status_callback(status=status, success=PandaState.READY, failure=[]) assert len(panda_box._status_callbacks) == 1, "Status callback should be added" panda_box._run_status_callbacks(PandaState.START) assert not status.done, "Status should not be done" assert not status.success, "Status should not be successful" panda_box._run_status_callbacks(PandaState.READY) status.wait(timeout=1) assert status.done, "Status should be done" assert status.success, "Status should be successful" # IV. Resolve status in failure status = StatusBase(obj=panda_box) panda_box.add_status_callback(status=status, success=[PandaState.END], failure=PandaState.START) panda_box._run_status_callbacks(PandaState.START) with pytest.raises(RuntimeError): status.wait(timeout=1) assert status.done, "Status should be done" assert not status.success, "Status should be unsuccessful" def test_panda_receive_frame_data(panda_box, _signal_aliases): """Test that _receive_frame_data processes data and updates signals.""" # Create a mock FrameData data = np.array( [ (np.float64(0), np.float64(10)), (np.float64(1), np.float64(11)), (np.float64(2), np.float64(12)), ], dtype=[("FMC_IN.VAL1.Value", "