async def test_disconnect_stop_exposure_exception(self): """Test that `disconnect` does not raise if `stop_exposure` raises, but does log an error message, and continues with deactivating the device. """ duration = 5 # seconds spec = AvsFiberSpectrograph() self.patch.return_value.AVS_StopMeasure.return_value = ( AvsReturnCode.ERR_INVALID_PARAMETER.value) t0 = time.monotonic() task = asyncio.create_task(spec.expose(duration)) await asyncio.sleep(0.1) # give the event loop time to start try: with self.assertLogs(spec.log, "ERROR"): spec.disconnect() except AvsReturnError: self.fail( "disconnect() should not raise an exception, even if `stop_exposure` does." ) with pytest.raises(asyncio.CancelledError): await task t1 = time.monotonic() # cancelling the task should make it end much sooner than the duration assert t1 - t0 < 1 self.patch.return_value.AVS_StopMeasure.assert_called_with(self.handle) self.patch.return_value.AVS_Deactivate.assert_called_once_with( self.handle) self.patch.return_value.AVS_Done.assert_called_once_with() assert spec.handle is None
async def test_expose_raises_if_active_exposure(self): """Starting a new exposure while one is currently active should raise. """ duration = 0.2 # seconds spec = AvsFiberSpectrograph() task = asyncio.create_task(spec.expose(duration)) await asyncio.sleep(0.1) # give the event loop time to start with pytest.raises(RuntimeError, match="Cannot start new exposure"): task2 = asyncio.create_task(spec.expose(duration)) await task2 await task # in addition to raising, should have only called these functions once self.patch.return_value.AVS_PrepareMeasure.assert_called_once() self.patch.return_value.AVS_Measure.assert_called_once_with( self.handle, 0, 1) self.patch.return_value.AVS_GetScopeData.assert_called_once()
async def check_duration_fails(duration): spec = AvsFiberSpectrograph() with pytest.raises(RuntimeError, match="Exposure duration not in valid range:"): # timeout=1s because the command should fail immediately. await asyncio.wait_for(spec.expose(duration), 1) self.patch.return_value.AVS_PrepareMeasure.assert_not_called() self.patch.return_value.AVS_Measure.assert_not_called() self.patch.return_value.AVS_PollScan.assert_not_called() self.patch.return_value.AVS_GetScopeData.assert_not_called()
async def test_stop_exposure(self): """Test that `stop_exposure` ends the active `expose`.""" duration = 5 # seconds spec = AvsFiberSpectrograph() t0 = time.monotonic() task = asyncio.create_task(spec.expose(duration)) await asyncio.sleep(0.1) # give the event loop time to start spec.stop_exposure() with pytest.raises(asyncio.CancelledError): await task t1 = time.monotonic() # cancelling the task should make it end much sooner than the duration assert t1 - t0 < 1 self.patch.return_value.AVS_StopMeasure.assert_called_with(self.handle)
async def test_stop_exposure_during_poll_loop(self): """Test that `stop_exposure` ends the active `expose` when called during the `PollData` loop. """ duration = 0.2 # seconds # repeat "no data" forever, so that `stop` will trigger during polling self.patch.return_value.AVS_PollScan.side_effect = itertools.repeat(0) spec = AvsFiberSpectrograph() task = asyncio.create_task(spec.expose(duration)) await asyncio.sleep(duration + 0.1 ) # wait until we are in the poll loop spec.stop_exposure() with pytest.raises(asyncio.CancelledError): await task self.patch.return_value.AVS_StopMeasure.assert_called_with(self.handle) self.patch.return_value.AVS_PollScan.assert_called_with(self.handle) self.patch.return_value.AVS_GetScopeData.assert_not_called()
async def test_stop_exposure_fails(self): """Test `AVS_StopMeasure` returning an error: the existing exposure task should be cancelled, but `stop_exposure` should also raise.""" duration = 5 # seconds self.patch.return_value.AVS_StopMeasure.return_value = ( AvsReturnCode.ERR_TIMEOUT.value) spec = AvsFiberSpectrograph() t0 = time.monotonic() task = asyncio.create_task(spec.expose(duration)) await asyncio.sleep(0.1) # give the event loop time to start with pytest.raises(AvsReturnError, match="StopMeasure"): spec.stop_exposure() with pytest.raises(asyncio.CancelledError): await task t1 = time.monotonic() # cancelling the task should make it end much sooner than the duration assert t1 - t0 < 1 self.patch.return_value.AVS_StopMeasure.assert_called_with(self.handle)
async def test_expose_PollScan_timeout(self): """Test that `expose` raises if it has to wait too long when polling. """ duration = 0.5 # seconds # Have the PollScan just run forever. self.patch.return_value.AVS_PollScan.side_effect = itertools.repeat(0) spec = AvsFiberSpectrograph() # asyncio.TimeoutError would be raised if the `wait_for` times out, # but the message would not include this text. with pytest.raises(asyncio.TimeoutError, match="Timeout polling for exposure to be ready"): # Use `wait_for` to keep `expose` from hanging if there is a bug. await asyncio.wait_for(spec.expose(duration), 2) self.patch.return_value.AVS_PrepareMeasure.assert_called_once() self.patch.return_value.AVS_Measure.assert_called_once_with( self.handle, 0, 1) # PollScan will be called a hundred times or so. self.patch.return_value.AVS_PollScan.assert_called()