async def test_stop_exposure_no_expose_running(self): """Test that stop_exposure does nothing if there is no active `expose` command. """ spec = AvsFiberSpectrograph() spec.stop_exposure() self.patch.return_value.AVS_StopMeasure.assert_not_called()
def test_disconnect_bad_handle(self): """Do not attempt to disconnect if the device handle is bad.""" spec = AvsFiberSpectrograph() spec.handle = AvsReturnCode.invalidHandle.value spec.disconnect() self.patch.return_value.AVS_Deactivate.assert_not_called() self.patch.return_value.AVS_Done.assert_called_once_with()
def test_get_status_getAnalogIn_fails(self): spec = AvsFiberSpectrograph() self.patch.return_value.AVS_GetAnalogIn.side_effect = None self.patch.return_value.AVS_GetAnalogIn.return_value = ( AvsReturnCode.ERR_TIMEOUT.value) with pytest.raises(AvsReturnError, match="GetAnalogIn.*ERR_TIMEOUT"): spec.get_status()
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
def test_disconnect(self): """Test a successful USB disconnect command.""" spec = AvsFiberSpectrograph() spec.disconnect() 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
def test_get_status_getParameter_fails(self): spec = AvsFiberSpectrograph() self.patch.return_value.AVS_GetParameter.side_effect = None self.patch.return_value.AVS_GetParameter.return_value = ( AvsReturnCode.ERR_INVALID_DEVICE_ID.value) with pytest.raises(AvsReturnError, match="GetParameter.*ERR_INVALID_DEVICE_ID"): spec.get_status()
def test_get_status_getVersionInfo_fails(self): spec = AvsFiberSpectrograph() self.patch.return_value.AVS_GetVersionInfo.side_effect = None self.patch.return_value.AVS_GetVersionInfo.return_value = ( AvsReturnCode.ERR_DEVICE_NOT_FOUND.value) with pytest.raises(AvsReturnError, match="GetVersionInfo.*ERR_DEVICE_NOT_FOUND"): spec.get_status()
def test_disconnect_fails_logged(self): """Test that a "failed" Deactivate emits an error.""" self.patch.return_value.AVS_Deactivate.return_value = False spec = AvsFiberSpectrograph() with self.assertLogs(spec.log, "ERROR"): spec.disconnect() self.patch.return_value.AVS_Deactivate.assert_called_once_with( self.handle) self.patch.return_value.AVS_Done.assert_called_once_with()
def test_disconnect_no_handle(self): """Test that we do not attempt to disconnect if there is no device handle. """ spec = AvsFiberSpectrograph() spec.handle = None spec.disconnect() self.patch.return_value.AVS_Deactivate.assert_not_called() self.patch.return_value.AVS_Done.assert_called_once_with()
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()
def test_disconnect_other_exception(self): """Test that disconnect continues if there some other exception raised during disconnect. """ self.patch.return_value.AVS_Deactivate.side_effect = RuntimeError spec = AvsFiberSpectrograph() with self.assertLogs(spec.log, "ERROR"): spec.disconnect() self.patch.return_value.AVS_Deactivate.assert_called_once_with( self.handle) self.patch.return_value.AVS_Done.assert_called_once_with()
def test_get_status(self): spec = AvsFiberSpectrograph() status = spec.get_status() assert status.fpga_version == self.fpga_version assert status.firmware_version == self.firmware_version assert status.library_version == self.library_version assert status.n_pixels == self.n_pixels assert status.temperature_setpoint == self.temperature_setpoint np.testing.assert_allclose(status.temperature, self.temperature) assert status.config is None # Check that full=True returns a AvsDeviceConfig instead of None # (we're not worried about the contents of it here) status = spec.get_status(full=True) assert status.config is not None
def test_disconnect_on_delete(self): """Test that the connection is closed if the object is deleted.""" spec = AvsFiberSpectrograph() del spec self.patch.return_value.AVS_Deactivate.assert_called_once_with( self.handle) self.patch.return_value.AVS_Done.assert_called_once_with()
def test_connect_device_serial_number_already_connected(self): """Test that connect raises if the device requested by serial number claims to already be connected in its AvsIdentity field. """ n_devices = 2 serial_number = "54321" id1 = AvsIdentity( bytes(str(serial_number), "ascii"), b"Fake Spectrograph 2", AvsDeviceStatus.USB_IN_USE_BY_OTHER.value, ) def mock_getList(a_listSize, a_pRequiredSize, a_pList): """Pretend that the desired device is already connected.""" a_pList[:] = [self.id0, id1] return n_devices self.patch.return_value.AVS_GetList.side_effect = mock_getList self.patch.return_value.AVS_UpdateUSBDevices.return_value = n_devices with pytest.raises(RuntimeError, match="Requested AVS device is already in use"): AvsFiberSpectrograph(serial_number=serial_number) self.patch.return_value.AVS_UpdateUSBDevices.assert_called_once() self.patch.return_value.AVS_GetList.assert_called_once() self.patch.return_value.AVS_Activate.assert_not_called()
def test_connect_no_serial_number_two_devices_fails(self): serial_number = "54321" n_devices = 2 id1 = AvsIdentity( bytes(str(serial_number), "ascii"), b"Fake Spectrograph 2", AvsDeviceStatus.USB_AVAILABLE.value, ) def mock_getList(a_listSize, a_pRequiredSize, a_pList): """Pretend that two devices are connected.""" a_pList[0] = self.id0 a_pList[1] = id1 return n_devices self.patch.return_value.AVS_GetList.side_effect = mock_getList self.patch.return_value.AVS_UpdateUSBDevices.return_value = n_devices msg = ( "Multiple devices found, but no serial number specified. Attached devices: " ) with pytest.raises(RuntimeError, match=msg): AvsFiberSpectrograph() self.patch.return_value.AVS_UpdateUSBDevices.assert_called_once() self.patch.return_value.AVS_GetList.assert_called_once() self.patch.return_value.AVS_Activate.assert_not_called()
def test_create_with_logger(self): """Test that a passed-in logger is used for log messages.""" log = logging.Logger("testingLogger") with self.assertLogs(log, logging.DEBUG): spec = AvsFiberSpectrograph(log=log) # simple check that the instance was created successfully assert spec.device == self.id0
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)
def test_connect(self): """Test connecting to the first device.""" spec = AvsFiberSpectrograph() self.patch.return_value.AVS_UpdateUSBDevices.assert_called_once() self.patch.return_value.AVS_GetList.assert_called_once() self.patch.return_value.AVS_Activate.assert_called_once_with(self.id0) self.patch.return_value.AVS_GetNumPixels.assert_called_once() assert spec.device == self.id0
def test_create_with_stdout_log(self): """Test that the ``log_to_stdout`` init option works.""" capture = io.StringIO() with contextlib.redirect_stdout(capture): spec = AvsFiberSpectrograph(log_to_stdout=True) assert "Found 1 attached USB Avantes device" in capture.getvalue() assert "Activated connection" in capture.getvalue() # simple check that the instance was created successfully assert spec.device == self.id0
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()
def test_connect_no_devices(self): """Test that connect raises if no devices were found.""" self.patch.return_value.AVS_UpdateUSBDevices.return_value = 0 with pytest.raises(RuntimeError, match="No attached USB Avantes devices found"): AvsFiberSpectrograph() self.patch.return_value.AVS_UpdateUSBDevices.assert_called_once() self.patch.return_value.AVS_GetList.assert_not_called() self.patch.return_value.AVS_Activate.assert_not_called()
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()
def test_connect_Activate_fails(self): """Test that connect raises if the Activate command fails.""" self.patch.return_value.AVS_Activate.return_value = ( AvsReturnCode.ERR_DLL_INITIALISATION.value) with pytest.raises(AvsReturnError, match="Activate"): AvsFiberSpectrograph() self.patch.return_value.AVS_UpdateUSBDevices.assert_called_once() self.patch.return_value.AVS_GetList.assert_called_once() self.patch.return_value.AVS_Activate.assert_called_once()
def test_connect_invalid_size(self): """Test that connect raises if GetList returns "Invalid Size".""" self.patch.return_value.AVS_GetList.side_effect = None self.patch.return_value.AVS_GetList.return_value = ( AvsReturnCode.ERR_INVALID_SIZE.value) with pytest.raises(AvsReturnError, match="Fatal Error"): AvsFiberSpectrograph() self.patch.return_value.AVS_UpdateUSBDevices.assert_called_once() self.patch.return_value.AVS_GetList.assert_called_once() self.patch.return_value.AVS_Activate.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()
async def test_expose_prepare_fails(self): duration = 0.5 # seconds self.patch.return_value.AVS_PrepareMeasure.side_effect = None self.patch.return_value.AVS_PrepareMeasure.return_value = ( AvsReturnCode.ERR_INVALID_PARAMETER.value) spec = AvsFiberSpectrograph() with pytest.raises(AvsReturnError, match="PrepareMeasure"): await spec.expose(duration) self.patch.return_value.AVS_PrepareMeasure.assert_called_once() self.patch.return_value.AVS_Measure.assert_not_called()
def test_connect_other_error(self): """Test that connect raises with a message containing the interpreted code if GetList returns an error code. """ self.patch.return_value.AVS_GetList.side_effect = None self.patch.return_value.AVS_GetList.return_value = ( AvsReturnCode.ERR_DLL_INITIALISATION.value) with pytest.raises(AvsReturnError, match="ERR_DLL_INITIALISATION"): AvsFiberSpectrograph() self.patch.return_value.AVS_UpdateUSBDevices.assert_called_once() self.patch.return_value.AVS_GetList.assert_called_once() self.patch.return_value.AVS_Activate.assert_not_called()
def test_connect_single_device_already_connected(self): """Test that connect raises if the single device claims to already be connected in its AvsIdentity field. """ self.id0.Status = AvsDeviceStatus.USB_IN_USE_BY_APPLICATION.value with pytest.raises(RuntimeError, match="Requested AVS device is already in use"): AvsFiberSpectrograph() self.patch.return_value.AVS_UpdateUSBDevices.assert_called_once() self.patch.return_value.AVS_GetList.assert_called_once() self.patch.return_value.AVS_Activate.assert_not_called()
def test_connect_GetNumPixels_fails(self): """Test that connect .""" self.patch.return_value.AVS_GetNumPixels.side_effect = None self.patch.return_value.AVS_GetNumPixels.return_value = ( AvsReturnCode.ERR_DEVICE_NOT_FOUND.value) with pytest.raises(AvsReturnError, match="GetNumPixels"): AvsFiberSpectrograph() self.patch.return_value.AVS_UpdateUSBDevices.assert_called_once() self.patch.return_value.AVS_GetList.assert_called_once() self.patch.return_value.AVS_Activate.assert_called_once() self.patch.return_value.AVS_GetNumPixels.assert_called_once()