From 4397db919a852d70c53d80a532540eaabdffc3ad Mon Sep 17 00:00:00 2001 From: appel_c Date: Sun, 28 Jul 2024 12:34:28 +0200 Subject: [PATCH] test: Fix and add test scenarios for DeviceStatus error handling --- .../base_classes/psi_detector_base.py | 5 +- ophyd_devices/sim/sim_camera.py | 13 ++-- ophyd_devices/sim/sim_monitor.py | 12 ++-- ophyd_devices/sim/sim_positioner.py | 30 +++------ ophyd_devices/sim/sim_waveform.py | 7 +-- tests/test_base_classes.py | 61 +++++++++++++++++-- 6 files changed, 81 insertions(+), 47 deletions(-) diff --git a/ophyd_devices/interfaces/base_classes/psi_detector_base.py b/ophyd_devices/interfaces/base_classes/psi_detector_base.py index 8c3b6e0..e8f0260 100644 --- a/ophyd_devices/interfaces/base_classes/psi_detector_base.py +++ b/ophyd_devices/interfaces/base_classes/psi_detector_base.py @@ -240,9 +240,11 @@ class CustomDetectorMixin: if result: status.set_finished() else: - if self.stopped: + if self.parent.stopped: + # INFO This will execute a callback to the parent device.stop() method status.set_exception(exc=DeviceStopError(f"{self.parent.name} was stopped")) else: + # INFO This will execute a callback to the parent device.stop() method status.set_exception(exc=exception_on_timeout) # pylint: disable=broad-except except Exception as exc: @@ -250,6 +252,7 @@ class CustomDetectorMixin: logger.warning( f"Error in wait_for_signals in {self.parent.name}; Traceback: {content}" ) + # INFO This will execute a callback to the parent device.stop() method status.set_exception(exc=exc) thread = threading.Thread( diff --git a/ophyd_devices/sim/sim_camera.py b/ophyd_devices/sim/sim_camera.py index 9945759..8a9cb9a 100644 --- a/ophyd_devices/sim/sim_camera.py +++ b/ophyd_devices/sim/sim_camera.py @@ -37,19 +37,16 @@ class SimCameraSetup(CustomDetectorMixin): status = DeviceStatus(self.parent) def on_trigger_call(status: DeviceStatus) -> None: - error = None try: for _ in range(self.parent.burst.get()): data = self.parent.image.get() # pylint: disable=protected-access self.parent._run_subs(sub_type=self.parent.SUB_MONITOR, value=data) if self.parent.stopped: - error = DeviceStopError(f"{self.parent.name} was stopped") - break + raise DeviceStopError(f"{self.parent.name} was stopped") if self.parent.write_to_disk.get(): self.parent.h5_writer.receive_data(data) - # pylint: disable=expression-not-assigned - status.set_finished() if not error else status.set_exception(exc=error) + status.set_finished() # pylint: disable=broad-except except Exception as exc: content = traceback.format_exc() @@ -93,15 +90,13 @@ class SimCameraSetup(CustomDetectorMixin): status = DeviceStatus(self.parent) def on_complete_call(status: DeviceStatus) -> None: - error = None try: if self.parent.write_to_disk.get(): self.parent.h5_writer.write_data() self.publish_file_location(done=True, successful=True) if self.parent.stopped: - error = DeviceStopError(f"{self.parent.name} was stopped") - # pylint: disable=expression-not-assigned - status.set_finished() if not error else status.set_exception(exc=error) + raise DeviceStopError(f"{self.parent.name} was stopped") + status.set_finished() # pylint: disable=broad-except except Exception as exc: content = traceback.format_exc() diff --git a/ophyd_devices/sim/sim_monitor.py b/ophyd_devices/sim/sim_monitor.py index ea2146f..3cd30a0 100644 --- a/ophyd_devices/sim/sim_monitor.py +++ b/ophyd_devices/sim/sim_monitor.py @@ -114,14 +114,12 @@ class SimMonitorAsyncPrepare(CustomDetectorMixin): status = DeviceStatus(self.parent) def on_complete_call(status: DeviceStatus) -> None: - error = None try: if self.parent.data_buffer["value"]: self._send_data_to_bec() if self.parent.stopped: - error = DeviceStopError(f"{self.parent.name} was stopped") - # pylint: disable=expression-not-assigned - status.set_finished() if not error else status.set_exception(exc=error) + raise DeviceStopError(f"{self.parent.name} was stopped") + status.set_finished() # pylint: disable=broad-except except Exception as exc: content = traceback.format_exc() @@ -157,7 +155,6 @@ class SimMonitorAsyncPrepare(CustomDetectorMixin): status = DeviceStatus(self.parent) def on_trigger_call(status: DeviceStatus) -> None: - error = None try: self.parent.data_buffer["value"].append(self.parent.readback.get()) self.parent.data_buffer["timestamp"].append(self.parent.readback.timestamp) @@ -166,9 +163,8 @@ class SimMonitorAsyncPrepare(CustomDetectorMixin): if self._counter % self._random_send_interval == 0: self._send_data_to_bec() if self.parent.stopped: - error = DeviceStopError(f"{self.parent.name} was stopped") - # pylint: disable=expression-not-assigned - status.set_finished() if not error else status.set_exception(exc=error) + raise DeviceStopError(f"{self.parent.name} was stopped") + status.set_finished() # pylint: disable=broad-except except Exception as exc: content = traceback.format_exc() diff --git a/ophyd_devices/sim/sim_positioner.py b/ophyd_devices/sim/sim_positioner.py index c3cec98..34691f3 100644 --- a/ophyd_devices/sim/sim_positioner.py +++ b/ophyd_devices/sim/sim_positioner.py @@ -159,8 +159,6 @@ class SimPositioner(Device, PositionerBase): def _move_and_finish(self, start_pos, stop_pos, st): """Move the simulated device and finish the motion.""" - error = None - target = stop_pos + self.tolerance.get() * np.random.uniform(-1, 1) updates = np.ceil(np.abs(target - start_pos) / self.velocity.get() * self.update_frequency) @@ -170,12 +168,9 @@ class SimPositioner(Device, PositionerBase): ttime.sleep(1 / self.update_frequency) self._update_state(ii) if self._stopped: - error = DeviceStopError(f"{self.name} was stopped") - break - else: - self._update_state(target) - # pylint: disable=expression-not-assigned - st.set_finished() if error is None else st.set_exception(error) + raise DeviceStopError(f"{self.parent.name} was stopped") + self._update_state(target) + st.set_finished() # pylint: disable=broad-except except Exception as exc: content = traceback.format_exc() @@ -231,7 +226,6 @@ class SimLinearTrajectoryPositioner(SimPositioner): super().__init__(*args, **kwargs) def _move_and_finish(self, start_pos, end_pos, st): - error = None acc_time = ( self.acceleration.get() ) # acceleration in Ophyd refers to acceleration time in seconds @@ -244,17 +238,13 @@ class SimLinearTrajectoryPositioner(SimPositioner): ttime.sleep(1 / self.update_frequency) self._update_state(traj.position()) if self._stopped: - error = DeviceStopError(f"{self.name} was stopped") - break - if self._stopped: - # simulate deceleration - traj = stop_trajectory(traj) - while not traj.ended: - ttime.sleep(1 / self.update_frequency) - self._update_state(traj.position()) - self._update_state(traj.position()) - # pylint: disable=expression-not-assigned - st.set_finished() if error is None else st.set_exception(error) + # simulate deceleration + traj = stop_trajectory(traj) + while not traj.ended: + ttime.sleep(1 / self.update_frequency) + self._update_state(traj.position()) + raise DeviceStopError(f"{self.parent.name} was stopped") + st.set_finished() # pylint: disable=broad-except except Exception as exc: content = traceback.format_exc() diff --git a/ophyd_devices/sim/sim_waveform.py b/ophyd_devices/sim/sim_waveform.py index 4b355fc..2b06eb4 100644 --- a/ophyd_devices/sim/sim_waveform.py +++ b/ophyd_devices/sim/sim_waveform.py @@ -93,15 +93,12 @@ class SimWaveform(Device): status = DeviceStatus(self) def acquire(status: DeviceStatus): - error = None try: for _ in range(self.burst.get()): self._run_subs(sub_type=self.SUB_MONITOR, value=self.waveform.get()) if self.stopped: - error = DeviceStopError(f"{self.name} was stopped") - break - # pylint: disable=expression-not-assigned - status.set_finished() if not error else status.set_exception(exc=error) + raise DeviceStopError(f"{self.name} was stopped") + status.set_finished() # pylint: disable=broad-except except Exception as exc: content = traceback.format_exc() diff --git a/tests/test_base_classes.py b/tests/test_base_classes.py index ef648db..424339e 100644 --- a/tests/test_base_classes.py +++ b/tests/test_base_classes.py @@ -11,6 +11,7 @@ from ophyd_devices.interfaces.base_classes.psi_detector_base import ( PSIDetectorBase, ) from ophyd_devices.utils.bec_scaninfo_mixin import BecScaninfoMixin +from ophyd_devices.utils.errors import DeviceStopError, DeviceTimeoutError @pytest.fixture @@ -113,13 +114,12 @@ def test_check_scan_id(detector_base): def test_wait_for_signal(detector_base): expected_value = "test" - exception = TimeoutError("Timeout") status = detector_base.custom_prepare.wait_with_status( [(detector_base.filepath.get, expected_value)], check_stopped=True, timeout=5, interval=0.01, - exception_on_timeout=exception, + exception_on_timeout=None, ) time.sleep(0.1) assert status.done is False @@ -128,14 +128,14 @@ def test_wait_for_signal(detector_base): # some delay to allow the stop to take effect time.sleep(0.15) assert status.done is True - assert id(status.exception()) == id(exception) + assert status.exception().args == DeviceStopError(f"{detector_base.name} was stopped").args detector_base.stopped = False status = detector_base.custom_prepare.wait_with_status( [(detector_base.filepath.get, expected_value)], check_stopped=True, timeout=5, interval=0.01, - exception_on_timeout=exception, + exception_on_timeout=None, ) # Check that thread resolves when expected value is set detector_base.filepath.set(expected_value) @@ -144,3 +144,56 @@ def test_wait_for_signal(detector_base): assert status.done is True assert status.success is True assert status.exception() is None + + detector_base.stopped = False + # Check that wait for status runs into timeout with expectd exception + detector_base.filepath.set("wrong_value") + exception = TimeoutError("Timeout") + status = detector_base.custom_prepare.wait_with_status( + [(detector_base.filepath.get, expected_value)], + check_stopped=True, + timeout=0.01, + interval=0.01, + exception_on_timeout=exception, + ) + time.sleep(0.2) + assert status.done is True + assert id(status.exception()) == id(exception) + assert status.success is False + + +def test_wait_for_signal_returns_exception(detector_base): + expected_value = "test" + # Check that wait for status runs into timeout with expectd exception + detector_base.filepath.set("wrong_value") + exception = TimeoutError("Timeout") + status = detector_base.custom_prepare.wait_with_status( + [(detector_base.filepath.get, expected_value)], + check_stopped=True, + timeout=0.01, + interval=0.01, + exception_on_timeout=exception, + ) + time.sleep(0.2) + assert status.done is True + assert id(status.exception()) == id(exception) + assert status.success is False + + detector_base.stopped = False + # Check that standard exception is thrown + status = detector_base.custom_prepare.wait_with_status( + [(detector_base.filepath.get, expected_value)], + check_stopped=True, + timeout=0.01, + interval=0.01, + exception_on_timeout=None, + ) + time.sleep(0.2) + assert status.done is True + assert ( + status.exception().args + == DeviceTimeoutError( + f"Timeout error for {detector_base.name} while waiting for signals {[(detector_base.filepath.get, expected_value)]}" + ).args + ) + assert status.success is False