def test_wrong_socket_type(): address = '127.0.0.1' port = get_available_port() term = b'\r\n' t = threading.Thread(target=echo_server_udp, args=(address, port, term)) t.start() time.sleep(0.1) # allow some time for the echo server to start with pytest.raises(MSLConnectionError): EquipmentRecord(connection=ConnectionRecord( address='TCP::{}::{}'.format(address, port), # trying TCP for a UDP server backend=Backend.MSL, properties=dict(termination=term, timeout=5), )).connect() record = EquipmentRecord(connection=ConnectionRecord( address='UDP::{}::{}'.format(address, port), backend=Backend.MSL, properties=dict(termination=term, timeout=5), )) # use the correct socket type to shutdown the server dev = record.connect() dev.write('SHUTDOWN')
def test_port_functions(): port = utils.get_available_port() assert not utils.is_port_in_use(port) sock = socket.socket() sock.bind(('', port)) sock.listen(1) assert utils.is_port_in_use(port) sock.close() assert not utils.is_port_in_use(port)
def test_udp_socket_read(): address = '127.0.0.1' port = get_available_port() term = b'\r\n' t = threading.Thread(target=echo_server_udp, args=(address, port, term)) t.start() time.sleep(0.1) # allow some time for the echo server to start record = EquipmentRecord(connection=ConnectionRecord( address='UDP::{}::{}'.format(address, port), backend=Backend.MSL, properties=dict( termination=term, # sets both read_termination and write_termination timeout=30), )) dev = record.connect() assert dev.read_termination == term assert dev.write_termination == term dev.write('hello') assert dev.read() == 'hello' n = dev.write('hello') assert dev.read(n) == 'hello' + term.decode( ) # specified `size` so `term` is not removed n = dev.write(b'021.3' + term + b',054.2') assert dev.read(n) == '021.3' + term.decode() + ',054.2' + term.decode( ) # `term` is not removed dev.write(b'021.3' + term + b',054.2') assert dev.read(3) == '021' assert dev.read(5) == '.3' + term.decode() + ',' assert dev.read( ) == '054.2' # read the rest -- removes the `term` at the end dev.write(b'021.3' + term + b',054.2') assert dev.read() == '021.3' # read until first `term` assert dev.read() == ',054.2' # read until second `term` n = dev.write('12345') assert n == 7 with pytest.raises(MSLTimeoutError): dev.read(n + 1) # read more bytes than are available assert dev.read(n) == '12345' + term.decode() assert len(dev.byte_buffer) == 0 dev.write('SHUTDOWN')
def test_tcp_socket_timeout(): address = '127.0.0.1' port = get_available_port() write_termination = b'\n' t = threading.Thread(target=echo_server_tcp, args=(address, port, write_termination)) t.start() time.sleep(0.1) # allow some time for the echo server to start record = EquipmentRecord(connection=ConnectionRecord( address='TCPIP::{}::{}::SOCKET'.format( address, port), # use PyVISA's address convention backend=Backend.MSL, properties=dict(write_termination=write_termination, timeout=7), )) dev = record.connect() assert dev.timeout == 7 assert dev.socket.gettimeout() == 7 dev.timeout = None assert dev.timeout is None assert dev.socket.gettimeout() is None dev.timeout = 0.1 assert dev.timeout == 0.1 assert dev.socket.gettimeout() == 0.1 dev.timeout = 0 assert dev.timeout is None assert dev.socket.gettimeout() is None dev.timeout = -1 assert dev.timeout is None assert dev.socket.gettimeout() is None dev.timeout = -12345 assert dev.timeout is None assert dev.socket.gettimeout() is None dev.timeout = 10 assert dev.timeout == 10 assert dev.socket.gettimeout() == 10 dev.write('SHUTDOWN')
def test_timeout(): with pytest.raises(utils.ConnectionTimeoutError): utils.wait_for_server('localhost', utils.get_available_port(), 2)
def test_port_functions(): assert not utils.port_in_use(utils.get_available_port(), 20)
def __init__(self, module32, host='127.0.0.1', port=None, timeout=10.0, quiet=True, append_sys_path=None, append_environ_path=None, **kwargs): """Base class for communicating with a 32-bit library from 64-bit Python. Starts a 32-bit server, :class:`~.server32.Server32`, to host a Python module that is a wrapper around a 32-bit library. The *client64* module runs within a 64-bit Python interpreter and it sends a request to the server which calls the 32-bit library to execute the request. The server then provides a response back to the client. Parameters ---------- module32 : :class:`str` The name of the Python module that is to be imported by the 32-bit server. host : :class:`str`, optional The IP address of the 32-bit server. Default is ``'127.0.0.1'``. port : :class:`int`, optional The port to open on the 32-bit server. Default is :obj:`None`, which means to automatically find a port that is available. timeout : :class:`float`, optional The maximum number of seconds to wait to establish a connection to the 32-bit server. Default is 10 seconds. quiet : :class:`bool`, optional Whether to hide :obj:`sys.stdout` messages from the 32-bit server. Default is :obj:`True`. append_sys_path : :class:`str` or :class:`list` of :class:`str`, optional Append path(s) to the 32-bit server's :obj:`sys.path` variable. The value of :obj:`sys.path` from the 64-bit process is automatically included, i.e., ``sys.path(32bit) = sys.path(64bit) + append_sys_path`` Default is :obj:`None`. append_environ_path : :class:`str` or :class:`list` of :class:`str`, optional Append path(s) to the 32-bit server's :obj:`os.environ['PATH'] <os.environ>` variable. This can be useful if the library that is being loaded requires additional libraries that must be available on ``PATH``. Default is :obj:`None`. **kwargs Keyword arguments that will be passed to the :class:`~.server32.Server32` subclass. The data type of each value is not preserved. It will be a string at the constructor of the :class:`~.server32.Server32` subclass. Note ---- If `module32` is not located in the current working directory then you must either specify the full path to `module32` **or** you can specify the folder where `module32` is located by passing a value to the `append_sys_path` parameter. Using the `append_sys_path` option also allows for any other modules that `module32` may depend on to also be included in :obj:`sys.path` so that those modules can be imported when `module32` is imported. Raises ------ IOError If the frozen executable cannot be found. TypeError If the data type of `append_sys_path` or `append_environ_path` is invalid. :exc:`~msl.loadlib.utils.ConnectionTimeoutError` If the connection to the 32-bit server cannot be established. """ self._is_active = False if port is None: port = utils.get_available_port() # the temporary file to use to save the pickle'd data self._pickle_temp_file = os.path.join(tempfile.gettempdir(), str(uuid.uuid4())) # select the highest-level pickle protocol to use based on the version of python major, minor = sys.version_info.major, sys.version_info.minor if (major <= 1) or (major == 2 and minor < 3): self._pickle_protocol = 1 elif major == 2: self._pickle_protocol = 2 elif (major == 3) and (minor < 4): self._pickle_protocol = 3 else: self._pickle_protocol = pickle.HIGHEST_PROTOCOL # make sure that the server32 executable exists server_exe = os.path.join(os.path.dirname(__file__), SERVER_FILENAME) if not os.path.isfile(server_exe): raise IOError('Cannot find ' + server_exe) cmd = [ server_exe, '--module', module32, '--host', host, '--port', str(port) ] # include paths to the 32-bit server's sys.path _append_sys_path = sys.path if append_sys_path is not None: if isinstance(append_sys_path, str): _append_sys_path.append(append_sys_path.strip()) elif isinstance(append_sys_path, (list, tuple)): _append_sys_path.extend(append_sys_path) else: raise TypeError('append_sys_path must be a str, list or tuple') cmd.extend(['--append-sys-path', ';'.join(_append_sys_path).strip()]) # include paths to the 32-bit server's os.environ['PATH'] if append_environ_path is not None: if isinstance(append_environ_path, str): env_str = append_environ_path.strip() elif isinstance(append_environ_path, (list, tuple)): env_str = ';'.join(append_environ_path).strip() else: raise TypeError( 'append_environ_path must be a str, list or tuple') if env_str: cmd.extend(['--append-environ-path', env_str]) # include any keyword arguments if kwargs: kw_str = ';'.join('{}={}'.format(key, value) for key, value in kwargs.items()) cmd.extend(['--kwargs', kw_str]) if quiet: cmd.append('--quiet') # start the server, cannot use subprocess.call() because it blocks subprocess.Popen(cmd, stderr=sys.stderr, stdout=sys.stderr) utils.wait_for_server(host, port, timeout) # start the connection HTTPConnection.__init__(self, host, port) self._is_active = True self._lib32_path = self.request32('LIB32_PATH')
def __init__(self, path, libtype='cdll'): """Load a shared library. For example, a C/C++, FORTRAN, CLR, Java, Delphi, LabVIEW, ... library. Based on the value of `libtype` or the file extension this class will load the shared library as a: * :class:`~ctypes.CDLL` if `libtype` is ``'cdll'``, * :class:`~ctypes.WinDLL` if `libtype` is ``'windll'``, * :class:`~ctypes.OleDLL` if `libtype` is ``'oledll'``, * `System.Reflection.Assembly <Assembly_>`_ if `libtype` is ``'net'``, or a * :class:`~.py4j.java_gateway.JavaGateway` if `libtype` is ``'java'``. .. _Assembly: https://msdn.microsoft.com/en-us/library/system.reflection.assembly(v=vs.110).aspx Parameters ---------- path : :class:`str` The path to the shared library. The search order for finding the shared library is: 1. assume that a full or a relative (to the current working directory) path is specified, 2. use :obj:`ctypes.util.find_library` to find the shared library file, 3. search :obj:`sys.path`, then 4. search :obj:`os.environ['PATH'] <os.environ>` to find the shared library. libtype : :class:`str`, optional The library type. The following values are currently supported: * ``'cdll'`` -- for a library that uses the __cdecl calling convention * ``'windll'`` or ``'oledll'`` -- for a __stdcall calling convention * ``'net'`` -- for Microsoft's .NET Framework (Common Language Runtime) * ``'java'`` -- for a Java archive, ``.jar``, or Java byte code, ``.class``, file Default is ``'cdll'``. .. note:: Since the ``.jar`` or ``.class`` extension uniquely defines a Java library, the `libtype` will automatically be set to ``'java'`` if `path` ends with ``.jar`` or ``.class``. Raises ------ IOError If the shared library cannot be loaded. TypeError If `libtype` is not a supported library type. """ # a reference to the shared library self._lib = None # a reference to the .NET Runtime Assembly self._assembly = None # a reference to the Py4J JavaGateway self._gateway = None # fixes Issue #8, if `path` is a <class 'pathlib.Path'> object path = str(path) # create a new reference to `path` just in case the # DEFAULT_EXTENSION is appended below so that the # ctypes.util.find_library function call will use the # unmodified value of `path` _path = path # assume a default extension if no extension was provided ext = os.path.splitext(_path)[1] if not ext: _path += DEFAULT_EXTENSION # the .jar or .class extension uniquely defines a Java library if ext in ('.jar', '.class'): libtype = 'java' self._path = os.path.abspath(_path) if not os.path.isfile(self._path): # for find_library use the original 'path' value since it may be a library name # without any prefix like 'lib', suffix like '.so', '.dylib' or version number self._path = ctypes.util.find_library(path) if self._path is None: # then search sys.path and os.environ['PATH'] success = False search_dirs = sys.path + os.environ['PATH'].split(os.pathsep) for directory in search_dirs: p = os.path.join(directory, _path) if os.path.isfile(p): self._path = p success = True break if not success: raise IOError( 'Cannot find the shared library "{}"'.format(path)) libtype = libtype.lower() if libtype == 'cdll': self._lib = ctypes.CDLL(self._path) elif libtype == 'windll': self._lib = ctypes.WinDLL(self._path) elif libtype == 'oledll': self._lib = ctypes.OleDLL(self._path) elif libtype == 'java': if not utils.is_py4j_installed(): raise IOError( 'Cannot load a Java file because Py4J is not installed.\n' 'To install Py4J run: pip install py4j') from py4j.version import __version__ from py4j.java_gateway import JavaGateway, GatewayParameters # the address and port to use to host the py4j.GatewayServer address = '127.0.0.1' port = utils.get_available_port() # find the py4j JAR (needed to import py4j.GatewayServer on the Java side) root = os.path.dirname(sys.executable) filename = 'py4j' + __version__ + '.jar' py4j_jar = os.path.join(root, 'share', 'py4j', filename) if not os.path.isfile(py4j_jar): root = os.path.dirname( root) # then check one folder up (for unix or venv) py4j_jar = os.path.join(root, 'share', 'py4j', filename) if not os.path.isfile(py4j_jar): py4j_jar = os.environ.get( 'PY4J_JAR', '') # then check the environment variable if not os.path.isfile(py4j_jar): raise IOError( 'Cannot find {0}\nCreate a PY4J_JAR environment ' 'variable to be equal to the full path to {0}'. format(filename)) # build the java command wrapper = os.path.join(os.path.dirname(__file__), 'py4j-wrapper.jar') cmd = [ 'java', '-cp', py4j_jar + os.pathsep + wrapper, 'Py4JWrapper', str(port) ] # from the URLClassLoader documentation: # Any URL that ends with a '/' is assumed to refer to a directory. Otherwise, the URL # is assumed to refer to a JAR file which will be downloaded and opened as needed. if ext == '.jar': cmd.append(self._path) else: # it is a .class file cmd.append(os.path.dirname(self._path) + '/') try: # start the py4j.GatewayServer, cannot use subprocess.call() because it blocks subprocess.Popen(cmd, stderr=sys.stderr, stdout=sys.stdout) err = None except IOError: err = 'You must have a Java Runtime Environment installed and available on PATH' if err: raise IOError(err) utils.wait_for_server(address, port, 5.0) self._gateway = JavaGateway(gateway_parameters=GatewayParameters( address=address, port=port)) self._lib = self._gateway.jvm elif libtype == 'net': if not utils.is_pythonnet_installed(): raise IOError( 'Cannot load a .NET Assembly because pythonnet is not installed.\n' 'To install pythonnet run: pip install pythonnet') import clr try: # By default, pythonnet can only load libraries that are for .NET 4.0+. # # In order to allow pythonnet to load a library from .NET <4.0 the # useLegacyV2RuntimeActivationPolicy property needs to be enabled # in a <python-executable>.config file. If the following statement # raises a FileLoadException then attempt to create the configuration # file that has the property enabled and then notify the user why # loading the library failed and ask them to re-run their Python # script to load the .NET library. self._assembly = clr.System.Reflection.Assembly.LoadFile( self._path) except clr.System.IO.FileLoadException as err: # Example error message that can occur if the library is for .NET <4.0, # and the useLegacyV2RuntimeActivationPolicy is not enabled: # # " Mixed mode assembly is built against version 'v2.0.50727' of the # runtime and cannot be loaded in the 4.0 runtime without additional # configuration information. " if str(err).startswith('Mixed mode assembly'): status, msg = utils.check_dot_net_config(sys.executable) if not status == 0: raise IOError(msg) else: update_msg = 'Checking .NET config returned "{}" '.format( msg) update_msg += 'and still cannot load library.\n' update_msg += str(err) raise IOError(update_msg) raise IOError( 'The above "System.IO.FileLoadException" is not handled.\n' ) # the shared library must be available in sys.path head, tail = os.path.split(self._path) sys.path.append(head) # don't include the library extension clr.AddReference(os.path.splitext(tail)[0]) # import namespaces, create instances of classes or reference a System.Type[] object dotnet = dict() for t in self._assembly.GetTypes(): if t.Namespace is not None: mod = __import__(t.Namespace) if mod.__name__ not in dotnet: dotnet[mod.__name__] = mod else: try: dotnet[t.Name] = self._assembly.CreateInstance( t.FullName) except: if t.Name not in dotnet: dotnet[t.Name] = t self._lib = DotNet(dotnet, self._path) else: raise TypeError('Cannot load libtype={}'.format(libtype)) logger.debug('Loaded ' + self._path)
def test_omega_ithx_iserver(): address = '127.0.0.1' port = get_available_port() term = b'\r' t = 20.0 h = 40.0 d = 10.0 records = [ EquipmentRecord( manufacturer='OMEGA', model='iTHX-W3', connection=ConnectionRecord( address='TCP::{}::{}'.format(address, port), backend='MSL', properties=dict( termination=term, timeout=2 ), ) ), # iTHX-W and iTHX-2 do not support the *SRB and *SRBF commands for # fetching both the temperature and humidity with a single write command EquipmentRecord( manufacturer='OMEGA', model='iTHX-2', connection=ConnectionRecord( address='TCP::{}::{}'.format(address, port), backend='MSL', properties=dict( termination=term, timeout=2 ), ) ), ] for index, record in enumerate(records): thread = threading.Thread(target=simulate_omega_iserver, args=(address, port, term)) thread.daemon = True thread.start() dev = record.connect(demo=False) assert isinstance(dev, iTHX) assert dev.temperature() == t assert dev.temperature(probe=1) == t assert dev.temperature(probe=2) == t assert dev.temperature(probe=1, nbytes=6) == t assert dev.temperature(probe=2, nbytes=6) == t assert dev.humidity() == h assert dev.humidity(probe=1) == h assert dev.humidity(probe=2) == h assert dev.humidity(probe=1, nbytes=6) == h assert dev.humidity(probe=2, nbytes=6) == h assert dev.dewpoint() == d assert dev.dewpoint(probe=1) == d assert dev.dewpoint(probe=2) == d assert dev.dewpoint(probe=1, nbytes=6) == d assert dev.dewpoint(probe=2, nbytes=6) == d assert dev.temperature_humidity() == (t, h) assert dev.temperature_humidity(probe=1) == (t, h) assert dev.temperature_humidity(probe=2) == (t, h) assert dev.temperature_humidity(probe=1, nbytes=12) == (t, h) assert dev.temperature_humidity(probe=1, nbytes=13) == (t, h) assert dev.temperature_humidity(probe=2, nbytes=12) == (t, h) assert dev.temperature_humidity(probe=2, nbytes=13) == (t, h) assert dev.temperature_humidity_dewpoint() == (t, h, d) assert dev.temperature_humidity_dewpoint(probe=1) == (t, h, d) assert dev.temperature_humidity_dewpoint(probe=2) == (t, h, d) assert dev.temperature_humidity_dewpoint(probe=1, nbytes=18) == (t, h, d) assert dev.temperature_humidity_dewpoint(probe=1, nbytes=19) == (t, h, d) assert dev.temperature_humidity_dewpoint(probe=1, nbytes=20) == (t, h, d) assert dev.temperature_humidity_dewpoint(probe=2, nbytes=18) == (t, h, d) assert dev.temperature_humidity_dewpoint(probe=2, nbytes=19) == (t, h, d) assert dev.temperature_humidity_dewpoint(probe=2, nbytes=20) == (t, h, d) dev.write('SHUTDOWN') dev.disconnect() thread.join()