class MercuryTests(unittest.TestCase): """ Tests for the Mercury IOC. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( "mercuryitc", DEVICE_PREFIX) self._lewis.backdoor_set_on_device("connected", True) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=20) card_pv_prefix = get_card_pv_prefix(TEMP_CARDS[0]) self.ca.assert_setting_setpoint_sets_readback( "OFF", readback_pv="{}:SPC".format(card_pv_prefix), expected_alarm=self.ca.Alarms.MAJOR) @parameterized.expand( parameterized_list( itertools.product(PID_PARAMS, PID_TEST_VALUES, TEMP_CARDS + PRESSURE_CARDS))) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_pid_params_set_via_backdoor_THEN_readback_updates( self, _, param, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [card, param.lower(), test_value]) self.ca.assert_that_pv_is("{}:{}".format(card_pv_prefix, param), test_value) @parameterized.expand( parameterized_list( itertools.product(PID_PARAMS, PID_TEST_VALUES, TEMP_CARDS + PRESSURE_CARDS))) def test_WHEN_pid_params_set_THEN_readback_updates(self, _, param, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( test_value, readback_pv="{}:{}".format(card_pv_prefix, param), set_point_pv="{}:{}:SP".format(card_pv_prefix, param)) @parameterized.expand( parameterized_list( itertools.product(AUTOPID_MODES, TEMP_CARDS + PRESSURE_CARDS))) def test_WHEN_autopid_set_THEN_readback_updates(self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( test_value, readback_pv="{}:PID:AUTO".format(card_pv_prefix), set_point_pv="{}:PID:AUTO:SP".format(card_pv_prefix)) @parameterized.expand( parameterized_list( itertools.product(TEMPERATURE_TEST_VALUES, TEMP_CARDS))) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_actual_temp_is_set_via_backdoor_THEN_pv_updates( self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [card, "temperature", test_value]) self.ca.assert_that_pv_is("{}:TEMP".format(card_pv_prefix), test_value) @parameterized.expand( parameterized_list( itertools.product(TEMPERATURE_TEST_VALUES, PRESSURE_CARDS))) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_actual_pressure_is_set_via_backdoor_THEN_pv_updates( self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [card, "pressure", test_value]) self.ca.assert_that_pv_is("{}:PRESSURE".format(card_pv_prefix), test_value) @parameterized.expand( parameterized_list( itertools.product(RESISTANCE_TEST_VALUES, TEMP_CARDS))) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_resistance_is_set_via_backdoor_THEN_pv_updates( self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [card, "resistance", test_value]) self.ca.assert_that_pv_is("{}:RESISTANCE".format(card_pv_prefix), test_value) @parameterized.expand( parameterized_list( itertools.product(RESISTANCE_TEST_VALUES, PRESSURE_CARDS))) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_voltage_is_set_via_backdoor_THEN_pv_updates( self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [card, "voltage", test_value]) self.ca.assert_that_pv_is("{}:VOLT".format(card_pv_prefix), test_value) @parameterized.expand( parameterized_list( itertools.product(TEMPERATURE_TEST_VALUES, TEMP_CARDS))) def test_WHEN_sp_temp_is_set_THEN_pv_updates(self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( test_value, set_point_pv="{}:TEMP:SP".format(card_pv_prefix), readback_pv="{}:TEMP:SP:RBV".format(card_pv_prefix)) @parameterized.expand( parameterized_list( itertools.product(TEMPERATURE_TEST_VALUES, PRESSURE_CARDS))) def test_WHEN_sp_pressure_is_set_THEN_pv_updates(self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( test_value, set_point_pv="{}:PRESSURE:SP".format(card_pv_prefix), readback_pv="{}:PRESSURE:SP:RBV".format(card_pv_prefix)) @parameterized.expand( parameterized_list( itertools.product(HEATER_MODES, TEMP_CARDS + PRESSURE_CARDS))) def test_WHEN_heater_mode_is_set_THEN_pv_updates(self, _, mode, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( mode, set_point_pv="{}:HEATER:MODE:SP".format(card_pv_prefix), readback_pv="{}:HEATER:MODE".format(card_pv_prefix)) @parameterized.expand( parameterized_list( itertools.product(GAS_FLOW_MODES, TEMP_CARDS + PRESSURE_CARDS))) def test_WHEN_gas_flow_mode_is_set_THEN_pv_updates(self, _, mode, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( mode, set_point_pv="{}:FLOW:STAT:SP".format(card_pv_prefix), readback_pv="{}:FLOW:STAT".format(card_pv_prefix)) @parameterized.expand( parameterized_list( itertools.product(GAS_FLOW_TEST_VALUES, TEMP_CARDS + PRESSURE_CARDS))) def test_WHEN_gas_flow_is_set_THEN_pv_updates(self, _, mode, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( mode, set_point_pv="{}:FLOW:SP".format(card_pv_prefix), readback_pv="{}:FLOW".format(card_pv_prefix)) @parameterized.expand( parameterized_list( itertools.product(HEATER_PERCENT_TEST_VALUES, TEMP_CARDS + PRESSURE_CARDS))) def test_WHEN_heater_percent_is_set_THEN_pv_updates(self, _, mode, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( mode, set_point_pv="{}:HEATER:SP".format(card_pv_prefix), readback_pv="{}:HEATER".format(card_pv_prefix)) @parameterized.expand( parameterized_list( itertools.product(HEATER_PERCENT_TEST_VALUES, TEMP_CARDS + PRESSURE_CARDS))) def test_WHEN_heater_voltage_limit_is_set_THEN_pv_updates( self, _, mode, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( mode, set_point_pv="{}:HEATER:VOLT_LIMIT:SP".format(card_pv_prefix), readback_pv="{}:HEATER:VOLT_LIMIT".format(card_pv_prefix)) @parameterized.expand( parameterized_list( itertools.product(HEATER_PERCENT_TEST_VALUES, TEMP_CARDS + PRESSURE_CARDS))) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_power_is_set_via_backdoor_THEN_pv_updates( self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) heater_chan_name = self.ca.get_pv_value( "{}:HTRCHAN".format(card_pv_prefix)) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [heater_chan_name, "power", test_value]) self.ca.assert_that_pv_is("{}:HEATER:POWER".format(card_pv_prefix), test_value) @parameterized.expand( parameterized_list( itertools.product(HEATER_PERCENT_TEST_VALUES, TEMP_CARDS + PRESSURE_CARDS))) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_curr_is_set_via_backdoor_THEN_pv_updates( self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) heater_chan_name = self.ca.get_pv_value( "{}:HTRCHAN".format(card_pv_prefix)) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [heater_chan_name, "current", test_value]) self.ca.assert_that_pv_is("{}:HEATER:CURR".format(card_pv_prefix), test_value) @parameterized.expand( parameterized_list( itertools.product(HEATER_PERCENT_TEST_VALUES, TEMP_CARDS + PRESSURE_CARDS))) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_voltage_is_set_via_backdoor_THEN_pv_updates( self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) heater_chan_name = self.ca.get_pv_value( "{}:HTRCHAN".format(card_pv_prefix)) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [heater_chan_name, "voltage", test_value]) self.ca.assert_that_pv_is("{}:HEATER:VOLT".format(card_pv_prefix), test_value) @parameterized.expand( parameterized_list( itertools.product(MOCK_NICKNAMES, TEMP_CARDS + PRESSURE_CARDS + LEVEL_CARDS))) def test_WHEN_name_is_set_THEN_pv_updates(self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( test_value, readback_pv="{}:NAME".format(card_pv_prefix), set_point_pv="{}:NAME:SP".format(card_pv_prefix)) @parameterized.expand( parameterized_list( itertools.product(GAS_LEVEL_TEST_VALUES, LEVEL_CARDS))) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_helium_level_is_set_via_backdoor_THEN_pv_updates( self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [card, "helium_level", test_value]) self.ca.assert_that_pv_is_number("{}:HELIUM".format(card_pv_prefix), test_value, tolerance=0.01) @parameterized.expand( parameterized_list( itertools.product(GAS_LEVEL_TEST_VALUES, LEVEL_CARDS))) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_nitrogen_level_is_set_via_backdoor_THEN_pv_updates( self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [card, "nitrogen_level", test_value]) self.ca.assert_that_pv_is_number("{}:NITROGEN".format(card_pv_prefix), test_value, tolerance=0.01) @parameterized.expand( parameterized_list( itertools.product(TEMP_CARDS + PRESSURE_CARDS, HEATER_CARDS))) def test_WHEN_heater_association_is_set_THEN_pv_updates( self, _, parent_card, associated_card): card_pv_prefix = get_card_pv_prefix(parent_card) with ManagerMode(ChannelAccess()): self.ca.assert_setting_setpoint_sets_readback( associated_card, "{}:HTRCHAN".format(card_pv_prefix)) @parameterized.expand( parameterized_list( itertools.product(TEMP_CARDS + PRESSURE_CARDS, AUX_CARDS))) def test_WHEN_aux_association_is_set_THEN_pv_updates( self, _, parent_card, associated_card): card_pv_prefix = get_card_pv_prefix(parent_card) with ManagerMode(ChannelAccess()): self.ca.assert_setting_setpoint_sets_readback( associated_card, "{}:AUXCHAN".format(card_pv_prefix)) @parameterized.expand( parameterized_list(itertools.product(HELIUM_READ_RATES, LEVEL_CARDS))) def test_WHEN_he_read_rate_is_set_THEN_pv_updates(self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) self.ca.assert_setting_setpoint_sets_readback( test_value, "{}:HELIUM:READ_RATE".format(card_pv_prefix)) @parameterized.expand( parameterized_list([ ("CATALOG:PARSE.VALA", TEMP_CARDS), ("CATALOG:PARSE.VALB", PRESSURE_CARDS), ("CATALOG:PARSE.VALC", LEVEL_CARDS), ("CATALOG:PARSE.VALD", HEATER_CARDS), ("CATALOG:PARSE.VALE", AUX_CARDS), ])) @skip_if_recsim("Complex logic not tested in recsim") def test_WHEN_getting_catalog_it_contains_all_cards(self, _, pv, cards): for card in cards: self.ca.assert_that_pv_value_causes_func_to_return_true( pv, lambda val: card in val) @parameterized.expand( parameterized_list( itertools.product(MOCK_CALIB_FILES, TEMP_CARDS + PRESSURE_CARDS))) def test_WHEN_setting_calibration_file_THEN_pv_updates( self, _, test_value, card): card_pv_prefix = get_card_pv_prefix(card) with ManagerMode(ChannelAccess()): self.ca.assert_setting_setpoint_sets_readback( test_value, "{}:CALFILE".format(card_pv_prefix)) @parameterized.expand(parameterized_list(["O", "R"])) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_resistance_suffix_is_changed_THEN_resistance_reads_correctly( self, _, resistance_suffix): self._lewis.backdoor_set_on_device("resistance_suffix", resistance_suffix) resistance_value = 3 self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [PRIMARY_TEMPERATURE_CHANNEL, "resistance", resistance_value]) self.ca.assert_that_pv_is( "{}:RESISTANCE".format( get_card_pv_prefix(PRIMARY_TEMPERATURE_CHANNEL)), resistance_value) self.ca.assert_that_pv_alarm_is( "{}:RESISTANCE".format( get_card_pv_prefix(PRIMARY_TEMPERATURE_CHANNEL)), self.ca.Alarms.NONE) def test_WHEN_auto_flow_set_THEN_pv_updates_and_states_are_set(self): card_pv_prefix = get_card_pv_prefix(TEMP_CARDS[0]) pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0]) self.ca.set_pv_value("{}:PID:AUTO:SP".format(card_pv_prefix), "OFF") self.ca.set_pv_value("{}:FLOW:STAT:SP".format(card_pv_prefix), "Auto") self.ca.set_pv_value("{}:HEATER:MODE:SP".format(card_pv_prefix), "Manual") self.ca.set_pv_value("{}:FLOW:STAT:SP".format(pressure_card_pv_prefix), "Manual") self.ca.assert_setting_setpoint_sets_readback( "ON", set_point_pv="{}:SPC:SP".format(card_pv_prefix), readback_pv="{}:SPC".format(card_pv_prefix)) self.ca.assert_that_pv_is("{}:PID:AUTO".format(card_pv_prefix), "ON") self.ca.assert_that_pv_is("{}:FLOW:STAT".format(card_pv_prefix), "Manual") self.ca.assert_that_pv_is("{}:HEATER:MODE".format(card_pv_prefix), "Auto") self.ca.assert_that_pv_is( "{}:FLOW:STAT".format(pressure_card_pv_prefix), "Auto") self.ca.assert_that_pv_is("{}:SPC:SP".format(pressure_card_pv_prefix), "ON") def test_WHEN_auto_flow_set_off_THEN_pv_updates_and_states_are_not_set( self): card_pv_prefix = get_card_pv_prefix(TEMP_CARDS[0]) pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0]) self.ca.set_pv_value("{}:PID:AUTO:SP".format(card_pv_prefix), "OFF") self.ca.set_pv_value("{}:FLOW:STAT:SP".format(card_pv_prefix), "Auto") self.ca.set_pv_value("{}:HEATER:MODE:SP".format(card_pv_prefix), "Manual") self.ca.set_pv_value("{}:FLOW:STAT:SP".format(pressure_card_pv_prefix), "Manual") self.ca.assert_setting_setpoint_sets_readback( "OFF", set_point_pv="{}:SPC:SP".format(card_pv_prefix), readback_pv="{}:SPC".format(card_pv_prefix), expected_alarm=self.ca.Alarms.MAJOR) self.ca.assert_that_pv_is("{}:PID:AUTO".format(card_pv_prefix), "OFF") self.ca.assert_that_pv_is("{}:FLOW:STAT".format(card_pv_prefix), "Auto") self.ca.assert_that_pv_is("{}:HEATER:MODE".format(card_pv_prefix), "Manual") self.ca.assert_that_pv_is( "{}:FLOW:STAT".format(pressure_card_pv_prefix), "Manual") self.ca.assert_that_pv_is("{}:SPC:SP".format(pressure_card_pv_prefix), "OFF") def set_temp_reading_and_sp(self, reading, set_point, spc_state="On"): """ Set the temperature in lewis and the set point on the first card :param reading: The reading lewis will return :param set_point: The set point to set on the device :param spc_state: State to set SPC to (defaults to On) """ card_pv_prefix = get_card_pv_prefix(TEMP_CARDS[0]) self.ca.set_pv_value("{}:SPC:SP".format(card_pv_prefix), spc_state) self.ca.set_pv_value("{}:TEMP:SP".format(card_pv_prefix), set_point) self._lewis.backdoor_run_function_on_device( "backdoor_set_channel_property", [TEMP_CARDS[0], "temperature", reading]) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_auto_flow_set_on_and_temp_lower_than_2_deadbands_THEN_pressure_set_to_minimum_pressure( self): set_point = 10 reading = 10 - SPC_TEMP_DEADBAND * 2.1 pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0]) self.set_temp_reading_and_sp(reading, set_point) self.ca.assert_that_pv_is( "{}:PRESSURE:SP:RBV".format(pressure_card_pv_prefix), SPC_MIN_PRESSURE) @parameterized.expand([(10, ), (1, ), (300, ), (12, ), (20, )]) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_auto_flow_set_on_and_temp_low_but_within_1_to_2_deadbands_THEN_pressure_set_to_pressure_for_setpoint_temp_and_does_not_ramp( self, set_point): reading = set_point - SPC_TEMP_DEADBAND * 1.5 pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0]) self.set_temp_reading_and_sp(reading, set_point) self.ca.assert_that_pv_is( "{}:PRESSURE:SP:RBV".format(pressure_card_pv_prefix), pressure_for(set_point)) sleep(1.5) self.ca.assert_that_pv_is( "{}:PRESSURE:SP:RBV".format(pressure_card_pv_prefix), pressure_for(set_point)) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_auto_flow_set_on_and_temp_at_setpoint_THEN_pressure_set_to_pressure_for_setpoint_temp_and_does_ramp_down( self): set_point = 10 reading = set_point pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0]) self.set_temp_reading_and_sp(reading, set_point) self.ca.assert_that_pv_is_number( "{}:PRESSURE:SP".format(pressure_card_pv_prefix), pressure_for(set_point) + SPC_OFFSET, tolerance=SPC_OFFSET / 4) # should see number in ramp self.ca.assert_that_pv_is( "{}:PRESSURE:SP".format(pressure_card_pv_prefix), pressure_for(set_point)) # final value @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_auto_flow_set_on_and_temp_above_setpoint_by_more_than_deadband_THEN_pressure_set_to_pressure_for_setpoint_temp_plus_gain_and_does_ramp( self): diff = SPC_TEMP_DEADBAND * 1.1 set_point = 10 reading = set_point + diff expected_pressure = pressure_for(set_point) + SPC_OFFSET + ( abs(reading - set_point - SPC_TEMP_DEADBAND) * SPC_GAIN)**2 pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0]) self.set_temp_reading_and_sp(reading, set_point) self.ca.assert_that_pv_is( "{}:PRESSURE:SP".format(pressure_card_pv_prefix), expected_pressure) # final value sleep(1.5) # wait for possible ramp self.ca.assert_that_pv_is( "{}:PRESSURE:SP".format(pressure_card_pv_prefix), expected_pressure) # final value @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_auto_flow_set_on_and_pressure_would_be_high_THEN_pressure_set_to_maximum_pressure( self): diff = 1000 set_point = 10 reading = set_point + diff pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0]) self.set_temp_reading_and_sp(reading, set_point) self.ca.assert_that_pv_is( "{}:PRESSURE:SP".format(pressure_card_pv_prefix), SPC_MAX_PRESSURE) # final value def test_WHEN_auto_flow_set_off_THEN_pressure_is_not_updated(self): diff = 1000 set_point = 10 reading = set_point + diff pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0]) expected_value = -10 self.ca.set_pv_value("{}:PRESSURE:SP".format(pressure_card_pv_prefix), expected_value) self.set_temp_reading_and_sp(reading, set_point, "OFF") self.ca.assert_that_pv_is( "{}:PRESSURE:SP".format(pressure_card_pv_prefix), expected_value) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_auto_flow_on_but_error_in_temp_readback_THEN_pressure_is_not_updated( self): set_point = 10 card_pv_prefix = get_card_pv_prefix(TEMP_CARDS[0]) pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0]) expected_value = -10 self.ca.set_pv_value("{}:PRESSURE:SP".format(pressure_card_pv_prefix), expected_value) self._lewis.backdoor_set_on_device("connected", False) self.ca.assert_that_pv_alarm_is( "{}:TEMP:SP:RBV".format(card_pv_prefix), self.ca.Alarms.INVALID) self.ca.set_pv_value("{}:SPC:SP".format(card_pv_prefix), "ON") self.ca.set_pv_value("{}:TEMP:SP".format(card_pv_prefix), set_point) self.ca.assert_that_pv_is( "{}:PRESSURE:SP".format(pressure_card_pv_prefix), expected_value)
class LSITests(unittest.TestCase): """ Tests for LSi Correlator """ def setUp(self): self._ioc = IOCRegister.get_running("LSI") self.ca = ChannelAccess(default_timeout=30, device_prefix=DEVICE_PREFIX) def test_GIVEN_setting_pv_WHEN_pv_written_to_THEN_new_value_read_back( self): pv_name = "MEASUREMENTDURATION" pv_value = 1000 self.ca.set_pv_value(pv_name, pv_value) self.ca.assert_that_pv_is_number(pv_name, pv_value) def test_GIVEN_setting_pv_WHEN_pv_written_to_with_invalid_value_THEN_value_not_updated( self): pv_name = "MEASUREMENTDURATION" original_value = self.ca.get_pv_value(pv_name) self.ca.set_pv_value(pv_name, -1) self.ca.assert_that_pv_is_number(pv_name, original_value) def test_GIVEN_integer_device_setting_WHEN_pv_written_to_with_a_float_THEN_value_is_rounded_before_setting( self): pv_name = "MEASUREMENTDURATION" new_value = 12.3 self.ca.set_pv_value(pv_name, new_value) self.ca.assert_that_pv_is_number(pv_name, 12) def test_GIVEN_monitor_on_setting_pv_WHEN_pv_changed_THEN_monitor_gets_updated( self): pv_name = "MEASUREMENTDURATION" self.ca.set_pv_value(pv_name, 10.0, wait=True) new_value = 12.3 expected_value = 12.0 with self.ca.assert_that_pv_monitor_is(pv_name, expected_value): self.ca.set_pv_value(pv_name + ":SP", new_value) def test_GIVEN_invalid_value_for_setting_WHEN_setting_pv_written_THEN_status_pv_updates_with_error( self): setting_pv = "MEASUREMENTDURATION" self.ca.set_pv_value(setting_pv, -1) error_message = "LSI --- wrong value assigned to MeasurementDuration" self.ca.assert_that_pv_is("ERRORMSG", error_message) @parameterized.expand([ ("NORMALIZATION", ("SYMMETRIC", "COMPENSATED")), ("SWAPCHANNELS", ("ChA_ChB", "ChB_ChA")), ("CORRELATIONTYPE", ("AUTO", "CROSS")), ("TRANSFERRATE", ("ms100", "ms150", "ms200", "ms250", "ms300", "ms400", "ms500", "ms600", "ms700")), ("SAMPLINGTIMEMULTIT", ("ns12_5", "ns200", "ns400", "ns800", "ns1600", "ns3200")) ]) def test_GIVEN_enum_setting_WHEN_setting_pv_written_to_THEN_new_value_read_back( self, pv, values): for value in values: self.ca.set_pv_value(pv, value, sleep_after_set=0.0) self.ca.assert_that_pv_is(pv, value) @parameterized.expand([("OVERLOADLIMIT", "Mcps"), ("SCATTERING_ANGLE", "degree"), ("SAMPLE_TEMP", "K"), ("SOLVENT_VISCOSITY", "mPas"), ("SOLVENT_REFRACTIVE_INDEX", ""), ("LASER_WAVELENGTH", "nm")]) def test_GIVEN_pv_with_unit_WHEN_EGU_field_read_from_THEN_unit_returned( self, pv, expected_unit): self.ca.assert_that_pv_is("{pv}.EGU".format(pv=pv), expected_unit) @parameterized.expand([ ("CORRELATION_FUNCTION", 400), ("LAGS", 400), ]) def test_GIVEN_array_pv_WHEN_NELM_field_read_THEN_length_of_array_returned( self, pv, expected_length): self.ca.assert_that_pv_is_number("{pv}.NELM".format(pv=pv), expected_length) @parameterized.expand(parameterized_list(SETTING_PVS)) def test_GIVEN_pv_name_THEN_setpoint_exists_for_that_pv( self, _, pv, value): self.ca.assert_setting_setpoint_sets_readback(value, pv) @parameterized.expand(parameterized_list(PV_NAMES)) def test_GIVEN_pv_name_THEN_val_field_exists_for_that_pv(self, _, pv): self.ca.assert_that_pv_is("{pv}.VAL".format(pv=pv), self.ca.get_pv_value(pv)) @parameterized.expand(parameterized_list(PV_NAMES)) def test_GIVEN_pv_WHEN_pv_read_THEN_pv_has_no_alarms(self, _, pv): self.ca.assert_that_pv_alarm_is(pv, self.ca.Alarms.NONE) @parameterized.expand(parameterized_list(["CORRELATION_FUNCTION", "LAGS"])) def test_GIVEN_start_pressed_WHEN_measurement_is_possible_THEN_correlation_and_lags_populated( self, _, pv): self.ca.assert_that_pv_is("RUNNING", "NO", timeout=10) self.ca.set_pv_value("START", 1, sleep_after_set=0.0) array_size = self.ca.get_pv_value("{pv}.NELM".format(pv=pv)) test_data = np.linspace(0, array_size, array_size) self.ca.assert_that_pv_value_causes_func_to_return_true( pv, lambda pv_value: np.allclose(pv_value, test_data)) def test_GIVEN_start_pressed_WHEN_measurement_already_on_THEN_error_raised( self): self.ca.set_pv_value("START", 1, sleep_after_set=0.0) self.ca.set_pv_value("START", 1, sleep_after_set=0.0) error_message = "LSI --- Cannot configure: Measurement active" self.ca.assert_that_pv_is("ERRORMSG", error_message)
class TritonTests(unittest.TestCase): """ Tests for the Triton IOC. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( "triton", DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX) def test_WHEN_device_is_started_THEN_it_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") def test_WHEN_P_setpoint_is_set_THEN_readback_updates(self): for value in PID_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback(value, "P") def test_WHEN_I_setpoint_is_set_THEN_readback_updates(self): for value in PID_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback(value, "I") def test_WHEN_D_setpoint_is_set_THEN_readback_updates(self): for value in PID_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback(value, "D") def test_WHEN_temperature_setpoint_is_set_THEN_readback_updates(self): for value in TEMPERATURE_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback( value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") @skip_if_recsim( "This is implemented at the protocol level, so does not work in recsim" ) def test_WHEN_temperature_setpoint_is_set_THEN_closed_loop_turned_on_automatically( self): for value in TEMPERATURE_TEST_VALUES: self.ca.set_pv_value("CLOSEDLOOP:SP", "Off") self.ca.assert_that_pv_is("CLOSEDLOOP", "Off") self.ca.assert_setting_setpoint_sets_readback( value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") self.ca.assert_that_pv_is("CLOSEDLOOP", "On") def test_GIVEN_closed_loop_already_on_WHEN_temperature_setpoint_is_set_THEN_closed_loop_setpoint_not_reprocessed( self): for value in TEMPERATURE_TEST_VALUES: self.ca.set_pv_value("CLOSEDLOOP:SP", "On") self.ca.assert_that_pv_is("CLOSEDLOOP", "On") timestamp_before = self.ca.get_pv_value("CLOSEDLOOP:SP.TSEL") self.ca.assert_setting_setpoint_sets_readback( value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") self.ca.assert_that_pv_is("CLOSEDLOOP", "On") self.ca.assert_that_pv_is("CLOSEDLOOP:SP.TSEL", timestamp_before) self.ca.assert_that_pv_value_is_unchanged("CLOSEDLOOP:SP.TSEL", wait=5) def test_WHEN_heater_range_is_set_THEN_readback_updates(self): for value in HEATER_RANGE_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback( value, "HEATER:RANGE") @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_power_is_set_via_backdoor_THEN_pv_has_the_value_just_set( self): for value in HEATER_RANGE_TEST_VALUES: self._lewis.backdoor_set_on_device("heater_power", value) self.ca.assert_that_pv_is("HEATER:POWER", value) self.ca.assert_that_pv_alarm_is("HEATER:POWER", self.ca.Alarms.NONE) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updates_with_value_just_set( self): for value in [False, True, False]: # Need to check both transitions work properly self._lewis.backdoor_set_on_device("closed_loop", value) self.ca.assert_that_pv_is("CLOSEDLOOP", "On" if value else "Off") @skip_if_recsim("Behaviour too complex for recsim") def test_WHEN_channels_are_enabled_and_disabled_via_pv_THEN_the_readback_pv_updates_with_value_just_set( self): for chan in VALID_TEMPERATURE_SENSORS: for enabled in [False, True, False ]: # Need to check both transitions work properly self.ca.assert_setting_setpoint_sets_readback( "ON" if enabled else "OFF", "CHANNELS:T{}:STATE".format(chan)) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical( self): # Status message that could be contained in an EPICS string type short_status = "Device status" assert 0 < len(short_status) < 40 # Status message that device is likely to return - longer than EPICS string type but reasonable for a protocol medium_status = "This is a device status that contains a bit more information" assert 40 < len(medium_status) < 256 # Short and medium statuses should be displayed in full. for status in [short_status, medium_status]: self._lewis.backdoor_set_on_device("status", status) self.ca.assert_that_pv_is("STATUS", status) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_displays_at_least_500_chars( self): # Somewhat arbitrary, but decide on a minimum number of characters that should be displayed in a # status message to the user if the status message is very long. This seems to be a reasonable # number given the messages expected, but the manual does not provide an exhaustive list. minimum_characters_in_pv = 500 # Very long status message, used to check that very long messages can be handled gracefully long_status = "This device status is quite long:" + " (here is a load of information)" * 50 assert minimum_characters_in_pv < len(long_status) # Allow truncation for long status, but it should still display as many characters as possible self._lewis.backdoor_set_on_device("status", long_status) self.ca.assert_that_pv_value_causes_func_to_return_true( "STATUS", lambda val: long_status.startswith(val) and len(val) >= minimum_characters_in_pv) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical( self): automations = [ "Warming up to 200K", "Cooling down to 1K", ] for automation in automations: self._lewis.backdoor_set_on_device("automation", automation) self.ca.assert_that_pv_is("AUTOMATION", automation) def _set_temp_via_backdoor(self, channel, temp): self._lewis.backdoor_command([ "device", "set_temperature_backdoor", "'{}'".format(channel), "{}".format(temp) ]) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_stil_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("STIL", temp) self.ca.assert_that_pv_is("STIL:TEMP", temp) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_mc_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("MC", temp) self.ca.assert_that_pv_is("MC:TEMP", temp) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_sorb_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("SORB", temp) self.ca.assert_that_pv_is("SORB:TEMP", temp) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_4KHX_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("PT2", temp) self.ca.assert_that_pv_is("4KHX:TEMP", temp) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_jthx_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("PT1", temp) self.ca.assert_that_pv_is("JTHX:TEMP", temp) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_pressure_is_set_via_backdoor_THEN_pressure_pv_updates(self): for sensor, pressure in itertools.product(VALID_PRESSURE_SENSORS, PRESSURE_TEST_VALUES): self._lewis.backdoor_command([ "device", "set_pressure_backdoor", str(sensor), str(pressure) ]) self.ca.assert_that_pv_is("PRESSURE:P{}".format(sensor), pressure) def test_WHEN_closed_loop_is_set_via_pv_THEN_readback_updates(self): for state in [False, True, False]: self.ca.assert_setting_setpoint_sets_readback( "On" if state else "Off", "CLOSEDLOOP") @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_read_mc_id_is_issued_via_arbitrary_command_THEN_response_is_in_format_device_uses( self): self.ca.set_pv_value("ARBITRARY:SP", "READ:SYS:DR:CHAN:MC") self.ca.assert_that_pv_value_causes_func_to_return_true( "ARBITRARY", lambda val: val.startswith("STAT:SYS:DR:CHAN:MC:")) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_temperature_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written( self): for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, TEMPERATURE_TEST_VALUES): self._lewis.backdoor_command([ "device", "set_sensor_property_backdoor", str(chan), "temperature", str(value) ]) self.ca.assert_that_pv_is("CHANNELS:T{}:TEMP".format(chan), value) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_resistance_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written( self): for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, RESISTANCE_TEST_VALUES): self._lewis.backdoor_command([ "device", "set_sensor_property_backdoor", str(chan), "resistance", str(value) ]) self.ca.assert_that_pv_is("CHANNELS:T{}:RES".format(chan), value) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_excitation_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written( self): for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, EXCITATION_TEST_VALUES): self._lewis.backdoor_command([ "device", "set_sensor_property_backdoor", str(chan), "excitation", str(value) ]) self.ca.assert_that_pv_is("CHANNELS:T{}:EXCITATION".format(chan), value) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_pause_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written( self): for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, TIME_DELAY_TEST_VALUES): self._lewis.backdoor_command([ "device", "set_sensor_property_backdoor", str(chan), "pause", str(value) ]) self.ca.assert_that_pv_is("CHANNELS:T{}:PAUSE".format(chan), value) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_dwell_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written( self): for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, TIME_DELAY_TEST_VALUES): self._lewis.backdoor_command([ "device", "set_sensor_property_backdoor", str(chan), "dwell", str(value) ]) self.ca.assert_that_pv_is("CHANNELS:T{}:DWELL".format(chan), value) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_resistance_is_changed_THEN_heater_heater_resistance_pv_updates( self): for heater_resistance in RESISTANCE_TEST_VALUES: self._lewis.backdoor_set_on_device("heater_resistance", heater_resistance) self.ca.assert_that_pv_is_number("HEATER:RES", heater_resistance) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_resistance_and_power_are_changed_THEN_heater_current_is_calculated_correctly( self): for res, power in itertools.product(RESISTANCE_TEST_VALUES, POWER_TEST_VALUES): self._lewis.backdoor_set_on_device("heater_resistance", res) self._lewis.backdoor_set_on_device("heater_power", power) self.ca.assert_that_pv_is_number( "HEATER:CURR", (power / res)**0.5, tolerance=0.01) # Ohm's law P = RI^2 @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_current_and_range_are_changed_THEN_heater_percent_power_is_calculated_correctly( self): for rang, res, power in itertools.product(HEATER_RANGE_TEST_VALUES, RESISTANCE_TEST_VALUES, POWER_TEST_VALUES): self._lewis.backdoor_set_on_device("heater_resistance", res) self._lewis.backdoor_set_on_device("heater_power", power) self._lewis.backdoor_set_on_device("heater_range", rang) assert rang != 0, "Heater range of zero will cause a zero division error!" self.ca.assert_that_pv_is_number("HEATER:PERCENT", 100 * ((power / res)**0.5) / rang, tolerance=0.05)
class FermichopperBase(object): """ Tests for the Fermi Chopper IOC. """ valid_commands = ["0001", "0002", "0003", "0004", "0005"] # Values that will be tested in the parametrized tests. test_chopper_speeds = [100, 350, 600] test_delay_durations = [0, 2.5, 18] test_gatewidth_values = [0, 0.5, 5] test_temperature_values = [20.0, 25.0, 37.5, 47.5] test_current_values = [0, 1.37, 2.22] test_voltage_values = [0, 282.9, 333.3] test_autozero_values = [-5.0, -2.22, 0, 1.23, 5] @abstractmethod def _get_device_prefix(self): pass def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( "fermichopper", self._get_device_prefix()) self.ca = ChannelAccess(device_prefix=self._get_device_prefix(), default_timeout=30) self.ca.assert_that_pv_exists("SPEED") self.ca.set_pv_value("DELAY:SP", 0) self.ca.set_pv_value("GATEWIDTH:SP", 0) self.ca.assert_that_pv_is_number("DELAY:SP:RBV", 0) self.ca.assert_that_pv_is_number("GATEWIDTH", 0) if not IOCRegister.uses_rec_sim: self._lewis.backdoor_run_function_on_device("reset") def is_device_broken(self): if IOCRegister.uses_rec_sim: return False # In recsim, assume device is always ok else: return self._lewis.backdoor_get_from_device("is_broken") def tearDown(self): self.assertFalse(self.is_device_broken(), "Device was broken.") def _turn_on_bearings_and_run(self): self.ca.set_pv_value("COMMAND:SP", 4) # Switch magnetic bearings on self.ca.assert_that_pv_is("STATUS.B3", "1") self.ca.set_pv_value("COMMAND:SP", 3) # Switch drive on and run self.ca.assert_that_pv_is("STATUS.B5", "1") def test_WHEN_ioc_is_started_THEN_ioc_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") @skip_if_recsim("In rec sim this test fails") def test_WHEN_last_command_is_set_via_backdoor_THEN_pv_updates(self): for value in self.valid_commands: self._lewis.backdoor_set_on_device("last_command", value) self.ca.assert_that_pv_is("LASTCOMMAND", value) def test_WHEN_speed_setpoint_is_set_THEN_readback_updates(self): for speed in self.test_chopper_speeds: self.ca.set_pv_value("SPEED:SP", speed) self.ca.assert_that_pv_is("SPEED:SP", speed) self.ca.assert_that_pv_alarm_is("SPEED:SP", self.ca.Alarms.NONE) self.ca.assert_that_pv_is("SPEED:SP:RBV", speed) self.ca.assert_that_pv_alarm_is("SPEED:SP:RBV", self.ca.Alarms.NONE) @skip_if_recsim("Recsim does not handle this") def test_WHEN_speed_setpoint_is_set_via_gui_pv_THEN_readback_updates(self): for speed in self.test_chopper_speeds: self.ca.set_pv_value("SPEED:SP:GUI", "{} Hz".format(speed)) self.ca.assert_that_pv_is("SPEED:SP", speed) self.ca.assert_that_pv_alarm_is("SPEED:SP", self.ca.Alarms.NONE) self.ca.assert_that_pv_is("SPEED:SP:RBV", speed) self.ca.assert_that_pv_alarm_is("SPEED:SP:RBV", self.ca.Alarms.NONE) def test_WHEN_delay_setpoint_is_set_THEN_readback_updates(self): for value in self.test_delay_durations: self.ca.set_pv_value("DELAY:SP", value) self.ca.assert_that_pv_is("DELAY:SP", value) self.ca.assert_that_pv_alarm_is("DELAY:SP", self.ca.Alarms.NONE) self.ca.assert_that_pv_is_number("DELAY:SP:RBV", value, tolerance=0.05) self.ca.assert_that_pv_alarm_is("DELAY:SP:RBV", self.ca.Alarms.NONE) def test_WHEN_gatewidth_is_set_THEN_readback_updates(self): for value in self.test_gatewidth_values: self.ca.set_pv_value("GATEWIDTH:SP", value) self.ca.assert_that_pv_is("GATEWIDTH:SP", value) self.ca.assert_that_pv_alarm_is("GATEWIDTH:SP", self.ca.Alarms.NONE) self.ca.assert_that_pv_is_number("GATEWIDTH", value, tolerance=0.05) self.ca.assert_that_pv_alarm_is("GATEWIDTH", self.ca.Alarms.NONE) @skip_if_recsim("In rec sim this test fails") def test_WHEN_autozero_voltages_are_set_via_backdoor_THEN_pvs_update(self): for number, boundary, value in itertools.product( [1, 2], ["upper", "lower"], self.test_autozero_values): self._lewis.backdoor_set_on_device( "autozero_{n}_{b}".format(n=number, b=boundary), value) self.ca.assert_that_pv_is_number("AUTOZERO:{n}:{b}".format( n=number, b=boundary.upper()), value, tolerance=0.05) self.ca.assert_that_pv_alarm_is( "AUTOZERO:{n}:{b}".format(n=number, b=boundary.upper()), self.ca.Alarms.NONE) @skip_if_recsim("In rec sim this test fails") def test_WHEN_drive_current_is_set_via_backdoor_THEN_pv_updates(self): for current in self.test_current_values: self._lewis.backdoor_set_on_device("current", current) self.ca.assert_that_pv_is_number("CURRENT", current, tolerance=0.1) self.ca.assert_that_pv_alarm_is("CURRENT", self.ca.Alarms.NONE) @skip_if_recsim("In rec sim this test fails") def test_GIVEN_a_stopped_chopper_WHEN_start_command_is_sent_THEN_chopper_goes_to_setpoint( self): for speed in self.test_chopper_speeds: # Setup setpoint speed self.ca.set_pv_value("SPEED:SP", speed) self.ca.assert_that_pv_is_number("SPEED:SP:RBV", speed) self._turn_on_bearings_and_run() self.ca.assert_that_pv_is_number("SPEED", speed, tolerance=0.1) @skip_if_recsim("In rec sim this test fails") def test_GIVEN_a_stopped_chopper_WHEN_start_command_is_sent_without_magnetic_bearings_on_THEN_chopper_does_not_go_to_setpoint( self): self.ca.assert_that_pv_is_number("SPEED", 0) # Switch OFF magnetic bearings self.ca.set_pv_value("COMMAND:SP", 5) self.ca.assert_that_pv_is("LASTCOMMAND", "0005") self.ca.assert_that_pv_is("STATUS.B3", "0") for speed in self.test_chopper_speeds: # Setup setpoint speed self.ca.set_pv_value("SPEED:SP", speed) self.ca.assert_that_pv_is_number("SPEED:SP:RBV", speed) with assert_log_messages(self._ioc, in_time=2, must_contain=ErrorStrings. ATTEMPT_TO_TURN_ON_WITHOUT_BEARINGS): # Run mode ON self.ca.set_pv_value("COMMAND:SP", 3) # Ensure the ON command has been ignored and last command is still "switch off bearings" self.ca.assert_that_pv_is("LASTCOMMAND", "0005") self.ca.assert_that_pv_is_number("SPEED", 0, tolerance=0.1) @skip_if_recsim("In rec sim this test fails") def test_GIVEN_a_chopper_at_speed_WHEN_switch_off_magnetic_bearings_command_is_sent_THEN_magnetic_bearings_do_not_switch_off( self): speed = 150 # Setup setpoint speed self.ca.set_pv_value("SPEED:SP", speed) self.ca.assert_that_pv_is_number("SPEED:SP:RBV", speed) # Run mode ON self._turn_on_bearings_and_run() # Wait for chopper to get up to speed self.ca.assert_that_pv_is_number("SPEED", speed, tolerance=0.1) with assert_log_messages(self._ioc, in_time=2, must_contain=ErrorStrings. ATTEMPT_TO_TURN_OFF_BEARINGS_AT_SPEED): # Attempt to switch OFF magnetic bearings self.ca.set_pv_value("COMMAND:SP", 5) # Assert that bearings did not switch off sleep(5) self.ca.assert_that_pv_is("LASTCOMMAND", "0003") self.ca.assert_that_pv_is("STATUS.B3", "1") self.ca.assert_that_pv_is("SPEED", speed) @skip_if_recsim("In rec sim this test fails") def test_GIVEN_a_stopped_chopper_WHEN_switch_on_and_off_magnetic_bearings_commands_are_sent_THEN_magnetic_bearings_switch_on_and_off( self): # Switch ON magnetic bearings self.ca.set_pv_value("COMMAND:SP", 4) self.ca.assert_that_pv_is("LASTCOMMAND", "0004") self.ca.assert_that_pv_is("STATUS.B3", "1") # Switch OFF magnetic bearings self.ca.set_pv_value("COMMAND:SP", 5) self.ca.assert_that_pv_is("LASTCOMMAND", "0005") self.ca.assert_that_pv_is("STATUS.B3", "0") @skip_if_recsim("In rec sim this test fails") def test_WHEN_chopper_speed_is_too_high_THEN_status_updates(self): too_fast = 700 # Turn on magnetic bearings otherwise device will report it is broken self._lewis.backdoor_set_on_device("magneticbearing", True) self.ca.assert_that_pv_is("STATUS.B3", "1") with assert_log_messages( self._ioc, in_time=2, must_contain=ErrorStrings.CONTROLLER_OVERSPEED): self._lewis.backdoor_set_on_device("speed", too_fast) self.ca.assert_that_pv_is("STATUS.BA", "1") self._lewis.backdoor_set_on_device("speed", 0) self.ca.assert_that_pv_is("STATUS.BA", "0") @skip_if_recsim("Uses lewis backdoor") def test_GIVEN_autozero_voltages_are_out_of_range_WHEN_chopper_is_moving_THEN_range_check_fails( self): for number, position in itertools.product([1, 2], ["upper", "lower"]): self.ca.assert_that_pv_is("AUTOZERO:RANGECHECK", 0) with assert_log_messages(self._ioc, in_time=2, must_contain=ErrorStrings. CONTROLLER_AUTOZERO_OUT_OF_RANGE): # Set autozero voltage too high self._lewis.backdoor_set_on_device( "autozero_{n}_{p}".format(n=number, p=position), 3.2) # Assert self.ca.assert_that_pv_is("AUTOZERO:RANGECHECK", 1) # Reset relevant autozero voltage back to zero self._lewis.backdoor_set_on_device( "autozero_{n}_{p}".format(n=number, p=position), 0) self.ca.assert_that_pv_is_number("AUTOZERO:{n}:{p}".format( n=number, p=position.upper()), 0, tolerance=0.1) @contextmanager def _lie_about(self, lie): if IOCRegister.uses_rec_sim: raise IOError("Can't use lewis backdoor in recsim!") self._lewis.backdoor_set_on_device("is_lying_about_{}".format(lie), True) try: yield finally: self._lewis.backdoor_set_on_device("is_lying_about_{}".format(lie), False) def _lie_about_delay_setpoint_readback(self): return self._lie_about("delay_sp_rbv") def _lie_about_gatewidth(self): return self._lie_about("gatewidth") @skip_if_recsim("Lying about setpoint readback not possible in recsim") def test_GIVEN_device_lies_about_delay_setpoint_WHEN_setting_a_delay_THEN_keeps_trying_until_device_does_not_lie( self): test_value = 567.8 tolerance = 0.05 self.ca.set_pv_value("DELAY:SP", test_value) self.ca.assert_that_pv_is_number("DELAY:SP:RBV", test_value, tolerance=tolerance) with self._lie_about_delay_setpoint_readback(): self.ca.assert_that_pv_value_causes_func_to_return_true( "DELAY:SP:RBV", lambda v: abs(v - test_value) > tolerance) # Some time later the driver should resend the setpoint which causes the device to behave properly again: self.ca.assert_that_pv_is_number("DELAY:SP:RBV", test_value, tolerance=tolerance) @skip_if_recsim("Lying about gate width not possible in recsim") def test_GIVEN_device_lies_about_gatewidth_WHEN_setting_a_gatewidth_THEN_keeps_trying_until_device_does_not_lie( self): test_value = 567.8 tolerance = 0.05 self.ca.set_pv_value("GATEWIDTH:SP", test_value) self.ca.assert_that_pv_is_number("GATEWIDTH", test_value, tolerance=tolerance) with self._lie_about_gatewidth(): self.ca.assert_that_pv_value_causes_func_to_return_true( "GATEWIDTH", lambda v: abs(v - test_value) > tolerance) # Some time later the driver should resend the setpoint which causes the device to behave properly again: self.ca.assert_that_pv_is_number("GATEWIDTH", test_value, tolerance=tolerance) @unstable_test() @skip_if_recsim("Device breakage not simulated in RECSIM") def test_GIVEN_setpoint_is_already_at_600Hz_WHEN_setting_setpoint_to_600Hz_THEN_device_does_not_break( self): self._turn_on_bearings_and_run() self.ca.set_pv_value("SPEED:SP", 600) self.ca.assert_that_pv_is_number("SPEED", 600, tolerance=0.1) self.ca.set_pv_value("SPEED:SP", 600) self.ca.assert_that_pv_is_number("SPEED", 600, tolerance=0.1) # Assertion that device is not broken occurs in tearDown() @skip_if_recsim("Device breakage not simulated in RECSIM") def test_GIVEN_setpoint_is_at_600Hz_and_device_already_running_WHEN_send_run_command_THEN_device_does_not_break( self): self._turn_on_bearings_and_run() self.ca.set_pv_value("SPEED:SP", 600) self.ca.assert_that_pv_is_number("SPEED", 600, tolerance=0.1) with assert_log_messages(self._ioc, in_time=2, must_contain=ErrorStrings.TURN_ON_AT_600HZ): self.ca.set_pv_value("COMMAND:SP", 3) # Switch drive on and run # Assertion that device is not broken occurs in tearDown() @skip_if_recsim("Cannot use backdoor in recsim") def test_GIVEN_speed_setpoint_has_been_sent_to_device_WHEN_the_device_decides_to_go_to_another_setpoint_THEN_the_original_setpoint_is_resent( self): """ This test simulates some behaviour seen on MERLIN where the device just decided to reset it's speed setpoint without being provoked. """ self._turn_on_bearings_and_run() for speed in self.test_chopper_speeds: self.ca.assert_setting_setpoint_sets_readback( speed, set_point_pv="SPEED:SP", readback_pv="SPEED:SP:RBV") # At some point the device starts to do it's own thing... self._lewis.backdoor_set_on_device("speed_setpoint", speed - 50) self.ca.assert_that_pv_is("SPEED:SP:RBV", speed - 50) # Within a minute, the IOC should notice and resend the setpoint to what it should really be self.ca.assert_that_pv_is("SPEED:SP:RBV", speed, timeout=60) # # Mandatory safety tests # # The following behaviours MUST be implemented by the chopper according to the manual # @skip_if_recsim("In rec sim this test fails") def test_WHEN_chopper_speed_is_too_high_THEN_switch_drive_off_is_sent( self): self._lewis.backdoor_set_on_device("magneticbearing", True) self.ca.assert_that_pv_is("STATUS.B3", "1") # Reset last command so that we can tell that it's changed later on self._lewis.backdoor_set_on_device("last_command", "0000") self.ca.assert_that_pv_is("LASTCOMMAND", "0000") with assert_log_messages(self._ioc, in_time=2, must_contain=ErrorStrings.SOFTWARE_OVERSPEED): # Speed = 610, this is higher than the maximum allowed speed (606) self._lewis.backdoor_set_on_device("speed", 610) # Assert that "switch drive off" was sent self.ca.assert_that_pv_is("LASTCOMMAND", "0002") @skip_if_recsim("In rec sim this test fails") def test_GIVEN_magnetic_bearing_is_off_WHEN_chopper_speed_is_moving_THEN_switch_drive_on_and_stop_is_sent( self): # Magnetic bearings should have been turned off in setUp self.ca.assert_that_pv_is("STATUS.B3", "0") # Reset last command so that we can tell that it's changed later on self._lewis.backdoor_set_on_device("last_command", "0000") self.ca.assert_that_pv_is("LASTCOMMAND", "0000") with assert_log_messages( self._ioc, in_time=2, must_contain=ErrorStrings.CHOPPER_AT_SPEED_WITH_BEARINGS_OFF): # Speed = 7 because that's higher than the threshold in the IOC (5) # but lower than the threshold in the emulator (10) self._lewis.backdoor_set_on_device("speed", 7) # Assert that "switch drive on and stop" was sent self.ca.assert_that_pv_is("LASTCOMMAND", "0001") @skip_if_recsim("In rec sim this test fails") def test_GIVEN_autozero_voltages_are_out_of_range_WHEN_chopper_is_moving_THEN_switch_drive_on_and_stop_is_sent( self): for number, position in itertools.product([1, 2], ["upper", "lower"]): err = None for i in range(10): # Try up to 10 times. try: self._lewis.backdoor_run_function_on_device("reset") # Assert that the last command is zero as expected self.ca.assert_that_pv_is("LASTCOMMAND", "0000") # Check that the last command is not being set to something else by the IOC self.ca.assert_that_pv_value_is_unchanged("LASTCOMMAND", wait=10) break except AssertionError as e: err = e else: # no-break raise err with assert_log_messages( self._ioc, in_time=2, must_contain=ErrorStrings.SOFTWARE_AUTOZERO_OUT_OF_RANGE): # Set autozero voltage too high and set device moving self._lewis.backdoor_set_on_device( "autozero_{n}_{p}".format(n=number, p=position), 3.2) self._lewis.backdoor_set_on_device("speed", 7) # Assert that "switch drive on and stop" was sent self.ca.assert_that_pv_is("LASTCOMMAND", "0001") # Reset relevant autozero voltage back to zero self._lewis.backdoor_set_on_device( "autozero_{n}_{p}".format(n=number, p=position), 0) self.ca.assert_that_pv_is_number("AUTOZERO:{n}:{p}".format( n=number, p=position.upper()), 0, tolerance=0.1)
class MezfliprTests(unittest.TestCase): def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc(EMULATOR_NAME, DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=30) def test_WHEN_ioc_is_started_THEN_ioc_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") @parameterized.expand(parameterized_list(["On", "Off"])) def test_GIVEN_power_is_set_THEN_can_be_read_back(self, _, state): self.ca.assert_setting_setpoint_sets_readback(state, readback_pv="{}:POWER".format(flipper)) @parameterized.expand(parameterized_list([0., 0.12, 5000.5])) def test_GIVEN_compensation_is_set_THEN_compensation_can_be_read_back(self, _, compensation): self.ca.assert_setting_setpoint_sets_readback(compensation, readback_pv="{}:COMPENSATION".format(flipper)) def _assert_mode(self, mode): self.ca.assert_that_pv_is("{}:MODE".format(flipper), mode) self.ca.assert_that_pv_alarm_is("{}:MODE".format(flipper), self.ca.Alarms.NONE) def _assert_params(self, param): self.ca.assert_that_pv_value_causes_func_to_return_true("{}:PARAMS".format(flipper), lambda val: val is not None and val.rstrip() == param) self.ca.assert_that_pv_alarm_is("{}:PARAMS".format(flipper), self.ca.Alarms.NONE) @skip_if_recsim("State of device not simulated in recsim") def test_WHEN_constant_current_mode_set_THEN_parameters_reflected_and_mode_is_constant_current(self): param = 25 self.ca.set_pv_value("{}:CURRENT:SP".format(flipper), param) self._assert_params("{:.1f}".format(param)) self._assert_mode("static") @skip_if_recsim("State of device not simulated in recsim") def test_WHEN_steps_mode_set_THEN_parameters_reflected_and_mode_is_steps(self): param = "[some, random, list, of, data]" self.ca.set_pv_value("{}:CURRENT_STEPS:SP".format(flipper), param) self._assert_params(param) self._assert_mode("steps") @skip_if_recsim("State of device not simulated in recsim") def test_WHEN_analytical_mode_set_THEN_parameters_reflected_and_mode_is_analytical(self): param = "a long string of parameters which is longer than 40 characters" self.ca.set_pv_value("{}:CURRENT_ANALYTICAL:SP".format(flipper), param) self._assert_params(param) self._assert_mode("analytical") @skip_if_recsim("State of device not simulated in recsim") def test_WHEN_file_mode_set_THEN_parameters_reflected_and_mode_is_file(self): param = r"C:\some\file\path\to\a\file\in\a\really\deep\directory\structure\with\path\longer\than\40\characters" self.ca.set_pv_value("{}:FILENAME:SP".format(flipper), param) self._assert_params(param) self._assert_mode("file") @parameterized.expand(parameterized_list(["MODE", "COMPENSATION", "PARAMS"])) @skip_if_recsim("Recsim cannot test disconnected device") def test_WHEN_device_is_disconnected_THEN_pvs_are_in_invalid_alarm(self, _, pv): try: self._lewis.backdoor_set_on_device("connected", False) self.ca.assert_that_pv_alarm_is("{}:{}".format(flipper, pv), self.ca.Alarms.INVALID) finally: self._lewis.backdoor_set_on_device("connected", True)
class SampleChangerTests(unittest.TestCase): """ Tests for the sample changer. """ def setUp(self): self.ca = ChannelAccess(default_timeout=5) self.ca.assert_that_pv_exists("SAMPCHNG:SLOT") for axis in AXES: self.ca.assert_that_pv_exists("MOT:{}".format(axis)) # Select one of the standard slots. self.ca.assert_setting_setpoint_sets_readback(readback_pv="SAMPCHNG:SLOT", value=SLOTS[0]) @parameterized.expand(parameterized_list([ { "slot_name": "_ALL", "positions_exist": ["{}CB".format(n) for n in range(1, 14+1)] + ["{}CT".format(n) for n in range(1, 14+1)], "positions_not_exist": [], }, { "slot_name": "CT", "positions_exist": ["{}CT".format(n) for n in range(1, 14+1)], "positions_not_exist": ["{}CB".format(n) for n in range(1, 14+1)], }, { "slot_name": "CB", "positions_exist": ["{}CB".format(n) for n in range(1, 14+1)], "positions_not_exist": ["{}CT".format(n) for n in range(1, 14+1)], }, ])) def test_WHEN_slot_set_to_empty_string_THEN_all_positions_listed(self, _, settings): self.ca.assert_setting_setpoint_sets_readback(readback_pv="SAMPCHNG:SLOT", value=settings["slot_name"]) for pos in settings["positions_exist"]: self.ca.assert_that_pv_value_causes_func_to_return_true("LKUP:SAMPLE:POSITIONS", func=lambda val: pos in val) for pos in settings["positions_not_exist"]: self.ca.assert_that_pv_value_causes_func_to_return_true("LKUP:SAMPLE:POSITIONS", func=lambda val: pos not in val) def test_WHEN_invalid_slot_is_entered_THEN_old_slot_kept(self): # First set a valid slot self.ca.assert_setting_setpoint_sets_readback(readback_pv="SAMPCHNG:SLOT", value="CT") self.ca.set_pv_value("SAMPCHNG:SLOT:SP", "does_not_exist", sleep_after_set=0) self.ca.assert_that_pv_alarm_is("SAMPCHNG:SLOT:SP", self.ca.Alarms.INVALID) self.ca.assert_that_pv_is("SAMPCHNG:SLOT", "CT") self.ca.assert_that_pv_value_is_unchanged("SAMPCHNG:SLOT", wait=3) for pos in ["{}CT".format(n) for n in range(1, 14+1)]: self.ca.assert_that_pv_value_causes_func_to_return_true("LKUP:SAMPLE:POSITIONS", func=lambda val: pos in val) def test_available_slots_can_be_loaded(self): self.ca.assert_that_pv_value_causes_func_to_return_true("SAMPCHNG:AVAILABLE_SLOTS", func=lambda val: all(s in val for s in SLOTS)) @contextlib.contextmanager def _temporarily_add_slot(self, new_slot_name): file_paths = [os.path.join(test_path, "samplechanger.xml"), os.path.join(test_path, "rack_definitions.xml")] xml_trees = {} for file_path in file_paths: xml_trees[file_path] = etree.parse(file_path) shutil.copy2(file_path, file_path + ".backup") try: for path, tree in xml_trees.items(): slot = tree.find("//slot") # Overwrite an existing slot rather than duplicating, otherwise we end up with duplicate positions # and the file fails to write correctly. slot.set("name", new_slot_name) tree.write(path) yield finally: for file_path in file_paths: os.remove(file_path) shutil.move(file_path + ".backup", file_path) def new_slot_positions_exist(self, new_slot_name): def _wrapper(val): return any(prefix + new_slot_name in val for prefix in ["1", "A"]) return _wrapper def new_slot_positions_do_not_exist(self, new_slot_name): def _wrapper(val): # Check none of first 5 positions exist return all(str(prefix) + new_slot_name not in val for prefix in ["1", "A"]) return _wrapper def test_GIVEN_sample_changer_file_modified_THEN_new_changer_available(self): new_slot_name = "NEWSLOT" with self._temporarily_add_slot(new_slot_name): # assert new slot has been picked up self.ca.assert_that_pv_value_causes_func_to_return_true("SAMPCHNG:AVAILABLE_SLOTS", func=lambda val: new_slot_name in val) self.ca.assert_that_pv_value_causes_func_to_return_true("SAMPCHNG:AVAILABLE_SLOTS", func=lambda val: new_slot_name not in val) def test_GIVEN_sample_changer_file_modified_and_selected_THEN_new_positions_available_without_needing_recalc(self): new_slot_name = "NEWSLOT" with self._temporarily_add_slot(new_slot_name): # assert new slot has been picked up self.ca.assert_that_pv_value_causes_func_to_return_true("SAMPCHNG:AVAILABLE_SLOTS", func=lambda val: new_slot_name in val) # Select newly added sample changer self.ca.set_pv_value("SAMPCHNG:SLOT:SP", new_slot_name) # Assert positions from newly added sample changer exist # Position names are slot_name + either 1 or A depending on naming convention of slot self.ca.assert_that_pv_value_causes_func_to_return_true("LKUP:SAMPLE:POSITIONS", func=self.new_slot_positions_exist(new_slot_name)) # Now out of context manager, NEWSLOT no longer exists self.ca.assert_that_pv_value_causes_func_to_return_true("SAMPCHNG:AVAILABLE_SLOTS", func=lambda val: new_slot_name not in val) # Use all positions from all available changers self.ca.set_pv_value("SAMPCHNG:SLOT:SP", "_ALL") # Positions from the now-deleted changer shouldn't exist self.ca.assert_that_pv_value_causes_func_to_return_true("LKUP:SAMPLE:POSITIONS", func=self.new_slot_positions_do_not_exist(new_slot_name)) def test_GIVEN_sample_changer_file_modified_THEN_new_positions_available(self): # Select all positions from all changers self.ca.set_pv_value("SAMPCHNG:SLOT:SP", "_ALL") new_slot_name = "NEWSLOT" with self._temporarily_add_slot(new_slot_name): # assert new slot has been picked up self.ca.assert_that_pv_value_causes_func_to_return_true("SAMPCHNG:AVAILABLE_SLOTS", func=lambda val: new_slot_name in val) self.ca.set_pv_value("SAMPCHNG:RECALC.PROC", 1) # Assert positions from newly added sample changer exist self.ca.assert_that_pv_value_causes_func_to_return_true("LKUP:SAMPLE:POSITIONS", func=self.new_slot_positions_exist(new_slot_name)) # Now out of context manager, NEWSLOT no longer exists self.ca.assert_that_pv_value_causes_func_to_return_true("SAMPCHNG:AVAILABLE_SLOTS", func=lambda val: new_slot_name not in val) # Explicit recalc as we are still looking at all positions so haven't changed sample changer. self.ca.set_pv_value("SAMPCHNG:RECALC.PROC", 1) # Positions from the now-deleted changer shouldn't exist self.ca.assert_that_pv_value_causes_func_to_return_true("LKUP:SAMPLE:POSITIONS", func=self.new_slot_positions_do_not_exist(new_slot_name))