コード例 #1
0
class Knr1050Tests(unittest.TestCase):
    """
    Tests for the Knr1050 IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(device_name, DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_wait_time=0.0)
        self._lewis.backdoor_run_function_on_device("reset")
        # Set the device in remote mode ready to receive instructions
        self.ca.set_pv_value("MODE:SP", "REMOTE")
        self.ca.set_pv_value("MODE.PROC", 1)
        # Set the flow and concentrations to a default state that enable pump switch on
        self.ca.set_pv_value("STOP:SP", 1)
        self.ca.set_pv_value("STATUS", "OFF")
        self.ca.set_pv_value("FLOWRATE:SP", 0.01)
        self.ca.set_pv_value("PRESSURE:MIN:SP", 0)
        self.ca.set_pv_value("PRESSURE:MAX:SP", 100)
        self.ca.set_pv_value("COMP:A:SP", 100)
        self.ca.set_pv_value("COMP:B:SP", 0)
        self.ca.set_pv_value("COMP:C:SP", 0)
        self.ca.set_pv_value("COMP:D:SP", 0)
        self.ca.set_pv_value("STATUS:GET.PROC", 1)
        self.ca.set_pv_value("DISABLE:CHECK.PROC", 1)

    def _set_pressure_limit_low(self, limit):
        self._lewis.backdoor_set_on_device("pressure_limit_low", limit)

    def _set_pressure_limit_high(self, limit):
        self._lewis.backdoor_set_on_device("pressure_limit_high", limit)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_an_ioc_WHEN_start_pump_sent_THEN_pump_starts(self):
        self.ca.set_pv_value("START:SP", 1)

        self.ca.assert_that_pv_is("STATUS", "IDLE")

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_an_ioc_WHEN_timed_pump_sent_THEN_pump_starts(self):
        self.ca.set_pv_value("TIMED:SP", 1)

        self.ca.assert_that_pv_is("STATUS", "IDLE")

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_an_ioc_WHEN_stop_pump_sent_via_ioc_THEN_device_state_off(self):
        expected_dev_state = "OFF"
        self.ca.set_pv_value("STOP:SP", 1)

        self.ca.assert_that_pv_is("STATUS", expected_dev_state)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_an_ioc_WHEN_pump_is_turned_on_via_ioc_THEN_pump_is_on(self):
        self.ca.set_pv_value("START:SP", 1)
        pump_status = self._lewis.backdoor_get_from_device("pump_on")

        self.assertEqual(pump_status, True)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_set_concentration_via_ioc_WHEN_ramp_command_sent_via_ioc_THEN_correct_concentration_set(self):
        expected_concentrations = [0, 50, 35, 15]
        self.ca.set_pv_value("COMP:A:SP", expected_concentrations[0])
        self.ca.set_pv_value("COMP:B:SP", expected_concentrations[1])
        self.ca.set_pv_value("COMP:C:SP", expected_concentrations[2])
        self.ca.set_pv_value("COMP:D:SP", expected_concentrations[3])
        self.ca.set_pv_value("START:SP", 1)

        sleep(1.0) # allow emulator to process above data

        concentrations = [self.ca.get_pv_value("COMP:A"),
                          self.ca.get_pv_value("COMP:B"),
                          self.ca.get_pv_value("COMP:C"),
                          self.ca.get_pv_value("COMP:D")]
        self.assertEqual(expected_concentrations, concentrations)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_an_ioc_WHEN_stop_pump_sent_THEN_lewis_pump_stops(self):
        expected_pump_status = False
        self.ca.set_pv_value("STOP:SP", 1)
        pump_status = self._lewis.backdoor_get_from_device("pump_on")

        self.assertEqual(pump_status, expected_pump_status)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_an_ioc_WHEN_stop2_command_sent_THEN_expected_stop_type(self):
        self._lewis.backdoor_set_on_device("keep_last_values", False)
        stopped_status = self._lewis.backdoor_get_from_device("keep_last_values")
        self.assertEqual(stopped_status, False)
        self.ca.set_pv_value("_STOP:KLV:SP", 1)

        stopped_status = self._lewis.backdoor_get_from_device("keep_last_values")
        self.assertEqual(stopped_status, True)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_an_ioc_WHEN_pump_switched_on_then_back_to_off_THEN_device_state_off(self):
        expected_dev_state = "OFF"
        self.ca.set_pv_value("START:SP", 1)
        self.ca.set_pv_value("STOP:SP", 1)

        sleep(1.0) # allow emulator to process above data

        state = self._lewis.backdoor_get_from_device("state")

        self.assertEqual(expected_dev_state, state)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_set_low_pressure_limit_via_backdoor_WHEN_get_low_pressure_limits_via_IOC_THEN_get_expected_pressure_limit(self):
        expected_pressure = 10
        self._set_pressure_limit_low(expected_pressure)
        self.ca.set_pv_value("PRESSURE:LIMITS.PROC", 1)

        self.ca.assert_that_pv_is("PRESSURE:MIN", expected_pressure)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_set_high_pressure_limit_via_backdoor_WHEN_get_high_pressure_limits_via_IOC_THEN_get_expected_pressure_limit(self):
        expected_pressure = 100
        self._set_pressure_limit_high(expected_pressure)
        self.ca.set_pv_value("PRESSURE:LIMITS.PROC", 1)

        self.ca.assert_that_pv_is("PRESSURE:MAX", expected_pressure)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_set_low_pressure_limit_via_ioc_WHEN_get_low_pressure_limit_THEN_get_expected_pressure_limit(self):
        expected_pressure = 10
        self.ca.set_pv_value("PRESSURE:MIN:SP", expected_pressure)
        self.ca.set_pv_value("PRESSURE:LIMITS.PROC", 1)
        self.ca.assert_that_pv_is("PRESSURE:MIN", expected_pressure)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_set_high_pressure_limit_via_ioc_WHEN_get_high_pressure_limit_via_backdoor_THEN_get_expected_pressure_limit(self):
        expected_pressure = 200
        self.ca.set_pv_value("PRESSURE:MAX:SP", expected_pressure)
        self.ca.set_pv_value("PRESSURE:LIMITS.PROC", 1)
        self.ca.assert_that_pv_is("PRESSURE:MAX", expected_pressure)

        self.assertEqual(self._lewis.backdoor_get_from_device("pressure_limit_high"), expected_pressure)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_set_low_pressure_limit_via_ioc_WHEN_get_low_pressure_limit_via_IOC_THEN_get_expected_value(self):
        expected_pressure = 45
        self.ca.set_pv_value("PRESSURE:MIN:SP", expected_pressure)
        self.ca.set_pv_value("PRESSURE:LIMITS.PROC", 1)
        self.ca.assert_that_pv_is("PRESSURE:MIN", expected_pressure)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_set_high_pressure_limit_via_ioc_WHEN_get_high_pressure_limit_via_IOC_THEN_get_expected_value(self):
        expected_pressure = 500

        self.ca.set_pv_value("PRESSURE:MAX:SP", expected_pressure)
        self.ca.set_pv_value("PRESSURE:LIMITS.PROC", 1)
        self.ca.assert_that_pv_is("PRESSURE:MAX", expected_pressure)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_set_flow_limit_min_via_ioc_WHEN_ramp_command_sent_via_IOC_THEN_correct_flow_limit_set(self):
        expected_flow = 0.01
        self.ca.set_pv_value("FLOWRATE:SP", expected_flow)
        self.ca.set_pv_value("START:SP", 1)

        self.ca.assert_that_pv_is("FLOWRATE", expected_flow)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_set_flow_limit_min_via_ioc_WHEN_get_flow_via_IOC_THEN_correct_flow_limit(self):
        expected_flow = 0.01
        self.ca.set_pv_value("FLOWRATE:SP", expected_flow)

        self.assertEqual(self.ca.get_pv_value("FLOWRATE:SP:RBV"), expected_flow)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_ioc_turned_on_WHEN_get_dev_state_via_ioc_THEN_off_state_returned(self):
        expected_dev_state = 'OFF'
        state = self.ca.get_pv_value("STATUS")

        self.assertEqual(expected_dev_state, state)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_ioc_turned_on_WHEN_set_local_mode_via_IOC_THEN_disabled_mode(self):
        expected_mode = 'Disabled'
        self.ca.set_pv_value("MODE:SP", "LOCAL")

        self.ca.assert_that_pv_is("DISABLE", expected_mode)

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_local_mode_WHEN_set_pump_on_via_IOC_THEN_pump_disabled(self):
        self.ca.set_pv_value("MODE:SP", "LOCAL")
        self.ca.set_pv_value("START:SP", 1)

        self.ca.assert_that_pv_is("STATUS", 'OFF')

    @skip_if_recsim("Recsim simulation not implemented")
    def test_GIVEN_incorrect_gradients_WHEN_set_pump_on_via_IOC_THEN_pump_disabled(self):
        self.ca.set_pv_value("COMP:A:SP", 50)  # sum of gradients =/= 100%
        self.ca.set_pv_value("START:SP", 1)

        self.ca.assert_that_pv_is("STATUS", 'OFF')

    @skip_if_recsim("Can not test disconnection in rec sim")
    def test_GIVEN_device_not_connected_WHEN_get_status_THEN_alarm(self):
        self._lewis.backdoor_set_on_device('connected', False)
        self.ca.assert_that_pv_alarm_is('PRESSURE:LIMITS', ChannelAccess.Alarms.INVALID)

    @skip_if_recsim("Can not test disconnection in rec sim")
    def test_GIVEN_timed_run_started_THEN_remaining_time_decreases(self):
        self.ca.set_pv_value("TIME:SP", 10)
        self.ca.set_pv_value("TIMED:SP", 1)

        self.ca.assert_that_pv_value_is_decreasing("TIME:REMAINING", wait=5)

    @skip_if_recsim("Can not test disconnection in rec sim")
    def test_GIVEN_timed_run_started_THEN_pump_stopped_once_finished_run(self):
        self.ca.set_pv_value("TIME:SP", 10)
        self.ca.set_pv_value("TIMED:SP", 1)

        self.ca.assert_that_pv_is("STATUS", 'OFF', timeout=15)

    @skip_if_recsim("Can not test disconnection in rec sim")
    def test_GIVEN_long_timed_run_started_THEN_if_remaining_time_checked_then_not_finished(self):
        self.ca.set_pv_value("TIME:SP", 100)
        self.ca.set_pv_value("TIMED:SP", 1)

        self.ca.assert_that_pv_is("TIME:CHECK", 0)

    @skip_if_recsim("Can not test disconnection in rec sim")
    def test_GIVEN_set_volume_run_started_THEN_remaining_volume_decreases(self):
        self.ca.set_pv_value("FLOWRATE:SP", 0.02)
        self.ca.set_pv_value("VOL:SP", 0.05)
        self.ca.set_pv_value("TIMED:SP", 1)
        self.ca.assert_that_pv_is_not("VOL:REMAINING", 0.0, timeout=5)

        self.ca.assert_that_pv_value_is_decreasing("VOL:REMAINING", wait=5)

    @skip_if_recsim("Can't use lewis backdoor in RECSIM")
    def test_GIVEN_input_error_THEN_error_string_captured(self):
        expected_error = "20,Instrument in standalone mode"
        self._lewis.backdoor_set_on_device("input_correct", False)

        self.ca.assert_that_pv_is("ERROR:STR", expected_error, timeout=5)
コード例 #2
0
class Ilm200Tests(unittest.TestCase):
    """
    Tests for the Ilm200 IOC.
    """
    DEFAULT_SCAN_RATE = 1
    SLOW = "Slow"
    FAST = "Fast"
    LEVEL_TOLERANCE = 0.1

    FULL = 100.0
    LOW = 10.0
    FILL = 5.0

    RATE = "RATE"
    LEVEL = "LEVEL"
    TYPE = "TYPE"
    CURRENT = "CURR"

    @staticmethod
    def channel_range():
        number_of_channels = 3
        starting_index = 1
        return range(starting_index, starting_index + number_of_channels)

    def helium_channels(self):
        for i in self.channel_range():
            if self.ca.get_pv_value(self.ch_pv(i, self.TYPE)) != "Nitrogen":
                yield i

    @staticmethod
    def ch_pv(channel, pv):
        return "CH{}:{}".format(channel, pv)

    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc("ilm200", DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_wait_time=0.0)
        self.ca.assert_that_pv_exists("VERSION", timeout=30)
        self._lewis.backdoor_set_on_device("cycle", False)

        self._lewis.backdoor_run_function_on_device("set_cryo_type", (1, Ilm200ChannelTypes.NITROGEN))
        self._lewis.backdoor_run_function_on_device("set_cryo_type", (2, Ilm200ChannelTypes.HELIUM))
        self._lewis.backdoor_run_function_on_device("set_cryo_type", (3, Ilm200ChannelTypes.HELIUM_CONT))

    def set_level_via_backdoor(self, channel, level):
        self._lewis.backdoor_command(["device", "set_level", str(channel), str(level)])

    def set_helium_current_via_backdoor(self, channel, is_on):
        self._lewis.backdoor_command(["device", "set_helium_current", str(channel), str(is_on)])

    def check_state(self, channel, level, is_filling, is_low):
        self.ca.assert_that_pv_is_number(self.ch_pv(channel, self.LEVEL), level, self.LEVEL_TOLERANCE)
        self.ca.assert_that_pv_is(self.ch_pv(channel, "FILLING"), "Filling" if is_filling else "Not filling")
        self.ca.assert_that_pv_is(self.ch_pv(channel, "LOW"), "Low" if is_low else "Not low")

    def test_GIVEN_ilm200_THEN_has_version(self):
        self.ca.assert_that_pv_is_not("VERSION", "")
        self.ca.assert_that_pv_alarm_is("VERSION", self.ca.Alarms.NONE)

    def test_GIVEN_ilm200_THEN_each_channel_has_type(self):
        for i in self.channel_range():
            self.ca.assert_that_pv_is_not(self.ch_pv(i, self.TYPE), "Not in use")
            self.ca.assert_that_pv_alarm_is(self.ch_pv(i, self.TYPE), self.ca.Alarms.NONE)

    def set_and_check_level(self):
        for i in self.channel_range():
            level = ALARM_THRESHOLDS[i] + 10
            self.set_level_via_backdoor(i, level)
            self.ca.assert_that_pv_is_number(self.ch_pv(i, self.LEVEL), level, tolerance=0.1)
            self.ca.assert_that_pv_alarm_is(self.ch_pv(i, self.LEVEL), self.ca.Alarms.NONE)

    @skip_if_recsim("no backdoor in recsim")
    def test_GIVEN_ilm_200_THEN_can_read_level(self):
        self.set_and_check_level()

    @skip_if_recsim("no backdoor in recsim")
    def test_GIVEN_ilm_200_non_isobus_THEN_can_read_level(self):
        with self._ioc.start_with_macros({"USE_ISOBUS": "NO"}, pv_to_wait_for="VERSION"):
            self.set_and_check_level()

    @skip_if_recsim("Cannot do back door of dynamic behaviour in recsim")
    def test_GIVEN_ilm_200_WHEN_level_set_on_device_THEN_reported_level_matches_set_level(self):
        for i in self.channel_range():
            expected_level = i*12.3
            self.set_level_via_backdoor(i, expected_level)
            self.ca.assert_that_pv_is_number(self.ch_pv(i, self.LEVEL), expected_level, self.LEVEL_TOLERANCE)

    @skip_if_recsim("No dynamic behaviour recsim")
    def test_GIVEN_ilm_200_WHEN_is_cycling_THEN_channel_levels_change_over_time(self):
        self._lewis.backdoor_set_on_device("cycle", True)
        for i in self.channel_range():
            def not_equal(a, b):
                tolerance = self.LEVEL_TOLERANCE
                return abs(a-b)/(a+b+tolerance) > tolerance
            self.ca.assert_that_pv_value_over_time_satisfies_comparator(self.ch_pv(i, self.LEVEL), 2 * Ilm200Tests.DEFAULT_SCAN_RATE, not_equal)

    def test_GIVEN_ilm200_channel_WHEN_rate_change_requested_THEN_rate_changed(self):
        for i in self.channel_range():
            initial_rate = self.ca.get_pv_value(self.ch_pv(i, self.RATE))
            alternate_rate = self.SLOW if initial_rate == self.FAST else self.SLOW

            self.ca.assert_setting_setpoint_sets_readback(alternate_rate, self.ch_pv(i, self.RATE))
            self.ca.assert_setting_setpoint_sets_readback(initial_rate, self.ch_pv(i, self.RATE))

    def test_GIVEN_ilm200_channel_WHEN_rate_set_to_current_value_THEN_rate_unchanged(self):
        for i in self.channel_range():
            self.ca.assert_setting_setpoint_sets_readback(self.ca.get_pv_value(self.ch_pv(i, self.RATE)),
                                                          self.ch_pv(i, self.RATE))

    @skip_if_recsim("Cannot do back door of dynamic behaviour in recsim")
    def test_GIVEN_ilm200_WHEN_channel_full_THEN_not_filling_and_not_low(self):
        for i in self.channel_range():
            level = self.FULL
            self.set_level_via_backdoor(i, level)
            self.check_state(i, level, False, False)

    @skip_if_recsim("Cannot do back door of dynamic behaviour in recsim")
    def test_GIVEN_ilm200_WHEN_channel_low_but_auto_fill_not_triggered_THEN_not_filling_and_low(self):
        for i in self.channel_range():
            level = self.LOW - (self.LOW - self.FILL)/2  # Somewhere between fill and low
            self.set_level_via_backdoor(i, level)
            self.check_state(i, level, False, True)

    @skip_if_recsim("Cannot do back door of dynamic behaviour in recsim")
    def test_GIVEN_ilm200_WHEN_channel_low_but_and_auto_fill_triggered_THEN_filling_and_low(self):
        for i in self.channel_range():
            level = self.FILL/2
            self.set_level_via_backdoor(i, level)
            self.check_state(i, level, True, True)

    @skip_if_recsim("Cannot do back door of dynamic behaviour in recsim")
    def test_GIVEN_ilm200_WHEN_channel_low_THEN_alarm(self):
        for i in self.channel_range():
            level = self.FILL/2
            self.set_level_via_backdoor(i, level)
            self.ca.assert_that_pv_alarm_is(self.ch_pv(i, "LOW"), self.ca.Alarms.MINOR)

    @skip_if_recsim("Cannot do back door in recsim")
    def test_GIVEN_helium_channel_WHEN_helium_current_set_on_THEN_ioc_reports_current(self):
        for i in self.helium_channels():
            self.set_helium_current_via_backdoor(i, True)
            self.ca.assert_that_pv_is(self.ch_pv(i, self.CURRENT), "On")

    @skip_if_recsim("Cannot do back door in recsim")
    def test_GIVEN_helium_channel_WHEN_helium_current_set_off_THEN_ioc_reports_no_current(self):
        for i in self.helium_channels():
            self.set_helium_current_via_backdoor(i, False)
            self.ca.assert_that_pv_is(self.ch_pv(i, self.CURRENT), "Off")

    @skip_if_recsim("cannot do back door in recsim")
    def test_GIVEN_not_in_use_channel_THEN_being_in_neither_fast_nor_slow_mode_does_not_cause_alarm(self):
        self._lewis.backdoor_run_function_on_device("set_cryo_type", (1, Ilm200ChannelTypes.NOT_IN_USE))

        # Assert in neither fast nor slow mode
        self.ca.assert_that_pv_is(self.ch_pv(1, "STAT:RAW.B1"), "0")
        self.ca.assert_that_pv_is(self.ch_pv(1, "STAT:RAW.B2"), "0")

        # Assert that this does not cause an alarm
        self.ca.assert_that_pv_alarm_is(self.ch_pv(1, "RATE:ASSERT"), self.ca.Alarms.NONE)

    @skip_if_recsim("no backdoor in recsim")
    def test_GIVEN_level_reading_is_below_threshold_THEN_goes_into_alarm(self):
        for channel in self.channel_range():
            self.set_level_via_backdoor(channel, ALARM_THRESHOLDS[channel] + 0.1)
            self.ca.assert_that_pv_alarm_is(self.ch_pv(channel, "LEVEL"), self.ca.Alarms.NONE)

            self.set_level_via_backdoor(channel, ALARM_THRESHOLDS[channel] - 0.1)
            self.ca.assert_that_pv_alarm_is(self.ch_pv(channel, "LEVEL"), self.ca.Alarms.MAJOR)
コード例 #3
0
class GemorcTests(unittest.TestCase):
    """
    Tests for the Gemorc IOC.
    """
    def reset_emulator(self):
        self._lewis.backdoor_set_on_device("reset", True)
        sleep(
            1
        )  # Wait for reset to finish so we don't jump the gun. No external indicator from emulator

    def reset_ioc(self):
        self.ca.set_pv_value("RESET", 1)
        # INIT:ONCE is a property held exclusively in the IOC
        calc_pv = "INIT:ONCE:CALC.CALC"
        original_calc = self.ca.get_pv_value(calc_pv)
        self.ca.set_pv_value(calc_pv, "0")
        self.ca.assert_that_pv_is("INIT:ONCE", "No")
        self.ca.set_pv_value(calc_pv, original_calc)

    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            "gemorc", DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX,
                                default_timeout=DEFAULT_TIMEOUT)
        self.ca.assert_that_pv_exists("ID", timeout=30)
        self.reset_ioc()
        if not IOCRegister.uses_rec_sim:
            self.reset_emulator()
            self.check_init_state(False, False, True, False)
            self.ca.assert_that_pv_is_number("CYCLES", 0)

    def check_init_state(self, initialising, initialised,
                         initialisation_required, oscillating):
        def bi_to_bool(val):
            return val == "Yes"

        # Do all states at once.
        match = False
        total_time = 0.0
        max_wait = DEFAULT_TIMEOUT
        interval = 1.0

        actual_initialising = None
        actual_initialised = None
        actual_initialisation_required = None
        actual_oscillating = None

        while not match and total_time < max_wait:
            actual_initialising = bi_to_bool(
                self.ca.get_pv_value("INIT:PROGRESS"))
            actual_initialised = bi_to_bool(self.ca.get_pv_value("INIT:DONE"))
            actual_initialisation_required = bi_to_bool(
                self.ca.get_pv_value("INIT:REQUIRED"))
            actual_oscillating = bi_to_bool(self.ca.get_pv_value("STAT:OSC"))

            match = all([
                initialising == actual_initialising,
                initialised == actual_initialised,
                initialisation_required == actual_initialisation_required,
                oscillating == actual_oscillating
            ])

            total_time += interval
            sleep(interval)

        try:
            self.assertTrue(match)
        except AssertionError:
            message_format = "State did not match the required state (initialising, initialised, initialisation " \
                             "required, oscillating)\nExpected: ({}, {}, {}, {})\nActual: ({}, {}, {}, {})"
            self.fail(
                message_format.format(initialising, initialised,
                                      initialisation_required, oscillating,
                                      actual_initialising, actual_initialised,
                                      actual_initialisation_required,
                                      actual_oscillating))

    def initialise(self):
        self.ca.set_pv_value("INIT", 1)
        self.ca.assert_that_pv_is("INIT:DONE", "Yes", timeout=10)

    def start_oscillating(self):
        self.initialise()
        self.ca.set_pv_value("START", 1)

    def wait_for_re_initialisation_required(self, interval=10):
        self.ca.set_pv_value("INIT:OPT", interval)
        self.start_oscillating()
        while self.ca.get_pv_value("CYCLES") < interval:
            sleep(1)

    @staticmethod
    def backlash(speed, acceleration):
        return int(0.5 * speed**2 / float(acceleration))

    @staticmethod
    def utility(width, backlash):
        return width / float(width + backlash) * 100.0

    @staticmethod
    def period(width, backlash, speed):
        return 2.0 * (width + backlash) / float(speed)

    @staticmethod
    def frequency(width, backlash, speed):
        return 1.0 / GemorcTests.period(width, backlash, speed)

    def set_and_confirm_state(self,
                              width=None,
                              speed=None,
                              acceleration=None,
                              offset=None):
        pv_value_pairs = [("WIDTH", width), ("SPEED", speed),
                          ("ACC", acceleration), ("OFFSET", offset)]
        filtered_pv_values = [(pv, value) for pv, value in pv_value_pairs
                              if value is not None]
        for pv, value in filtered_pv_values:
            self.ca.set_pv_value("{}:SP".format(pv), value)
        # Do all sets then all confirms to reduce wait time
        for pv, value in filtered_pv_values:
            self.ca.assert_that_pv_is_number(pv, value)

    def test_WHEN_width_setpoint_set_THEN_local_readback_matches(self):
        self.ca.assert_setting_setpoint_sets_readback(DEFAULT_WIDTH + 1,
                                                      "WIDTH:SP:RBV",
                                                      "WIDTH:SP")

    def test_WHEN_width_setpoint_set_THEN_remote_readback_matches(self):
        self.ca.assert_setting_setpoint_sets_readback(DEFAULT_WIDTH + 1,
                                                      "WIDTH")

    def test_WHEN_speed_setpoint_set_THEN_local_readback_matches(self):
        self.ca.assert_setting_setpoint_sets_readback(DEFAULT_SPEED + 1,
                                                      "SPEED:SP:RBV",
                                                      "SPEED:SP")

    def test_WHEN_speed_setpoint_set_THEN_remote_readback_matches(self):
        self.ca.assert_setting_setpoint_sets_readback(DEFAULT_SPEED + 1,
                                                      "SPEED")

    def test_WHEN_acceleration_setpoint_set_THEN_local_readback_matches(self):
        self.ca.assert_setting_setpoint_sets_readback(DEFAULT_ACCELERATION + 1,
                                                      "ACC:SP:RBV", "ACC:SP")

    def test_WHEN_acceleration_setpoint_set_THEN_remote_readback_matches(self):
        self.ca.assert_setting_setpoint_sets_readback(DEFAULT_ACCELERATION + 1,
                                                      "ACC")

    def test_WHEN_offset_setpoint_set_THEN_local_readback_matches(self):
        self.ca.assert_setting_setpoint_sets_readback(DEFAULT_OFFSET + 1,
                                                      "OFFSET:SP:RBV",
                                                      "OFFSET:SP")

    def test_WHEN_offset_setpoint_set_THEN_remote_readback_matches(self):
        self.ca.assert_setting_setpoint_sets_readback(DEFAULT_OFFSET + 1,
                                                      "OFFSET")

    def test_WHEN_offset_setpoint_set_to_negative_value_THEN_remote_readback_matches(
            self):
        self.ca.assert_setting_setpoint_sets_readback(-DEFAULT_OFFSET,
                                                      "OFFSET")

    def test_WHEN_device_first_started_THEN_initialisation_required(self):
        self.check_init_state(initialising=False,
                              initialised=False,
                              initialisation_required=True,
                              oscillating=False)

    @skip_if_recsim("Device reset requires Lewis backdoor")
    def test_GIVEN_starting_state_WHEN_initialisation_requested_THEN_initialising_becomes_true(
            self):
        self.ca.set_pv_value("INIT", 1)
        self.check_init_state(initialising=True,
                              initialised=False,
                              initialisation_required=False,
                              oscillating=False)

    @skip_if_recsim("Device reset requires Lewis backdoor")
    def test_GIVEN_starting_state_WHEN_initialisation_requested_THEN_becomes_initialised_when_no_longer_in_progress(
            self):
        self.ca.set_pv_value("INIT", 1)

        total_wait = 0
        max_wait = DEFAULT_TIMEOUT
        interval = 1
        initialisation_complete = self.ca.get_pv_value("INIT:DONE")
        while self.ca.get_pv_value(
                "INIT:PROGRESS") == "Yes" and total_wait < max_wait:
            # Always check value from before we confirmed initialisation was in progress to avoid race conditions
            self.assertNotEqual(initialisation_complete, 1)
            sleep(interval)
            total_wait += interval
            initialisation_complete = self.ca.get_pv_value("INIT:DONE")
        self.check_init_state(initialising=False,
                              initialised=True,
                              initialisation_required=False,
                              oscillating=False)

    @skip_if_recsim("Device reset requires Lewis backdoor")
    def test_GIVEN_initialised_WHEN_oscillation_requested_THEN_reports_oscillating(
            self):
        self.start_oscillating()
        self.ca.assert_that_pv_is("STAT:OSC", "Yes")

    @skip_if_recsim("Device reset requires Lewis backdoor")
    def test_GIVEN_initialised_WHEN_oscillation_requested_THEN_complete_cycles_increases(
            self):
        self.start_oscillating()
        self.ca.assert_that_pv_value_is_increasing("CYCLES", DEFAULT_TIMEOUT)

    @skip_if_recsim("Device reset requires Lewis backdoor")
    def test_GIVEN_oscillating_WHEN_oscillation_stopped_THEN_reports_not_oscillating(
            self):
        self.start_oscillating()
        self.ca.set_pv_value("STOP", 1)
        self.ca.assert_that_pv_is("STAT:OSC", "No")

    @skip_if_recsim("Device reset requires Lewis backdoor")
    def test_GIVEN_initialised_WHEN_oscillation_requested_THEN_complete_cycles_does_not_change(
            self):
        self.start_oscillating()
        self.ca.set_pv_value("STOP", 1)
        self.ca.assert_that_pv_value_is_unchanged("CYCLES", DEFAULT_TIMEOUT)

    @skip_if_recsim("Device reset requires Lewis backdoor")
    def test_GIVEN_oscillating_WHEN_initialisation_requested_THEN_initialises(
            self):
        self.start_oscillating()
        self.ca.set_pv_value("INIT", 1)
        self.check_init_state(initialising=True,
                              initialised=False,
                              initialisation_required=False,
                              oscillating=False)

    @skip_if_recsim("Device reset requires Lewis backdoor")
    def test_GIVEN_oscillating_and_initialisation_requested_WHEN_initialisation_complete_THEN_resumes_oscillation(
            self):
        self.start_oscillating()
        self.initialise()
        self.check_init_state(initialising=False,
                              initialised=True,
                              initialisation_required=False,
                              oscillating=True)

    def test_WHEN_settings_reset_requested_THEN_settings_return_to_default_values(
            self):
        settings = (
            ("WIDTH", DEFAULT_WIDTH),
            ("ACC", DEFAULT_ACCELERATION),
            ("SPEED", DEFAULT_SPEED),
            ("OFFSET", DEFAULT_OFFSET),
            ("INIT:AUTO", DEFAULT_AUTO_INITIALISE),
            ("INIT:OPT", DEFAULT_OPT_INITIALISE),
        )
        for pv, default in settings:
            self.ca.set_pv_value("{}:SP".format(pv),
                                 default + 1)  # I prefer the two lines here
            self.ca.assert_that_pv_is_not_number(pv, default)

        self.ca.set_pv_value("RESET", 1)

        for pv, default in settings:
            self.ca.assert_that_pv_is_number(pv, default)

    @skip_if_recsim("ID is emulator specific")
    def test_WHEN_device_is_running_THEN_it_gets_PnP_identity_from_emulator(
            self):
        self.ca.assert_that_pv_is(
            "ID",
            "0002 0001 ISIS Gem Oscillating Rotary Collimator (IBEX EMULATOR)",
            timeout=20)  # On a very slow scan

    def test_GIVEN_standard_test_cases_WHEN_backlash_calculated_locally_THEN_result_is_in_range_supported_by_device(
            self):
        for _, speed, acceleration in SETTINGS_TEST_CASES:
            self.assertTrue(0 <= self.backlash(speed, acceleration) <= 999)

    @skip_if_recsim("Depends on emulator value")
    def test_WHEN_emulator_running_THEN_backlash_has_value_derived_from_speed_and_acceleration(
            self):
        for width, speed, acceleration in SETTINGS_TEST_CASES:
            self.set_and_confirm_state(speed=speed, acceleration=acceleration)
            self.ca.assert_that_pv_is_number(
                "BACKLASH", self.backlash(speed, acceleration))

    def test_GIVEN_non_zero_speed_WHEN_width_and_speed_set_THEN_utility_time_corresponds_to_formula_in_test(
            self):
        for width, speed, acceleration in SETTINGS_TEST_CASES:
            self.set_and_confirm_state(width, speed, acceleration)
            backlash = self.ca.get_pv_value("BACKLASH")
            self.ca.assert_that_pv_is_number("UTILITY",
                                             self.utility(width, backlash),
                                             tolerance=DEFAULT_TOLERANCE)

    def test_WHEN_emulator_running_THEN_period_has_value_as_derived_from_speed_width_and_backlash(
            self):
        for width, speed, acceleration in SETTINGS_TEST_CASES:
            self.set_and_confirm_state(width, speed, acceleration)
            backlash = self.ca.get_pv_value("BACKLASH")
            self.ca.assert_that_pv_is_number("PERIOD",
                                             self.period(
                                                 width, backlash, speed),
                                             tolerance=DEFAULT_TOLERANCE)

    def test_WHEN_emulator_running_THEN_frequency_has_value_as_derived_from_speed_width_and_backlash(
            self):
        for width, speed, acceleration in SETTINGS_TEST_CASES:
            self.set_and_confirm_state(width, speed, acceleration)
            backlash = self.ca.get_pv_value("BACKLASH")
            self.ca.assert_that_pv_is_number("FREQ",
                                             self.frequency(
                                                 width, backlash, speed),
                                             tolerance=DEFAULT_TOLERANCE)

    @skip_if_recsim("This behaviour not implemented in recsim")
    def test_GIVEN_non_zero_offset_WHEN_re_zeroed_to_datum_THEN_offset_is_zero(
            self):
        self.ca.assert_setting_setpoint_sets_readback(DEFAULT_OFFSET + 1,
                                                      "OFFSET", "OFFSET:SP")
        self.ca.assert_that_pv_is_not_number("OFFSET", 0)
        self.ca.set_pv_value("ZERO", 1)
        self.ca.assert_that_pv_is_number("OFFSET", 0)

    def test_WHEN_auto_initialisation_interval_set_THEN_readback_matches_set_value(
            self):
        self.ca.assert_setting_setpoint_sets_readback(
            DEFAULT_AUTO_INITIALISE + 1, "INIT:AUTO")

    def test_WHEN_opt_initialisation_interval_set_THEN_readback_matches_set_value(
            self):
        self.ca.assert_setting_setpoint_sets_readback(
            DEFAULT_OPT_INITIALISE + 1, "INIT:OPT")

    @skip_if_recsim("Cycle counting not performed in Recsim")
    def test_GIVEN_oscillating_WHEN_number_of_cycles_exceeds_optional_init_interval_THEN_initialisation_required(
            self):
        self.wait_for_re_initialisation_required()
        self.check_init_state(False, True, True, True)

    @skip_if_recsim("Cycle counting not performed in Recsim")
    def test_GIVEN_initialisation_required_after_oscillating_WHEN_reinitialised_THEN_re_initialisation_not_required(
            self):
        self.wait_for_re_initialisation_required()
        self.ca.set_pv_value("INIT:OPT", DEFAULT_OPT_INITIALISE)
        self.initialise()
        self.check_init_state(False, True, False, True)

    @skip_if_recsim("Initialisation logic not performed in Recsim")
    def test_WHEN_device_initialised_THEN_initialised_once(self):
        self.initialise()
        self.ca.assert_that_pv_is("INIT:ONCE", "Yes")

    @skip_if_recsim("Initialisation logic not performed in Recsim")
    def test_WHEN_oscillating_THEN_initialised_once(self):
        self.start_oscillating()
        self.ca.assert_that_pv_is("INIT:ONCE", "Yes")

    @skip_if_recsim("Initialisation logic not performed in Recsim")
    def test_WHEN_oscillating_and_initialisation_required_THEN_initialised_once(
            self):
        self.wait_for_re_initialisation_required()
        self.ca.assert_that_pv_is("INIT:ONCE", "Yes")

    @skip_if_recsim("Initialisation logic not performed in Recsim")
    def test_WHEN_reinitialising_THEN_initialised_once(self):
        self.wait_for_re_initialisation_required()
        self.ca.set_pv_value("INIT", 1)
        self.ca.assert_that_pv_is("INIT:ONCE", "Yes")

    @skip_if_recsim("Initialisation logic not performed in Recsim")
    def test_WHEN_reinitialised_THEN_initialised_once(self):
        self.wait_for_re_initialisation_required()
        self.initialise()
        self.ca.assert_that_pv_is("INIT:ONCE", "Yes")

    @skip_if_recsim("Initialisation logic not performed in Recsim")
    def test_GIVEN_oscillating_WHEN_stopped_and_immediately_initialised_THEN_number_of_cycles_goes_to_zero(
            self):
        self.start_oscillating()
        self.ca.set_pv_value("STOP", 1)
        self.ca.set_pv_value("INIT", 1)
        self.ca.assert_that_pv_is_number("CYCLES", 0)

    @skip_if_recsim("Initialisation logic not performed in Recsim")
    def test_WHEN_oscillating_THEN_auto_reinitialisation_triggers_after_counter_reaches_auto_trigger_value(
            self):
        initialisation_interval = 100
        initial_status_string = "Sequence not run since IOC startup"
        self.ca.set_pv_value("INIT:AUTO", initialisation_interval)
        self.start_oscillating()
        while self.ca.get_pv_value("CYCLES") < initialisation_interval:
            self.ca.assert_that_pv_is("INIT:PROGRESS", "No")
            self.ca.assert_that_pv_is("INIT:STAT", initial_status_string)
            sleep(1)
        self.ca.assert_that_pv_is_not("INIT:STAT", initial_status_string)
        self.ca.assert_that_pv_is(
            "STAT:OSC", "No",
            timeout=10)  # Initialisation seq has a 5s wait at the start
コード例 #4
0
class Aldn1000Tests(unittest.TestCase):
    """
    Tests for the Aldn1000 IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            DEVICE_NAME, DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX)
        self._lewis.backdoor_run_function_on_device("reset")

    @parameterized.expand([('Value 1', 12.12), ('Value 2', 1.123),
                           ('Value 3', 123.0)])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_new_diameter_WHEN_set_diameter_THEN_new_diameter_set(
            self, _, value):
        expected_diameter = value
        self.ca.set_pv_value("DIAMETER:SP", expected_diameter)

        self.ca.assert_that_pv_is("DIAMETER", expected_diameter, timeout=2)

    @parameterized.expand([('Value 1', 12345), ('Value 2', 1234),
                           ('Value 3', 77424)])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_new_invalid_high_diameter_WHEN_set_diameter_THEN_diameter_set_limit_returned(
            self, _, value):
        invalid_diameter = value
        expected_diameter = 1000.00
        self.ca.set_pv_value("DIAMETER:SP", invalid_diameter)

        self.ca.assert_that_pv_is("DIAMETER", expected_diameter, timeout=2)

    @parameterized.expand([('Value 1', -2345), ('Value 2', -1234),
                           ('Value 3', -676424)])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_new_invalid_low_diameter_WHEN_set_diameter_THEN_diameter_set_limit_returned(
            self, _, value):
        invalid_diameter = value
        expected_diameter = 0.00
        self.ca.set_pv_value("DIAMETER:SP", invalid_diameter)

        self.ca.assert_that_pv_is("DIAMETER", expected_diameter, timeout=2)

    @parameterized.expand([('Value 1', 14.1), ('Value 2', 24.23),
                           ('Value 3', 30)])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_new_diameter_above_14mm_WHEN_set_diameter_THEN_volume_units_changed(
            self, _, value):
        set_diameter = value
        expected_units = 'mL'
        self.ca.set_pv_value("DIAMETER:SP", set_diameter)

        self.ca.assert_that_pv_is("VOLUME:UNITS", expected_units, timeout=2)

    @parameterized.expand([('Value 1', 14.0), ('Value 2', 10.0),
                           ('Value 3', 5.0)])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_new_diameter_below_or_eq_14mm_WHEN_set_diameter_THEN_volume_units_changed(
            self, _, value):
        set_diameter = value
        expected_units = 'uL'
        self.ca.set_pv_value("DIAMETER:SP", set_diameter)

        self.ca.assert_that_pv_is("VOLUME:UNITS", expected_units, timeout=2)

    @parameterized.expand([('Value 1', 0.123), ('Value 2', 1.342),
                           ('Value 3', 12.34)])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_new_volume_WHEN_set_volume_THEN_new_volume_set(
            self, _, value):
        expected_volume = value
        self.ca.set_pv_value("VOLUME:SP", expected_volume)

        self.ca.assert_that_pv_is("VOLUME", expected_volume, timeout=2)

    @parameterized.expand([('Direction 1', 'Withdraw'),
                           ('Direction 2', 'Infuse')])
    def test_GIVEN_new_direction_WHEN_set_direction_THEN_new_direction_set(
            self, _, direction):
        expected_direction = direction
        self.ca.set_pv_value("DIRECTION:SP", expected_direction)

        self.ca.assert_that_pv_is("DIRECTION", expected_direction)

    @parameterized.expand([('Direction 1', 'Withdraw'),
                           ('Direction 2', 'Infuse')])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_direction_WHEN_set_reverse_direction_THEN_direction_reversed(
            self, _, direction):
        initial_direction = direction
        if initial_direction == 'Infuse':
            expected_direction = 'Withdraw'
        else:
            expected_direction = 'Infuse'
        self.ca.set_pv_value("DIRECTION:SP", direction)
        self.ca.assert_that_pv_is("DIRECTION", direction, timeout=2)
        self.ca.set_pv_value("DIRECTION:SP", 'Reverse')

        self.ca.assert_that_pv_is("DIRECTION", expected_direction, timeout=2)

    @parameterized.expand([('Value 1', 0.123), ('Value 2', 1.342),
                           ('Value 3', 12.34)])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_new_rate_WHEN_set_rate_THEN_new_rate_set(self, _, value):
        expected_rate = value
        self.ca.set_pv_value("RATE:SP", expected_rate)

        self.ca.assert_that_pv_is("RATE", expected_rate)

    @parameterized.expand([('Value 1', 2123), ('Value 2', 1411.342),
                           ('Value 3', 1222.34)])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_new_invalid_high_rate_WHEN_set_rate_THEN_rate_high_limit_set(
            self, _, value):
        invalid_rate = value
        expected_rate = 1000.0
        self.ca.set_pv_value("RATE:SP", invalid_rate)

        self.ca.assert_that_pv_is("RATE", expected_rate)

    @parameterized.expand([('Value 1', -9085), ('Value 2', -0.123342),
                           ('Value 3', -5226.31234)])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_new_invalid_low_rate_WHEN_set_rate_THEN_rate_high_limit_set(
            self, _, value):
        invalid_rate = value
        expected_rate = 0.0
        self.ca.set_pv_value("RATE:SP", invalid_rate)

        self.ca.assert_that_pv_is("RATE", expected_rate)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_an_infused_volume_WHEN_get_volume_dispensed_THEN_infused_volume_returned(
            self):
        expected_infusion_volume = 1.123
        self._lewis.backdoor_set_on_device("volume_infused",
                                           expected_infusion_volume)

        self.ca.assert_that_pv_is("VOLUME:INF",
                                  expected_infusion_volume,
                                  timeout=2)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_infusion_volume_dispensed_WHEN_clear_infused_volume_dispensed_THEN_volume_cleared(
            self):
        infused_volume_dispensed = 2.342
        expected_volume_dispensed = 0.0
        self._lewis.backdoor_set_on_device("volume_infused",
                                           infused_volume_dispensed)
        self.ca.assert_that_pv_is("VOLUME:INF",
                                  infused_volume_dispensed,
                                  timeout=2)
        self.ca.set_pv_value("VOLUME:INF:CLEAR:SP", "CLEAR")

        self.ca.assert_that_pv_is("VOLUME:INF", expected_volume_dispensed)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_withdrawn_volume_dispensed_WHEN_clear_withdrawn_volume_dispensed_THEN_volume_cleared(
            self):
        withdrawn_volume_dispensed = 93.12
        expected_volume_dispensed = 0.0
        self._lewis.backdoor_set_on_device("volume_withdrawn",
                                           withdrawn_volume_dispensed)
        self.ca.assert_that_pv_is("VOLUME:WDR",
                                  withdrawn_volume_dispensed,
                                  timeout=2)
        self.ca.set_pv_value("VOLUME:WDR:CLEAR:SP", "CLEAR")

        self.ca.assert_that_pv_is("VOLUME:WDR", expected_volume_dispensed)

    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_pump_off_WHEN_set_pump_on_THEN_pump_turned_on(self):
        status_mode = 'Pumping Program Stopped'
        self.ca.set_pv_value("VOLUME:SP", 1.0)
        self.ca.set_pv_value("RUN:SP", "Run")

        self.ca.assert_that_pv_is_not("STATUS", status_mode)

    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_pump_on_WHEN_set_pump_off_THEN_pump_paused(self):
        status_mode = "Infusing"
        expected_status_mode = "Pumping Program Paused"
        self.ca.set_pv_value("VOLUME:SP", 100.00)
        self.ca.set_pv_value("DIRECTION:SP", "Infuse")
        self.ca.set_pv_value("RUN:SP", "Run")
        self.ca.assert_that_pv_is("STATUS", status_mode, timeout=2)

        self.ca.set_pv_value("STOP:SP", "Stop")

        self.ca.assert_that_pv_is("STATUS", expected_status_mode)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_given_program_function_WHEN_program_function_changed_THEN_program_function_updated(
            self):
        expected_function = 'INCR'
        self._lewis.backdoor_set_on_device('program_function',
                                           expected_function)

        self.ca.assert_that_pv_is("PROGRAM:FUNCTION",
                                  expected_function,
                                  timeout=2)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_an_input_error_WHEN_open_file_THEN_file_error_str_returned(
            self):
        self._lewis.backdoor_set_on_device("input_correct", False)
        expected_value = "Command N/A currently"
        expected_diameter = 1.123
        self.ca.set_pv_value("DIAMETER:SP", expected_diameter)

        self.ca.assert_that_pv_is("ERROR", expected_value, timeout=2)

    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_device_connected_WHEN_get_status_THEN_device_status_returned(
            self):
        expected_status = 'Pumping Program Stopped'

        self.ca.assert_that_pv_is("STATUS", expected_status, timeout=2)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_device_not_connected_WHEN_get_error_THEN_alarm(self):
        self._lewis.backdoor_set_on_device('connected', False)
        self.ca.set_pv_value("STATUS.PROC", 1)
        self.ca.assert_that_pv_alarm_is('STATUS',
                                        ChannelAccess.Alarms.INVALID,
                                        timeout=5)

    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_pump_infusing_WHEN_pump_on_THEN_infused_volume_dispensed_increases(
            self):
        self.ca.set_pv_value("VOLUME:SP", 10.00)
        self.ca.set_pv_value("RATE:SP", 0.50)
        self.ca.set_pv_value("RATE:UNITS:SP", "uL/min")
        self.ca.set_pv_value("DIRECTION:SP", "Infuse")
        self.ca.set_pv_value("VOLUME:INF:CLEAR:SP", "CLEAR")
        self.ca.set_pv_value("RUN:SP", "Run")

        self.ca.assert_that_pv_is_not("VOLUME:INF", 0.0, timeout=0.0)
        self.ca.set_pv_value("STOP:SP", "Stop")

    @parameterized.expand([("Low limit", 1.0, "uL"),
                           ("High limit", 15.0, "mL")])
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_diameter_change_WHEN_new_diamater_causes_units_changed_THEN_volume_EGU_units_updated(
            self, _, value, units):
        expected_units = units
        self.ca.set_pv_value("DIAMETER:SP", value)

        self.ca.assert_that_pv_is("VOLUME.EGU", expected_units)

    @parameterized.expand(("uL/min", "mL/min", "uL/min", "mL/hr"))
    @skip_if_recsim("Requires emulator logic so not supported in RECSIM")
    def test_GIVEN_rate_change_WHEN_new_rate_units_THEN_rate_EGU_units_updated(
            self, units):
        expected_units = units
        self.ca.set_pv_value("RATE:UNITS:SP", expected_units)
        self.ca.set_pv_value("RATE:SP.PROC", 1)

        self.ca.assert_that_pv_is("RATE.EGU", expected_units)
コード例 #5
0
class AstriumTests(unittest.TestCase):
    """
    Tests for the Astrium Chopper.
    """
    def setUp(self):
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX,
                                default_timeout=30)

    @parameterized.expand(parameterized_list(VALID_FREQUENCIES))
    def test_that_WHEN_setting_the_frequency_setpoint_THEN_it_is_set(
            self, _, value):
        self.ca.set_pv_value("CH1:FREQ:SP", value)
        self.ca.assert_that_pv_is("CH1:FREQ", value)

    @parameterized.expand(parameterized_list(VALID_PHASE_DELAYS))
    def test_that_WHEN_setting_the_phase_setpoint_THEN_it_is_set(
            self, _, value):
        self.ca.set_pv_value("CH1:PHASE:SP", value)
        self.ca.assert_that_pv_is("CH1:PHASE", value)

    @parameterized.expand(parameterized_list(VALID_PHASE_DELAYS))
    @skip_if_recsim("Behaviour of phase readback not implemented in recsim")
    def test_that_WHEN_setting_the_phase_setpoint_and_then_speed_THEN_phases_to_the_correct_place(
            self, _, value):
        """
        This test simulates the bug in https://github.com/ISISComputingGroup/IBEX/issues/4123
        """

        # Arrange - set initial speed and phase
        old_speed = 10
        self.ca.set_pv_value("CH1:FREQ:SP", old_speed)
        self.ca.assert_that_pv_is_number("CH1:FREQ",
                                         old_speed)  # Wait for it to get there
        self.ca.set_pv_value("CH1:PHASE:SP", value)
        self.ca.assert_that_pv_is_number("CH1:PHASE", value)
        self.ca.assert_that_pv_is_number("CH1:PHASE:SP:RBV", value)

        # Act - set frequency
        new_speed = 20
        self.ca.set_pv_value("CH1:FREQ:SP", new_speed)
        self.ca.assert_that_pv_is_number("CH1:FREQ",
                                         new_speed)  # Wait for it to get there

        # Assert - both the actual phase and the setpoint readback should be correct after setting speed.
        self.ca.assert_that_pv_is_number("CH1:PHASE", value)
        self.ca.assert_that_pv_value_is_unchanged("CH1:PHASE", wait=10)
        self.ca.assert_that_pv_is_number("CH1:PHASE:SP:RBV", value)
        self.ca.assert_that_pv_value_is_unchanged("CH1:PHASE:SP:RBV", wait=10)

    def test_WHEN_frequency_set_to_180_THEN_actual_setpoint_not_updated(self):
        sent_frequency = 180
        self.ca.set_pv_value("CH1:FREQ:SP", sent_frequency)
        self.ca.assert_that_pv_is_not("CH1:FREQ:SP_ACTUAL", sent_frequency)
        self.ca.assert_that_pv_is_not("CH1:FREQ", sent_frequency)

    @skip_if_recsim("No state changes in recsim")
    def test_WHEN_brake_called_THEN_state_is_BRAKE(self):
        self.ca.set_pv_value("CH1:BRAKE", 1)
        self.ca.assert_that_pv_is("CH1:STATE", "BRAKE")

    @skip_if_recsim("No state changes in recsim")
    def test_WHEN_speed_set_THEN_state_is_POSITION(self):
        self.ca.set_pv_value("CH1:FREQ:SP", 10)
        self.ca.assert_that_pv_is("CH1:STATE", "POSITION")

    @skip_if_devsim("No backdoor to state in devsim")
    def test_WHEN_one_channel_state_not_ESTOP_THEN_calibration_disabled(self):
        self.ca.set_pv_value("CH1:SIM:STATE", "NOT_ESTOP")
        self.ca.set_pv_value("CH2:SIM:STATE", "E_STOP")
        # Need to process to update disabled status.
        # We don't want the db to process when disabled changes as this will write to the device.
        self.ca.process_pv("CALIB")
        self.ca.assert_that_pv_is("CALIB.STAT",
                                  self.ca.Alarms.DISABLE,
                                  timeout=1)

    @skip_if_devsim("No backdoor to state in devsim")
    def test_WHEN_both_channels_state_ESTOP_THEN_calibration_enabled(self):
        self.ca.set_pv_value("CH1:SIM:STATE", "E_STOP")
        self.ca.set_pv_value("CH2:SIM:STATE", "E_STOP")
        # Need to process to update disabled status.
        # We don't want the db to process when disabled changes as this will write to the device.
        self.ca.process_pv("CALIB")
        self.ca.assert_that_pv_is_not("CALIB.STAT",
                                      self.ca.Alarms.DISABLE,
                                      timeout=1)
コード例 #6
0
class ReadasciiTests(unittest.TestCase):
    """
    Tests for ReadASCII
    """
    def _write_contents_of_temporary_test_file(self, data):
        """
        Writes the given data to the temporary test file.

        This method assumes that the test directory exists (e.g. it has been created by _generate_temporary_test_file)

        :param data: a list of 5-element tuples (setpoint, p, i, d, heater_range)
        """
        with open(
                os.path.join(TEMP_TEST_SETTINGS_DIR, TEMP_SETTINGS_FILE_NAME),
                "w") as f:
            f.write("SP P I D HEATER\n")
            for row in data:
                assert len(row) == 5, "Each row should have exactly 5 elements"
                f.write("{}\n".format(" ".join(str(d) for d in row)))
        time.sleep(5)  # allow new file on disk to be noticed

    @contextmanager
    def _generate_temporary_test_file(self, data):
        """
        Context manager which generates a temporary test file containing the given data.
        :param data: a list of 5-element tuples (setpoint, p, i, d, heater_range)
        """
        if not os.path.exists(TEMP_TEST_SETTINGS_DIR):
            os.mkdir(TEMP_TEST_SETTINGS_DIR)

        try:
            self._write_contents_of_temporary_test_file(data)
            yield
        finally:
            shutil.rmtree(TEMP_TEST_SETTINGS_DIR)

    def _set_and_use_file(self, dir, name):
        self.ca.assert_setting_setpoint_sets_readback(dir, "DIRBASE")
        self.ca.assert_setting_setpoint_sets_readback(name, "RAMP_FILE")
        self.ca.set_pv_value("LUTON", 1)

    @contextmanager
    def _use_test_file(self):
        """
        Context manager which sets the ReadASCII file to the temporary test file on entry, and reverts it back to the
        default on exit.

        Need to set the file back to the default one on exit, otherwise the file will be marked as "in use" and
        cannot be deleted properly
        """
        try:
            self._set_and_use_file(TEMP_TEST_SETTINGS_DIR,
                                   TEMP_SETTINGS_FILE_NAME)
            yield
        finally:
            self._set_and_use_file(DEFAULT_SETTINGS_DIR, DEFAULT_SETTINGS_FILE)

    def _set_and_check(self, current_val, p, i, d, output_range):
        self.ca.set_pv_value("CURRENT_VAL", current_val)

        # The LUTON PV is FLNK'ed to by the IOCs that use ReadASCII after the setpoint changes.
        # Here we're not using any particular IOC so have to trigger the processing manually.
        self.ca.assert_that_pv_is("LUTON:RBV", "1")
        self.ca.assert_that_pv_is("LUTON", "1")
        self.ca.process_pv("LUTON")

        self.ca.assert_that_pv_is_number("OUT_P", p, tolerance=TOLERANCE)
        self.ca.assert_that_pv_is_number("OUT_I", i, tolerance=TOLERANCE)
        self.ca.assert_that_pv_is_number("OUT_D", d, tolerance=TOLERANCE)
        self.ca.assert_that_pv_is_number("OUT_MAX",
                                         output_range,
                                         tolerance=TOLERANCE)

    def setUp(self):
        self.ca = ChannelAccess(default_timeout=30,
                                device_prefix=DEVICE_PREFIX)
        self._ioc = IOCRegister.get_running(DEVICE_PREFIX)
        self.ca.assert_that_pv_exists("DIRBASE")
        self._set_ramp_status(False)

    def test_GIVEN_the_test_file_has_entries_for_a_setpoint_WHEN_that_exact_setpoint_is_set_THEN_it_updates_the_pid_pvs_with_the_values_from_the_file(
            self):
        rows = [
            (50, 1, 2, 3, 4),
            (100, 5, 6, 7, 8),
            (150, 9, 10, 11, 12),
        ]

        with self._generate_temporary_test_file(rows), self._use_test_file():
            for row in rows:
                self._set_and_check(*row)

    def test_GIVEN_the_test_file_has_non_integer_pid_entries_for_a_setpoint_WHEN_that_exact_setpoint_is_set_THEN_it_updates_the_pid_pvs_with_the_values_from_the_file(
            self):
        rows = [
            (50, 1.23, 2.7, 3.8, 4.1),
            (100, 5.555, 6.666, 7.777, 8.888),
            (150, 9.1, 10.2, 11.3, 12.4),
        ]

        with self._generate_temporary_test_file(rows), self._use_test_file():
            for row in rows:
                self._set_and_check(*row)

    def test_WHEN_a_setpoint_lower_than_the_minimum_bound_of_the_file_is_set_THEN_the_pid_settings_are_updated_to_be_the_minimum(
            self):
        rows = [
            (50, 1, 2, 3, 4),
            (100, 5, 6, 7, 8),
        ]

        with self._generate_temporary_test_file(rows), self._use_test_file():
            self._set_and_check(20, 1, 2, 3, 4)
            self._set_and_check(120, 5, 6, 7, 8)
            self._set_and_check(20, 1, 2, 3, 4)

    def test_GIVEN_the_test_file_has_entries_for_a_setpoint_WHEN_the_file_is_changed_on_disk_THEN_the_pid_lookup_uses_the_new_values(
            self):
        rows = [
            (50, 1, 2, 3, 4),
            (100, 5, 6, 7, 8),
            (150, 9, 10, 11, 12),
        ]

        with self._generate_temporary_test_file(rows), self._use_test_file():
            for row in rows:
                self._set_and_check(*row)

            new_rows = [[item + 10 for item in row] for row in rows]
            self._write_contents_of_temporary_test_file(new_rows)
            self.assertNotEqual(rows, new_rows)

            for row in new_rows:
                self._set_and_check(*row)

    def _set_ramp_status(self, status):
        self.ca.set_pv_value("RAMPON", status)

    def test_GIVEN_ramping_is_off_WHEN_setting_setpoint_THEN_it_is_sent_to_device_immediately(
            self):
        self._set_ramp_status(False)
        for val in TEST_VALUES:
            self.ca.assert_setting_setpoint_sets_readback(
                val, set_point_pv="VAL:SP", readback_pv="OUT_SP")

    def test_GIVEN_ramping_is_on_WHEN_setting_setpoint_THEN_setpoint_sent_to_the_device_ramps(
            self):
        setpoint_change = 1  # K

        # secs - The test will take at least this long to run but if it's too small may get random timing problems
        # causing the test to fail
        ramp_time = 20

        ramp_rate = setpoint_change * SECONDS_PER_MINUTE / ramp_time  # K per min

        # Ensure ramp is off and setpoint is zero initially
        self._set_ramp_status(False)
        self.ca.set_pv_value("VAL:SP", 0)
        self.ca.assert_that_pv_is("OUT_SP", 0)

        # Set up ramp and set a setpoint so that the ramp starts.
        self.ca.assert_setting_setpoint_sets_readback(ramp_rate, "RATE")
        self._set_ramp_status(True)
        self.ca.set_pv_value("VAL:SP", setpoint_change)

        # Verify that setpoint does not reach final value within first half of ramp time
        self.ca.assert_that_pv_is_not("OUT_SP",
                                      setpoint_change,
                                      timeout=ramp_time / 2)

        # ... But after a further ramp_time, it should have.
        self.ca.assert_that_pv_is("OUT_SP", setpoint_change, timeout=ramp_time)