def test_remove_accents_from_name(self):
        # Arrange
        # list of names in unicode code points, which is the same as ISO-8859-1 encoding 
        names_uni = [ u'Somebody', u'S\xf8rina', u'\xe9\xe5\xf5\xf6\xc6' ]
        # best ascii equivalents of names 
        names_ascii = [ 'Somebody', 'Sorina', 'eaooAE' ]

        # Act
        conv_names = ExpData.make_name_list_ascii(names_uni).split(',')

        # Assert
        self.assertTrue(conv_names == names_ascii)
    def __init__(self, ca_server, dbid, options_folder, test_mode=False):
        """Constructor.

        Args:
            ca_server (CAServer): The CA server used for generating PVs on the fly
            dbid (string): The id of the database that holds IOC information.
            options_folder (string): The location of the folder containing the config.xml file that holds IOC options
        """
        if test_mode:
            ps = MockProcServWrapper()
        else:
            super(DatabaseServer, self).__init__()
            ps = ProcServWrapper()
        self._ca_server = ca_server
        self._options_holder = OptionsHolder(options_folder, OptionsLoader())

        # Initialise database connection
        try:
            self._db = IOCData(dbid, ps, MACROS["$(MYPVPREFIX)"])
            print_and_log("Connected to database", "INFO", "DBSVR")
        except Exception as err:
            self._db = None
            print_and_log("Problem initialising DB connection: %s" % err, "MAJOR", "DBSVR")

        # Initialise experimental database connection
        try:
            self._ed = ExpData(MACROS["$(MYPVPREFIX)"])
            print_and_log("Connected to experimental details database", "INFO", "DBSVR")
        except Exception as err:
            self._ed = None
            print_and_log("Problem connecting to experimental details database: %s" % err, "MAJOR", "DBSVR")

        if self._db is not None and not test_mode:
            # Start a background thread for keeping track of running IOCs
            self.monitor_lock = RLock()
            monitor_thread = Thread(target=self.update_ioc_monitors, args=())
            monitor_thread.daemon = True  # Daemonise thread
            monitor_thread.start()
 def setUp(self):
     self.ca = MockChannelAccess()
     self.exp_data = ExpData("TEST_PREFIX", self.ca)
class TestExpData(unittest.TestCase):
    def setUp(self):
        self.ca = MockChannelAccess()
        self.exp_data = ExpData("TEST_PREFIX", self.ca)

    def decodepv(self, pv):
        return json.loads(dehex_and_decompress(self.ca.caget(pv)))

    def test_single_surname_returns_surname(self):
        # Arrange
        fullname = "Tom Jones"

        # Act
        surname = self.exp_data._get_surname_from_fullname(fullname)

        # Assert
        self.assertEquals(surname, "Jones")

    def test_double_barrelled_surname_returns_last_name(self):
        # Arrange
        fullname = "Tom de Jones"

        # Act
        surname = self.exp_data._get_surname_from_fullname(fullname)

        # Assert
        self.assertEquals(surname, "Jones")

    def test_single_name_returns_fullname(self):
        # Arrange
        fullname = "TomJones"

        # Act
        surname = self.exp_data._get_surname_from_fullname(fullname)

        # Assert
        self.assertEquals(surname, "TomJones")

    def test_update_username_for_single_user(self):
        # Arrange
        users = '[{"name":"Tom Jones","institute":"STFC","role":"user"}]'

        # Act
        self.exp_data.updateUsername(users)

        # Assert
        simnames = self.decodepv(self.exp_data._simnames)
        surnames = self.decodepv(self.exp_data._surnamepv)
        orgs = self.decodepv(self.exp_data._orgspv)

        self.assertEqual(simnames[0]["name"], "Tom Jones")
        self.assertTrue("Jones" in surnames)
        self.assertTrue("STFC" in orgs)

    def test_update_username_for_multiple_users_same_institute(self):
        # Arrange
        users = '['
        users += '{"name":"Tom Jones","institute":"STFC","role":"user"},'
        users += '{"name":"David James","institute":"STFC","role":"user"}'
        users += ']'

        # Act
        self.exp_data.updateUsername(users)

        # Assert
        simnames = self.decodepv(self.exp_data._simnames)
        surnames = self.decodepv(self.exp_data._surnamepv)
        orgs = self.decodepv(self.exp_data._orgspv)

        self.assertEqual(len(simnames), 2)
        self.assertEqual(len(surnames), 2)
        self.assertEqual(len(orgs), 1)

    def test_update_username_for_blank_users(self):
        # Arrange
        users = '[{"name":"Tom Jones","institute":"STFC","role":"user"}]'
        self.exp_data.updateUsername(users)

        # Act
        self.exp_data.updateUsername("")

        # Assert
        simnames = self.decodepv(self.exp_data._simnames)
        surnames = self.decodepv(self.exp_data._surnamepv)
        orgs = self.decodepv(self.exp_data._orgspv)

        self.assertEqual(len(simnames), 0)
        self.assertEqual(len(surnames), 0)
        self.assertEqual(len(orgs), 0)

    def test_remove_accents_from_name(self):
        # Arrange
        # list of names in unicode code points, which is the same as ISO-8859-1 encoding 
        names_uni = [ u'Somebody', u'S\xf8rina', u'\xe9\xe5\xf5\xf6\xc6' ]
        # best ascii equivalents of names 
        names_ascii = [ 'Somebody', 'Sorina', 'eaooAE' ]

        # Act
        conv_names = ExpData.make_name_list_ascii(names_uni).split(',')

        # Assert
        self.assertTrue(conv_names == names_ascii)
class DatabaseServer(Driver):
    """The class for handling all the static PV access and monitors etc.
    """
    def __init__(self, ca_server, dbid, options_folder, test_mode=False):
        """Constructor.

        Args:
            ca_server (CAServer): The CA server used for generating PVs on the fly
            dbid (string): The id of the database that holds IOC information.
            options_folder (string): The location of the folder containing the config.xml file that holds IOC options
        """
        if test_mode:
            ps = MockProcServWrapper()
        else:
            super(DatabaseServer, self).__init__()
            ps = ProcServWrapper()
        self._ca_server = ca_server
        self._options_holder = OptionsHolder(options_folder, OptionsLoader())

        # Initialise database connection
        try:
            self._db = IOCData(dbid, ps, MACROS["$(MYPVPREFIX)"])
            print_and_log("Connected to database", "INFO", "DBSVR")
        except Exception as err:
            self._db = None
            print_and_log("Problem initialising DB connection: %s" % err, "MAJOR", "DBSVR")

        # Initialise experimental database connection
        try:
            self._ed = ExpData(MACROS["$(MYPVPREFIX)"])
            print_and_log("Connected to experimental details database", "INFO", "DBSVR")
        except Exception as err:
            self._ed = None
            print_and_log("Problem connecting to experimental details database: %s" % err, "MAJOR", "DBSVR")

        if self._db is not None and not test_mode:
            # Start a background thread for keeping track of running IOCs
            self.monitor_lock = RLock()
            monitor_thread = Thread(target=self.update_ioc_monitors, args=())
            monitor_thread.daemon = True  # Daemonise thread
            monitor_thread.start()

    def read(self, reason):
        """A method called by SimpleServer when a PV is read from the DatabaseServer over Channel Access.

        Args:
            reason (string): The PV that is being requested (without the PV prefix)

        Returns:
            string : A compressed and hexed JSON formatted string that gives the desired information based on reason.
        """
        if reason == 'SAMPLE_PARS':
            value = self.encode4return(self.get_sample_par_names())
        elif reason == 'BEAMLINE_PARS':
            value = self.encode4return(self.get_beamline_par_names())
        elif reason == 'USER_PARS':
            value = self.encode4return(self.get_user_par_names())
        elif reason == "IOCS_NOT_TO_STOP":
            value = self.encode4return(IOCS_NOT_TO_STOP)
        else:
            value = self.getParam(reason)
        return value

    def write(self, reason, value):
        """A method called by SimpleServer when a PV is written to the DatabaseServer over Channel Access.

        Args:
            reason (string): The PV that is being requested (without the PV prefix)
            value (string): The data being written to the 'reason' PV

        Returns:
            bool : True
        """
        status = True
        try:
            if reason == 'ED:RBNUMBER:SP':
                #print_and_log("Updating to use experiment ID: " + value, "INFO", "DBSVR")
                self._ed.updateExperimentID(value)
            elif reason == 'ED:USERNAME:SP':
                self._ed.updateUsername(dehex_and_decompress(value))
        except Exception as err:
            value = compress_and_hex(convert_to_json("Error: " + str(err)))
            print_and_log(str(err), "MAJOR")
        # store the values
        if status:
            self.setParam(reason, value)
        return status

    def update_ioc_monitors(self):
        """Updates all the PVs that hold information on the IOCS and their associated PVs
        """
        while True:
            if self._db is not None:
                self._db.update_iocs_status()
                self.setParam("IOCS", self.encode4return(self._get_iocs_info()))
                self.setParam("PVS:ALL", self.encode4return(self._get_interesting_pvs("")))
                self.setParam("PVS:ACTIVE", self.encode4return(self._get_active_pvs()))
                self.setParam("PVS:INTEREST:HIGH", self.encode4return(self._get_interesting_pvs("HIGH")))
                self.setParam("PVS:INTEREST:MEDIUM", self.encode4return(self._get_interesting_pvs("MEDIUM")))
                self.setParam("PVS:INTEREST:FACILITY", self.encode4return(self._get_interesting_pvs("FACILITY")))
                # Update them
                with self.monitor_lock:
                    self.updatePVs()
            sleep(1)

    def encode4return(self, data):
        """Converts data to JSON, compresses it and converts it to hex.

        Args:
            data (string): The data to encode

        Returns:
            string : The encoded data
        """
        return compress_and_hex(json.dumps(data).encode('ascii', 'replace'))

    def _get_iocs_info(self):
        iocs = self._db.get_iocs()
        options = self._options_holder.get_config_options()
        for iocname in iocs.keys():
            if iocname in options:
                iocs[iocname].update(options[iocname])
        return iocs

    def _get_interesting_pvs(self, level, ioc=None):
        if self._db is not None:
            return self._db.get_interesting_pvs(level, ioc)
        else:
            return list()

    def _get_active_pvs(self):
        if self._db is not None:
            return self._db.get_active_pvs()
        else:
            return list()

    def get_sample_par_names(self):
        """Returns the sample parameters from the database, replacing the MYPVPREFIX macro

        Returns:
            list : A list of sample parameter names, an empty list if the database does not exist
        """
        if self._db is not None:
            return [p.replace(MACROS["$(MYPVPREFIX)"], "") for p in self._db.get_sample_pars()]
        else:
            return list()

    def get_beamline_par_names(self):
        """Returns the beamline parameters from the database, replacing the MYPVPREFIX macro

        Returns:
            list : A list of beamline parameter names, an empty list if the database does not exist
        """
        if self._db is not None:
            return [p.replace(MACROS["$(MYPVPREFIX)"], "") for p in self._db.get_beamline_pars()]
        else:
            return list()

    def get_user_par_names(self):
        """Returns the user parameters from the database, replacing the MYPVPREFIX macro

        Returns:
            list : A list of user parameter names, an empty list if the database does not exist
        """
        if self._db is not None:
            return [p.replace(MACROS["$(MYPVPREFIX)"], "") for p in self._db.get_user_pars()]
        else:
            return list()