def test_GIVEN_multiple_channels_WHEN_parse_THEN_have_mulitple_blocks_containing_all_info(
            self):
        expected_names = ["BLOCK1", "block_2", "block3"]
        expected_expected_connectivities = [True, True, False]
        given_values = ["0.001", "hello", "null"]
        given_units = ["mA", "", ""]
        expected_values = ["0.001 mA", "hello", "null"]
        expected_alarm = ["", "MINOR/LOW", ""]
        channels = []
        for name, connectivity, value, units, alarm in zip(
                expected_names, expected_expected_connectivities, given_values,
                given_units, expected_alarm):
            channels.append(
                ArchiveMother.create_channel(name=name,
                                             is_connected=connectivity,
                                             units=units,
                                             alarm=alarm,
                                             value=value))

        json = ArchiveMother.create_info_page(channels)
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result, has_length(len(expected_names)))
        for name, connectivity, expected_value, alarm in zip(
                expected_names, expected_expected_connectivities,
                expected_values, expected_alarm):
            assert_that(result[name].get_name(), is_(name))
            assert_that(result[name].get_alarm(), is_(alarm))
            assert_that(result[name].is_connected(), is_(connectivity))
            assert_that(result[name].get_description()["value"],
                        is_(expected_value))
            assert_that(result[name].get_visibility(), is_(True))
    def test_GIVEN_no_channels_WHEN_parse_THEN_channels_are_blank(self):
        json = ArchiveMother.create_info_page([])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        self.assertEqual(len(result), 0, "Length of result")
    def test_GIVEN_one_invalid_channels_WHEN_parse_THEN_no_blocks_returned(
            self):
        expected_name = "BLOCK"
        json = ArchiveMother.create_info_page([None])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result, has_length(0))
    def test_GIVEN_one_channels_WHEN_parse_THEN_block_is_visible(self):
        expected_name = "BLOCK"
        json = ArchiveMother.create_info_page(
            [ArchiveMother.create_channel(expected_name)])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result[expected_name].get_visibility(), is_(True))
    def test_GIVEN_no_object_WHEN_parse_THEN_exception(self):
        json = {}
        parser = WebPageParser()

        try:
            parser.extract_blocks(json)
            self.fail("Should have thrown exception")
        except BlocksParseError:
            pass
    def test_GIVEN_one_channels_WHEN_parse_THEN_blocks_contain_channel(self):
        expected_name = "BLOCK"
        json = ArchiveMother.create_info_page(
            [ArchiveMother.create_channel(expected_name)])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result, has_length(1))
        assert_that(result[expected_name].name, is_(expected_name))
    def test_GIVEN_one_channels_in_alarm_WHEN_parse_THEN_block_has_alarm(self):
        expected_name = "BLOCK"
        expected_alarm = u"INVALID/UDF_ALARM"
        json = ArchiveMother.create_info_page([
            ArchiveMother.create_channel(name=expected_name,
                                         alarm=expected_alarm)
        ])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result[expected_name].get_alarm(), is_(expected_alarm))
    def test_GIVEN_one_channels_has_value_WHEN_parse_THEN_block_has_value(
            self):
        expected_name = "BLOCK"
        expected_value = u"5.4"
        json = ArchiveMother.create_info_page([
            ArchiveMother.create_channel(name=expected_name,
                                         value=expected_value)
        ])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result[expected_name].get_value(), is_(expected_value))
    def __init__(self, host="localhost", reader=None):
        """
        Initialize.
        Args:
            host: The host of the instrument from which to read the information.
            reader: A reader object to get external information.
        """
        if reader is None:
            self.reader = DataSourceReader(host)
        else:
            self.reader = reader

        self.web_page_parser = WebPageParser()
    def test_GIVEN_one_channels_with_value_with_incorrect_utf8_in_WHEN_parse_THEN_block_has_correct_utf8_in(
            self):
        expected_name = "BLOCK"
        value = u'mu \\u-062\\u-075'
        expected_value = u'mu \u00B5'  # decimal 181
        channel = ArchiveMother.create_channel(name=expected_name, value=value)
        del channel['Current Value']["Units"]
        json = ArchiveMother.create_info_page([channel])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result[expected_name].get_description()["value"],
                    is_(expected_value))
    def test_GIVEN_one_channels_is_disconnected_WHEN_parse_THEN_block_has_value_null(
            self):
        expected_name = "BLOCK"
        expected_connectivity = False
        expected_value = u"null"
        json = ArchiveMother.create_info_page([
            ArchiveMother.create_channel(name=expected_name,
                                         value=expected_value,
                                         is_connected=expected_connectivity)
        ])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result[expected_name].get_value(), is_(expected_value))
    def test_GIVEN_one_channels_is_connected_WHEN_parse_THEN_block_is_connected_and_status_reflects_this(
            self):
        expected_name = "BLOCK"
        expected_connectivity = True
        expected_status = "Connected"
        json = ArchiveMother.create_info_page([
            ArchiveMother.create_channel(name=expected_name,
                                         is_connected=expected_connectivity)
        ])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result[expected_name].is_connected(),
                    is_(expected_connectivity))
        assert_that(result[expected_name].status, is_(expected_status))
    def test_GIVEN_one_channels_is_disconnected_WHEN_parse_THEN_block_is_disconnected_and_status_reflects_this(
            self):
        expected_name = "BLOCK"
        expected_connectivity = False
        expected_status = "Disconnected"  # Don't use constant this is the word expected in the js file.
        json = ArchiveMother.create_info_page([
            ArchiveMother.create_channel(name=expected_name,
                                         is_connected=expected_connectivity)
        ])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result[expected_name].is_connected(),
                    is_(expected_connectivity))
        assert_that(result[expected_name].status, is_(expected_status))
    def test_GIVEN_one_channels_with_no_units_but_connected_WHEN_parse_THEN_block_has_units(
            self):
        """
        e.g. CS:PS: PVs
        """
        expected_name = "BLOCK"
        value = u'0.000'
        expected_value = "{}".format(value)
        channel = ArchiveMother.create_channel(name=expected_name, value=value)
        del channel['Current Value']["Units"]
        json = ArchiveMother.create_info_page([channel])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result[expected_name].get_description()["value"],
                    is_(expected_value))
    def test_GIVEN_one_channels_with_units_WHEN_parse_THEN_block_has_units(
            self):
        expected_name = "BLOCK"
        units = u'uA hour'
        value = u'0.000'
        expected_value = "{} {}".format(value, units)
        json = ArchiveMother.create_info_page([
            ArchiveMother.create_channel(name=expected_name,
                                         units=units,
                                         value=value)
        ])
        parser = WebPageParser()

        result = parser.extract_blocks(json)

        assert_that(result[expected_name].get_description()["value"],
                    is_(expected_value))
class InstrumentInformationCollator:
    """
    Collect instrument information and summarise as a dictionary.
    """

    # String to use for title and username if they are private
    PRIVATE_VALUE = "Unavailable"
    # Name of the username channel
    USERNAME_CHANNEL_NAME = "_USERNAME"
    # name of the title channel
    TITLE_CHANNEL_NAME = "TITLE"
    # name of the channel which determins of the username and title should be displayed
    DISPLAY_TITLE_CHANNEL_NAME = "DISPLAY"
    # name of the channel of the current run duration
    RUN_DURATION_CHANNEL_NAME = "RUNDURATION"
    # name of the channel fo the run duration for the current period
    RUN_DURATION_PD_CHANNEL_NAME = "RUNDURATION_PD"

    def __init__(self, host="localhost", reader=None):
        """
        Initialize.
        Args:
            host: The host of the instrument from which to read the information.
            reader: A reader object to get external information.
        """
        if reader is None:
            self.reader = DataSourceReader(host)
        else:
            self.reader = reader

        self.web_page_parser = WebPageParser()

    def _get_inst_pvs(self, ans, blocks_all):
        """
        Extracts and formats a list of relevant instrument PVs from all instrument PVs.

        Args:
            ans: List of blocks from the instrument archive.
            blocks_all: List of blocks from the block and dataweb archives.

        Returns: A trimmed list of instrument PVs.

        """
        wanted = {}

        title_channel_name = InstrumentInformationCollator.TITLE_CHANNEL_NAME
        username_channel_name = InstrumentInformationCollator.USERNAME_CHANNEL_NAME
        run_duration_channel_name = InstrumentInformationCollator.RUN_DURATION_CHANNEL_NAME
        run_duration_pd_channel_name = InstrumentInformationCollator.RUN_DURATION_PD_CHANNEL_NAME
        required_pvs = [
            "RUNSTATE", "RUNNUMBER", "_RBNUMBER", title_channel_name,
            username_channel_name, "STARTTIME", run_duration_channel_name,
            run_duration_pd_channel_name, "GOODFRAMES", "GOODFRAMES_PD",
            "RAWFRAMES", "RAWFRAMES_PD", "PERIOD", "NUMPERIODS", "PERIODSEQ",
            "BEAMCURRENT", "TOTALUAMPS", "COUNTRATE", "DAEMEMORYUSED",
            "TOTALCOUNTS", "DAETIMINGSOURCE", "MONITORCOUNTS",
            "MONITORSPECTRUM", "MONITORFROM", "MONITORTO", "NUMTIMECHANNELS",
            "NUMSPECTRA", "SHUTTER", "SIM_MODE"
        ]

        try:
            set_rc_values_for_blocks(blocks_all.values(), ans)
        except Exception as e:
            logging.error("Error in setting rc values for blocks: " + str(e))

        for pv in required_pvs:
            if pv + ".VAL" in ans:
                wanted[pv] = ans[pv + ".VAL"]

        try:
            self._convert_seconds(wanted[run_duration_channel_name])
        except KeyError:
            pass

        try:
            self._convert_seconds(wanted[run_duration_pd_channel_name])
        except KeyError:
            pass

        display_title_channel_name = InstrumentInformationCollator.DISPLAY_TITLE_CHANNEL_NAME + ".VAL"
        if display_title_channel_name not in ans or ans[
                display_title_channel_name].get_value().lower() != "yes":
            if title_channel_name in wanted:
                wanted[title_channel_name].set_value(
                    InstrumentInformationCollator.PRIVATE_VALUE)
            if username_channel_name in wanted:
                wanted[username_channel_name].set_value(
                    InstrumentInformationCollator.PRIVATE_VALUE)

        return wanted

    def _convert_seconds(self, block):
        """
        Receives the value from the block and converts to hours, minutes and seconds.

        Args:
            block: the block to convert

        """
        if not block.is_connected():
            return
        old_value = block.get_value()
        minutes, seconds = divmod(int(old_value), 60)
        hours, minutes = divmod(minutes, 60)

        if hours == 0 and minutes == 0:
            block.set_value("{} s".format(old_value))
        elif hours == 0:
            block.set_value("{} min {} s".format(str(minutes), str(seconds)))
        else:
            block.set_value("{} hr {} min {} s".format(str(hours),
                                                       str(minutes),
                                                       str(seconds)))
        block.set_units("")

    def collate(self):
        """
        Returns the collated information on instrument configuration, blocks and run status PVs as JSON.

        Returns: JSON of the instrument's configuration and status.

        """

        instrument_config = InstrumentConfig(self.reader.read_config())

        try:

            # read blocks
            json_from_blocks_archive = self.reader.get_json_from_blocks_archive(
            )
            blocks_log = self.web_page_parser.extract_blocks(
                json_from_blocks_archive)

            json_from_dataweb_archive = self.reader.get_json_from_dataweb_archive(
            )
            blocks_nolog = self.web_page_parser.extract_blocks(
                json_from_dataweb_archive)

            blocks_all = dict(blocks_log.items() + blocks_nolog.items())

            # get block visibility from config
            for block_name, block in blocks_all.items():
                block.set_visibility(
                    instrument_config.block_is_visible(block_name))

            json_from_instrument_archive = self.reader.get_json_from_instrument_archive(
            )
            instrument_blocks = self.web_page_parser.extract_blocks(
                json_from_instrument_archive)

            inst_pvs = format_blocks(
                self._get_inst_pvs(instrument_blocks, blocks_all))

        except Exception as e:
            logger.error("Failed to read blocks: " + str(e))
            raise e

        blocks_all_formatted = format_blocks(blocks_all)
        groups = {}
        for group in instrument_config.groups:
            blocks = {}
            for block in group["blocks"]:
                if block in blocks_all_formatted.keys():
                    blocks[block] = blocks_all_formatted[block]
            groups[group["name"]] = blocks

        return {
            "config_name": instrument_config.name,
            "groups": groups,
            "inst_pvs": inst_pvs
        }