Пример #1
0
 def _start_sikuli_java_process(self):
     libFolder = os.path.join(os.path.abspath(os.path.dirname(__file__)),
                              'lib')
     jarList = glob.glob(libFolder + os.sep + '*.jar')
     if len(jarList) != 1:
         raise Exception('Sikuli jar package should be exist in lib folder')
     sikuliJar = jarList[0]
     java = 'java'
     arguments = [
         '-jar', sikuliJar,
         str(self.port),
         self._get_output_folder()
     ]
     self.process = Process()
     if os.getenv("DISABLE_SIKULI_LOG"):
         self.process.start_process(java, *arguments, shell=True)
     else:
         self.process.start_process(java,
                                    *arguments,
                                    shell=True,
                                    stdout=self._output_file(),
                                    stderr=self._err_file())
     self.logger.info('Start sikuli java process on port %s' %
                      str(self.port))
     self._wait_process_started()
     self.logger.info('Sikuli java process is started')
Пример #2
0
    def _run_process(self, command, *arguments, **configuration):

        expected_rc = int(configuration.pop('expected_rc', 0))
        token = configuration.pop('token', 'process')
        merged_output = is_truthy(configuration.pop('merged_output', True))
        input = configuration.pop('input', None)
        if input and not isinstance(input, bytes):
            input = input.encode()
        tty = is_truthy(configuration.pop('tty', False))
        redirection = configuration.pop('redirection', None)

        # For compatibility with Process.run_process()
        timeout = configuration.pop('timeout', None)
        on_timeout = configuration.pop('on_timeout', 'terminate')

        if redirection and not tty:
            raise ValueError('Cannot use "redirection" without "tty"')

        with ExitStack() as stack:
            if merged_output:
                stdout = stack.enter_context(_Attachment(token +
                                                         '-output.txt')).path
                stderr = 'STDOUT'
            else:
                stdout = stack.enter_context(_Attachment(token +
                                                         '-stdout.txt')).path
                stderr = stack.enter_context(_Attachment(token +
                                                         '-stderr.txt')).path

            if tty:
                joined = shlex.join((command, ) + arguments)
                if redirection:
                    joined += ' ' + redirection
                command = 'script'
                arguments = list()
                arguments += ['--return']
                arguments += ['--quiet']
                arguments += ['--echo', 'never', '--log-out', '/dev/null']
                arguments += ['--command', joined]

            process = Process()
            handle = process.start_process(command,
                                           *arguments,
                                           **configuration,
                                           stdout=str(stdout),
                                           stderr=str(stderr))
            if input:
                process_object = process.get_process_object(handle)
                process_object.stdin.write(input)
                process_object.stdin.close()
            result = process.wait_for_process(handle, timeout, on_timeout)

            if result.rc != expected_rc:
                raise AssertionError(
                    'Process exited with unexpected code {}'.format(result.rc))
            return result
 def _start_sikuli_java_process(self):
     libFolder = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'lib')
     jarList = glob.glob(libFolder+os.sep+'*.jar')
     if len(jarList) != 1:
         raise Exception('Sikuli jar package should be exist in lib folder')
     sikuliJar = jarList[0]
     command = 'java -jar '+sikuliJar+' %s ' % str(self.port)
     process = Process()
     process.start_process(command, shell=True)
     self.logger.info('Start sikuli java process on port %s' % str(self.port))
     self._wait_process_started()
     self.logger.info('Sikuli java process is started')
Пример #4
0
 def __init__(self):
     Screenshot.__init__(self, "C:\\Keyword\\Results")
     XML.__init__(self, use_lxml=True)
     """CommonUtil.__init__(self, None, dict , False)"""
     Telnet.__init__(self,
                     timeout='3 seconds',
                     newline='CRLF',
                     prompt=None,
                     prompt_is_regexp=False,
                     encoding='UTF-8',
                     encoding_errors='ignore',
                     default_log_level='INFO')
     Process.__init__(self)
     """Also this doc should be in shown in library doc."""
Пример #5
0
 def _start_sikuli_java_process(self):
     libFolder = os.path.join(os.path.abspath(os.path.dirname(__file__)),
                              'lib')
     jarList = glob.glob(libFolder + os.sep + '*.jar')
     if len(jarList) != 1:
         raise Exception('Sikuli jar package should be exist in lib folder')
     sikuliJar = jarList[0]
     command = 'java -jar ' + sikuliJar + ' %s ' % str(
         self.port) + self._get_output_folder()
     process = Process()
     process.start_process(command,
                           shell=True,
                           stdout=self._output_file(),
                           stderr=self._err_file())
     self.logger.info('Start sikuli java process on port %s' %
                      str(self.port))
     self._wait_process_started()
     self.logger.info('Sikuli java process is started')
Пример #6
0
    def maybe_uninstall_sdk(self):
        variables = BuiltIn().get_variables()
        use_existing_sdk_installation = variables['${USE_EXISTING_SDK_INSTALLATION}']

        if use_existing_sdk_installation:
            # Ensure we always return a valid result object
            return Process().run_process('true')

        return self._run_sdk_maintenance_tool(mode='uninstall')
    def stop_smtp_stub(self):
        print 'Handle: %d' % self.__stub_handle
        if self.__stub_started:
            logger.info('Stopping STUB')
            #Process().terminate_process(self.__stub_handle, kill=True)
            Process().terminate_all_processes(kill=True)
        else:
            logger.warn('Stub already stopped')

        self.__stub_started = False
    def start_smtp_stub(self):
        if not self.__stub_started:
            logger.info('Starting STUB on host: %s port: %d' %
                        (self.__smtphost, self.__smtpport))
            stubscript = os.path.join(base_dir, '..', 'server', 'smtp-stub.py')
            self.__stub_handle = Process().start_process(sys.executable,
                                                         stubscript,
                                                         cwd=base_dir,
                                                         alias='stub')
            print 'Handle: %d' % self.__stub_handle
        else:
            logger.warn('Stub already started')

        self.__stub_started = True
Пример #9
0
    def _run_process(self, command, *arguments, expected_rc=0, token='process',
            merged_output=True, **configuration):
        with ExitStack() as stack:
            if merged_output:
                stdout = stack.enter_context(_Attachment(token + '-output.txt')).path
                stderr = 'STDOUT'
            else:
                stdout = stack.enter_context(_Attachment(token + '-stdout.txt')).path
                stderr = stack.enter_context(_Attachment(token + '-stderr.txt')).path

            result = Process().run_process(command, *arguments, **configuration,
                    stdout=str(stdout), stderr=str(stderr))
            if result.rc != expected_rc:
                raise AssertionError('Process exited with unexpected code {}'.format(result.rc))
            return result
Пример #10
0
    def maybe_install_sdk(self, engine_memory_size_mb=None):
        variables = BuiltIn().get_variables()

        sdk_install_dir = variables['${SDK_INSTALL_DIR}']

        use_existing_sdk_installation = variables[
            '${USE_EXISTING_SDK_INSTALLATION}']
        if use_existing_sdk_installation:
            OperatingSystem().directory_should_exist(sdk_install_dir)
            # Ensure we always return a valid result object
            return Process().run_process('true')

        try:
            OperatingSystem().directory_should_not_exist(sdk_install_dir)
        except:
            # Ensure maybe_uninstall_sdk will not destroy existing installation
            BuiltIn().set_global_variable('${USE_EXISTING_SDK_INSTALLATION}',
                                          True)
            raise

        command = variables['${INSTALLER}']
        build_engine_type = variables['${BUILD_ENGINE_TYPE}']
        args = [
            '--verbose', 'non-interactive=1', 'accept-licenses=1',
            'buildEngineType=' + build_engine_type
        ]
        result = self._run_process(command, *args, token='installer')

        if engine_memory_size_mb:
            args = ['engine', 'set', 'vm.memorySize=' + engine_memory_size_mb]
            result = self.run_sfdk(*args)

        if variables['${DO_SSU_REGISTER}']:
            credentials_file = variables['${CREDENTIALS}']
            args = ['engine', 'exec', 'bash', '-c',
                    'creds=$(<"{}") && sdk-manage register-all --no-sdk --force \
                            --user "${{creds%%:*}}" --password "${{creds#*:}}"' \
                            .format(credentials_file)]
            result = self.run_sfdk(*args)

        return result
Пример #11
0
class SikuliLibrary(object):
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = VERSION

    def __init__(self, port=0, timeout=3.0, mode='OLD'):
        """
        @port: sikuli java process socket port
        @timeout: Timeout of waiting java process started
        @mode: if set as 'DOC',  will stop java process automatically, 
               if set as 'PYTHON', means library is running out of robot environment
               if set as 'CREATE', it is only for mvn package usage, will create keywords.py file
               if set as 'OLD'(default), sikuli java process will be started when library is inited
               if set as 'NEW', user should use 'start_sikuli_process' to start java process
        """
        self.logger = self._init_logger()
        self.timeout = float(timeout)
        self.port = None
        self.remote = None
        self.mode = mode.upper().strip()
        if mode == 'OLD':
            self.start_sikuli_process(port)
        if mode.upper().strip() == 'DOC':
            self.start_sikuli_process()
            self._stop_thread(4)
        elif mode.upper().strip() == 'PYTHON':
            self.connect_sikuli_process(port)
        elif mode.upper().strip() == 'CREATE':
            self._create_keywords_file()
        elif mode.upper().strip() != 'NEW':
            self._check_robot_running()

    def start_sikuli_process(self, port=None):
        """
        This keyword is used to start sikuli java process.
        If library is inited with mode "OLD", sikuli java process is started automatically.
        If library is inited with mode "NEW", this keyword should be used.

        :param port: port of sikuli java process, if value is None or 0, a random free port will be used
        :return: None
        """
        if port is None or int(port) == 0:
            port = self._get_free_tcp_port()
        self.port = port
        start_retries = 0
        started = False
        while start_retries < 5:
            try:
                self._start_sikuli_java_process()
            except RuntimeError as err:
                print('error........%s' % err)
                if self.process:
                    self.process.terminate_process()
                self.port = self._get_free_tcp_port()
                start_retries += 1
                continue
            started = True
            break
        if not started:
            raise RuntimeError('Start sikuli java process failed!')
        self.remote = self._connect_remote_library()

    def connect_sikuli_process(self, port):
        self.port = port
        self.remote = self._connect_remote_library()

    def _create_keywords_file(self):
        keywordDict = {}
        self.start_sikuli_process()
        try:
            keywordList = self.get_keyword_names()
            for keywordName in keywordList:
                keywordDict[keywordName] = {}
                keywordDict[keywordName]['arg'] = self.get_keyword_arguments(
                    keywordName)
                keywordDict[keywordName][
                    'doc'] = self.get_keyword_documentation(keywordName)
            with codecs.open(os.path.join(
                    os.path.abspath(os.path.dirname(__file__)), 'keywords.py'),
                             'w',
                             encoding='utf-8') as f:
                f.write('# -*- coding: utf-8 -*-\n')
                # keywords = ','.join(['"%s": %s' % (k, keywordDict[k]) for k in keywordDict.keys()])
                f.write('KEYWORDS = %s' % keywordDict)
        finally:
            self._stop_thread(3)

    def _check_robot_running(self):
        try:
            BuiltIn().get_variable_value('${SUITE SOURCE}')
        except Exception as err:
            self.logger.warn('Robot may not running, stop java process: %s' %
                             err)
            self._stop_thread(1)

    def _init_logger(self):
        robotLogLevels = {
            'TRACE': int(logging.DEBUG / 2),
            'DEBUG': logging.DEBUG,
            'INFO': logging.INFO,
            'HTML': logging.INFO,
            'WARN': logging.WARN
        }
        builtIn = BuiltIn()
        handler = logging.StreamHandler(sys.stdout)
        formatter = logging.Formatter('%(message)s')
        handler.setFormatter(formatter)
        logger = logging.getLogger('SikuliLibraryLogger')
        logger.addHandler(handler)
        level = logging.DEBUG
        try:
            logLevel = builtIn.get_variable_value('${LOG_LEVEL}')
            level = robotLogLevels[logLevel]
        except Exception:
            pass
        logger.setLevel(level)
        return logger

    def _get_free_tcp_port(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind(('localhost', 0))
        sock.listen(1)
        host, port = sock.getsockname()
        self.logger.debug('Free TCP port is: %d' % port)
        sock.close()
        return port

    def _start_sikuli_java_process(self):
        libFolder = os.path.join(os.path.abspath(os.path.dirname(__file__)),
                                 'lib')
        jarList = glob.glob(libFolder + os.sep + '*.jar')
        if len(jarList) != 1:
            raise Exception('Sikuli jar package should be exist in lib folder')
        sikuliJar = jarList[0]
        java = 'java'
        arguments = [
            '-jar', sikuliJar,
            str(self.port),
            self._get_output_folder()
        ]
        self.process = Process()
        if os.getenv("DISABLE_SIKULI_LOG"):
            self.process.start_process(java, *arguments, shell=True)
        else:
            self.process.start_process(java,
                                       *arguments,
                                       shell=True,
                                       stdout=self._output_file(),
                                       stderr=self._err_file())
        self.logger.info('Start sikuli java process on port %s' %
                         str(self.port))
        self._wait_process_started()
        self.logger.info('Sikuli java process is started')

    def _wait_process_started(self):
        url = "http://127.0.0.1:%s/" % str(self.port)
        currentTime = startedTime = time.time()
        started = False
        while (currentTime - startedTime) < self.timeout:
            try:
                urlopen(url).read()
            except Exception:
                currentTime = time.time()
                time.sleep(1.0)
                continue
            started = True
            break
        if not started:
            raise RuntimeError('Start sikuli java process failed!')

    def _output_file(self):
        outputDir = self._get_output_folder()
        outputFile = 'Sikuli_java_stdout_' + str(time.time()) + '.txt'
        return os.path.join(outputDir, outputFile)

    def _err_file(self):
        outputDir = self._get_output_folder()
        errFile = 'Sikuli_java_stderr_' + str(time.time()) + '.txt'
        return os.path.join(outputDir, errFile)

    def _get_output_folder(self):
        outputDir = os.path.abspath(os.curdir)
        try:
            outputDir = BuiltIn().get_variable_value('${OUTPUTDIR}')
        except Exception:
            pass
        return outputDir

    def _connect_remote_library(self):
        remoteUrl = 'http://127.0.0.1:%s/' % str(self.port)
        remote = Remote(remoteUrl)
        self._test_get_keyword_names(remote)
        return remote

    def _test_get_keyword_names(self, remote):
        currentTime = startedTime = time.time()
        started = False
        while (currentTime - startedTime) < self.timeout:
            try:
                remote.get_keyword_names()
            except Exception as err:
                self.logger.warn("Test get_keyword_names failed! %s" % err)
                currentTime = time.time()
                time.sleep(1.0)
                continue
            started = True
            break
        if not started:
            raise RuntimeError('Failed to get_keyword_names!')

    def get_keyword_names(self):
        if self.mode == 'CREATE':
            return self.remote.get_keyword_names() + ['start_sikuli_process']
        return list(KEYWORDS.keys()) + ['start_sikuli_process']
        # return self.remote.get_keyword_names() + ['start_sikuli_process']

    def get_keyword_arguments(self, name):
        if name == 'start_sikuli_process':
            return ['port=None']
        if self.mode == 'CREATE':
            return self.remote.get_keyword_arguments(name)
        return KEYWORDS[name]['arg']

    def get_keyword_documentation(self, name):
        if name == 'start_sikuli_process':
            return self.start_sikuli_process.__doc__
        elif name == '__intro__':
            return SikuliLibrary.__doc__
        elif name == '__init__':
            return getattr(self, name).__doc__
        if self.mode == 'CREATE':
            return self.remote.get_keyword_documentation(name)
        return KEYWORDS[name]['doc']

    def run_keyword(self, name, arguments=[], kwargs={}):
        if name == 'start_sikuli_process':
            return self.start_sikuli_process(*arguments)
        return self.remote.run_keyword(name, arguments, kwargs)

    def _stop_thread(self, timeout):
        def stop():
            time.sleep(float(timeout))
            self.run_keyword('stop_remote_server')

        thread = threading.Thread(target=stop, args=())
        thread.start()
    def update_queue_message(self, MQPropertiesFile, MisysUtilBatfile,
                             *params):
        """| Usage |
         Used to put or delete MQ queue messages.

         PreRequisite : Security feature should be disable in MQ before updating any queue messages. Follow below steps to disable it.

         1.Login to system where MQ is installed.

         2.Make sure the user with which you start your WebSphere  profile should be in mq server and  user should be added to mqm group

         3.Run below commands :

         *  runmqsc <QManager name> [Example: runmqsc MM453]

         *  alter qmgr chlauth(disabled)

         *  restart qmanager

         | Arguments |

         'MQPropertiesFile' = mq.properties file path.

         'MisysUtilBatfile' = 'misys-mq-util' bat file location present under misys-mq-util folder.

         '*params' : Can pass variable number of arguments in mq.properties file to configure Queue.

         Input format for * params :  key=value (example: MQCTestCase=C:\\ITL)

         Example:

         create list variable:

         | *** Variables *** |

         | @{QueueDetails}    QueueManager=xyz    HostName=pqr    QueueName=qm    Channel=SYSTEM.DEF.SVRCONN    Port=1416    InputfilePath=D:\\MQ_Queue_Messages\\message1.txt  |

         |Update Queue Message | ${mqpropertiesfile} | ${MisysUtilBatfile} | @{QueueDetails} |

         """
        # --- check if MQPropertiesFile exist
        if not os.path.exists(MQPropertiesFile):
            raise AssertionError("File not found error :" + MQPropertiesFile +
                                 " doesnot exist")

        # --- check if MisysUtilBatfile exist
        if not os.path.exists(MisysUtilBatfile):
            raise AssertionError("File not found error :" + MisysUtilBatfile +
                                 " doesnot exist")

        # ----- param will hold variable number of parameters that needs to be changed
        param_dict = collections.OrderedDict()
        for param in params:
            parameter = str(param).split("=")
            parameter_name = parameter[0]
            if parameter_name.lower() == 'inputfilepath':
                parameter_value = parameter[1].replace('\\', '\\\\')
            else:
                parameter_value = parameter[1]
            param_dict[parameter_name] = parameter_value

            # --- check if that parameter_name is present in file , if present then update it
            with open(MQPropertiesFile, 'r') as f:
                Filedata = f.read().split("\n")
                columns = list(param_dict.keys())
                for column in columns:

                    for i in range(0, len(Filedata) - 1):
                        if str(column).lower() == str(
                                Filedata[i].split("=")[0]).lower():
                            parametername = Filedata[i].split("=")[0]
                            Filedata[
                                i] = parametername + "=" + param_dict[column]

            with open(MQPropertiesFile, 'w') as fr:
                for data in Filedata:
                    fr.write(data)
                    fr.write("\n")
                fr.close()

        # ------ create cwd for to run bat process
        filedir = MisysUtilBatfile.split("\\")
        batcwd = ""
        for i in range(0, len(filedir) - 1):
            if i < len(filedir) - 2:
                batcwd = batcwd + filedir[i] + "\\"
            else:
                batcwd = batcwd + filedir[i]
        status = Process().run_process(MisysUtilBatfile,
                                       cwd=str(batcwd).strip())
        stdoutput = status.stdout

        if "failed" in stdoutput:
            print(
                " Following data passed in mq.properties file: ------------------------"
            )
            for param in params:
                print(param)
            print("\n---------------Error Details --------------")
            raise AssertionError(stdoutput)

        else:
            print(
                " Following Data updated in mq.properties file: -----------------"
            )
            for param in params:
                print(param)
            return "PASS"
 def __init__(self, cf_cli_path="cf"):
     os.makedirs("./.cf_home", exist_ok=True)
     self._process = Process()
     self.cf_cli_path = cf_cli_path
class CFCliLibrary(object):

    def __init__(self, cf_cli_path="cf"):
        os.makedirs("./.cf_home", exist_ok=True)
        self._process = Process()
        self.cf_cli_path = cf_cli_path

    def login(self, api, user, password, skip_ssl=False):
        api_cmd = ["api", api]
        if skip_ssl:
            api_cmd.append("--skip-ssl-validation")
        self.cf(*api_cmd)
        auth_cmd = ["auth", user, password]
        return self.cf(*auth_cmd)

    def cf(self, *arguments, **configuration):
        configuration["env:CF_HOME"] = "./.cf_home"
        configuration["env:CF_PLUGIN_HOME"] = expanduser("~")
        result = self._process.run_process(self.cf_cli_path, *arguments, **configuration)
        result_msg = result.stdout
        if result_msg is None:
            result_msg = result.stderr
        elif result.stderr is not None:
            result_msg += result.stderr
        if result.rc != 0:
            raise AssertionError(result_msg)
        return result_msg

    def target(self, org, space=None):
        target_cmd = ["target", "-o", org]
        if space is not None:
            target_cmd.append("-s")
            target_cmd.append(space)
        return self.cf(*target_cmd)

    def create_org_with_space_and_target(self, org, space):
        self.cf("create-org", org)
        self.cf("create-space", space, "-o", org)
        return self.target(org, space)

    def create_space_and_target(self, org, space):
        self.cf("create-space", space, "-o", org)
        return self.target(org, space)

    def get_first_buildpack(self, name):
        result = self.cf(*["buildpacks"])
        for line in result.splitlines():
            fields = line.strip().split()
            if len(fields) < 2:
                continue
            if fields[0].startswith(name):
                return fields[0]
            # do the same job for cli v7 which set position first
            if fields[1].startswith(name):
                return fields[1]
        raise AssertionError('Buildpack with {} does not exists'.format(name))

    def delete_org(self, org):
        return self.cf("delete-org", "-f", org)

    def download_and_extract_app(self, appname):
        tfile = tempfile.mkstemp(prefix="cfrobot-cats")
        (fd, filename) = tfile
        os.close(fd)
        tdir = tempfile.mkdtemp(prefix="cfrobot-cats")
        try:
            app_guid = self.cf("app", appname, "--guid")
            self.cf("curl", '/v2/apps/{}/download'.format(app_guid), "--output", filename,
                    **{'stdout': 'DEVNULL', 'stderr': 'DEVNULL'})
            with zipfile.ZipFile(filename, "r") as zip_ref:
                zip_ref.extractall(tdir)
        except Exception as e:
            shutil.rmtree(tdir, ignore_errors=True)
            raise e
        finally:
            os.remove(filename)
        return tdir

    def ensure_feature_flags(self, flags_config):
        feature_flags = {}
        result = self.cf(*["feature-flags"])
        for line in result.splitlines():
            fields = line.strip().split()
            if len(fields) < 2:
                continue
            feature_flags[fields[0]] = fields[1]
        for flag, state in flags_config.items():
            if feature_flags[flag] != state:
                raise AssertionError('Flag {} is not in requested state {}'.format(flag, state))

    def cleanup(self, kill=False):
        self._process.terminate_all_processes(kill)
Пример #15
0
class FormsLibrary(object):
    """Robot Framework library leveraging Java-agents to run [https://github.com/robotframework/SwingLibrary|SwingLibrary]
    keywords on Java-processes.

    To take the library in to use add formslibrary-[version].jar to PYTHONPATH.

    The library contains a simple socket server to communicate with Java agents. When taking the library into use,
    you can specify the port this server uses. Providing the port is optional. If you do not provide one,
    FormsLibrary will ask the OS for an unused port.

    Keywords directly offered by this library on top of SwingLibrary keywords are:
    - [#Application Started|Application Started]
    - [#Ensure Application Should Close|Ensure Application Should Close]
    - [#Log Java System Properties|Log Java System Properties]
    - [#Set Java Tool Options|Set Java Tool Options]
    - [#Start Application|Start Application]
    - [#System Exit|System Exit]
    - [#Switch To Application|Switch To Application]
    - [#Connect To Application|Connect To Application]

    FormsLibrary also introduces two global variables that can be used during testing:
    - ${REMOTESWINGLIBRARYPATH} the location of the formslibrary jar file.
    - ${REMOTESWINGLIBRARYPORT} port used by the agents to communicate with the library - this is needed if a java agent
    is started for example from another machine.

    [https://github.com/ombre42/jrobotremoteserver|jrobotremoteserver]
    that is used by FormsLibrary also offers a keyword:
    - [#Stop Remote Server|Stop Remote Server]

    Following SwingLibrary Keywords are not available through FormsLibrary:
    - Launch Application
    - SwingLibrary version of Start Application
    - Start Application In Separate Thread

    NOTE! [#Get Table Cell Property|Get Table Cell Property] will return the string representation of that property
    and not the actual object. Complex objects are not passed through Remote library interface.

    Examples:
    | * Settings * |
    | Library | FormsLibrary |
    | * Test Cases * |
    | Testing java application |
    | | Start Application | myjavaapp | java -jar myjava.jar |
    | | Select Window  | My App |
    | | Ensure Application Should Close | 60 seconds | Push Button | Exit |


    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    KEYWORDS = ['system_exit', 'start_application', 'application_started', 'switch_to_application',
                'ensure_application_should_close', 'log_java_system_properties', 'set_java_tool_options',
                'connect_to_application']
    REMOTES = {}
    CURRENT = None
    PROCESS = Process()
    ROBOT_NAMESPACE_BRIDGE = RobotLibraryImporter()
    TIMEOUT = 60
    PORT = None
    APHOST = None
    APPORT = None
    AGENT_PATH = os.path.abspath(os.path.dirname(__file__))
    _output_dir = ''

    def __init__(self, port=None, aphost='127.0.0.1', apport=None, debug=False,
                 close_security_dialogs=False, __reload=False):
        """
        *port*: optional port for the server receiving connections from remote agents

        *apport*: optional port for server receiving connections of remote agent.
        Should be used if you want to reconnect to same application between robot runs.

        *aphost*: opptional network address of application we want to connect into

        *debug*: optional flag that will start agent in mode with more logging for troubleshooting (set to TRUE to enable)

        *close_security_dialogs*: optional flag for automatic security dialogs closing (set to TRUE to enable)

        NOTE! with special value 'TEST' starts a test application for documentation generation
        purposes `python -m robot.libdoc FormsLibrary::TEST FormsLibrary.html`

        NOTE! FormsLibrary is a so called Global Scope library. This means when it is imported once it will be
        available until end of robot run. If Robot encounters another import of FormsLibrary with different
        import parameters the library will be reloaded and new options will be in use. You should be careful if you
        import FormsLibrary with different options in multiple test suites and resources.

        """
        if not __reload:
            FormsLibrary.CURRENT = None
            global REMOTE_AGENTS_LIST
            REMOTE_AGENTS_LIST = AgentList()
            FormsLibrary.PORT = None
        self.ROBOT_NAMESPACE_BRIDGE.set_args(port, aphost, apport, debug, close_security_dialogs)
        if FormsLibrary.PORT is None:
            FormsLibrary.PORT = self._start_port_server(0 if port == 'TEST' else port or 0)
        FormsLibrary.APHOST = aphost
        FormsLibrary.APPORT = apport
        self._create_env(bool(debug), bool(close_security_dialogs))
        if port == 'TEST':
            self.start_application('docgenerator', 'java -jar %s' % FormsLibrary.AGENT_PATH, timeout=4.0)

    @property
    def current(self):
        if not self.CURRENT:
            return None
        return self.REMOTES[self.CURRENT][0]

    def _start_port_server(self, port):
        address = ('0.0.0.0', int(port))
        server = SocketServer.TCPServer(address, SimpleServer)
        server.allow_reuse_address = True
        #t = threading.Thread(name="FormsLibrary registration server thread",
        #                     target=server.serve_forever)
        t = threading.Thread(name="FormsLibrary registration server thread",
                             target=server.serve_forever, args=(0.01,))
        t.setDaemon(True)
        t.start()
        return server.server_address[1]

    def _create_env(self, debug, close_security_dialogs):
        agent_command = ' -agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n -javaagent:"%s"=127.0.0.1:%s' % (FormsLibrary.AGENT_PATH, FormsLibrary.PORT)
        if FormsLibrary.APPORT:
            agent_command += ':APPORT=%s' % FormsLibrary.APPORT
        if debug:
            agent_command += ':DEBUG'
        if close_security_dialogs:
            agent_command += ':CLOSE_SECURITY_DIALOGS'
        self._agent_command = agent_command
        try:
            BuiltIn().set_global_variable('\${REMOTESWINGLIBRARYPATH}', self._escape_path(FormsLibrary.AGENT_PATH))
            BuiltIn().set_global_variable('\${REMOTESWINGLIBRARYPORT}', FormsLibrary.PORT)
            self._output_dir = BuiltIn().get_variable_value('${OUTPUTDIR}')
        except RobotNotRunningError:
            pass
        logger.info(agent_command)


    def _escape_path(self, text):
        return text.replace("\\","\\\\")

    @contextmanager
    def _agent_java_tool_options(self):
        old_tool_options = os.environ.get('JAVA_TOOL_OPTIONS', '')
        old_options = os.environ.get('_JAVA_OPTIONS', '')
        logger.debug("Picked old JAVA_TOOL_OPTIONS='%s'" % old_tool_options)
        logger.debug("Picked old _JAVA_OPTIONS='%s'" % old_options)
        self.set_java_tool_options()
        try:
            yield
        finally:
            os.environ['JAVA_TOOL_OPTIONS'] = old_tool_options
            os.environ['_JAVA_OPTIONS'] = old_options
            logger.debug("Returned old JAVA_TOOL_OPTIONS='%s'" % old_tool_options)
            logger.debug("Returned old _JAVA_OPTIONS='%s'" % old_options)

    def set_java_tool_options(self):
        """Sets the JAVA_TOOL_OPTIONS to include FormsLibrary Agent and
        the _JAVA_OPTIONS to set a temporary policy granting all permissions.

        FormsLibrary Agent is normally enabled by `Start Application` by
        setting the JAVA_TOOL_OPTIONS environment variable only during
        that keyword call. So java processes started by other commands wont
        normally use the FormsLibrary Agent. This keyword sets that same
        environment variable to be used always. So all java processes started
        after this will use the Agent. This methods also creates temporary
        Java policy file which grants all permissions. This file is set as
        policy for each java command call.
        """
        os.environ['JAVA_TOOL_OPTIONS'] = self._agent_command
        logger.info("Set JAVA_TOOL_OPTIONS='%s'" % self._agent_command)
        with tempfile.NamedTemporaryFile(prefix='grant_all_', suffix='.policy', delete=False) as t:
            text = b"""
                grant {
                    permission java.security.AllPermission;
                };
                """
            t.write(text)
        java_policy = '-Djava.security.policy="%s"' % t.name
        variables = BuiltIn().get_variables()
        output_dir = variables['${OUTPUTDIR}']
        robot_output_dir = ' -Drobot.output_dir="%s"' % output_dir
        os.environ['_JAVA_OPTIONS'] = java_policy + robot_output_dir
        logger.info("Set _JAVA_OPTIONS='%s'" % os.environ['_JAVA_OPTIONS'])


    def start_application(self, alias, command, timeout=60, name_contains=None):
        """Starts the process in the `command` parameter  on the host operating system.
        The given alias is stored to identify the started application in FormsLibrary.

        timeout (default 60) is timeout in seconds.
        name_contains is a text that must be part of the name of the java process that we are connecting to.
        name_contains helps in situations where multiple java-processes are started.
        To see the name of the connecting java agents run tests with --loglevel DEBUG.

        """
        stdout = "remote_stdout.txt"
        stderr = "remote_stderr.txt"
        logger.info('<a href="%s">Link to stdout</a>' % stdout, html=True)
        logger.info('<a href="%s">Link to stderr</a>' % stderr, html=True)



        print("starting process ", command )
        REMOTE_AGENTS_LIST.set_received_to_old()
        with self._agent_java_tool_options():
            self.PROCESS.start_process(command, alias=alias, shell=True, 
                                       stdout=self._output(stdout),
                                       stderr=self._output(stderr))
        try:
            self._application_started(alias, timeout=timeout, name_contains=name_contains, accept_old=False)
        except TimeoutError:
            raise
        except Exception:
            logger.info("Failed to start application: %s" % traceback.format_exc())
            # FIXME: this may hang, how is that possible?
            result = self.PROCESS.wait_for_process(timeout=0.01)
            if result:
                logger.info('STDOUT: %s' % result.stdout)
                logger.info('STDERR: %s' % result.stderr)
            else:
                logger.info("Process is running, but application startup failed")
            raise

    def connect_to_application(self, alias, timeout=60, name_contains=None):
        """Connects to application that was started in earlier RobotFramework run.
        *apport* import option needs to be set to use this keyword.
        Application that we want to connect into needs to be started by Start Application keyword when using same *apport* value.
        """
        try:
            self._application_started(alias, timeout=timeout, name_contains=name_contains, accept_old=True)
        except TimeoutError:
            raise
        except Exception:
            logger.info("Failed to connect to application: %s" % traceback.format_exc())
            result = self.PROCESS.wait_for_process(timeout=0.01)
            if result:
                logger.info('STDOUT: %s' % result.stdout)
                logger.info('STDERR: %s' % result.stderr)
            else:
                logger.info("Process is running, but application startup failed")
            raise

    def _output(self, filename):
        return os.path.join(self._output_dir, filename)

    def application_started(self, alias, timeout=60, name_contains=None):
        """Detects new FormsLibrary Java-agents in applications that are started without
        using the Start Application -keyword. The given alias is stored to identify the
        started application in FormsLibrary.
        Subsequent keywords will be passed on to this application."""
        self._application_started(alias, timeout, name_contains, accept_old=True)

    def _application_started(self, alias, timeout=60, name_contains=None, accept_old=True):
        self.TIMEOUT = timestr_to_secs(timeout)
        if (FormsLibrary.APPORT):
            url = '%s:%s'%(FormsLibrary.APHOST, FormsLibrary.APPORT)
        else:
            url = self._get_agent_address(name_contains, accept_old)
        logger.info('connecting to started application at %s' % url)
        self._initialize_remote_libraries(alias, url)
        FormsLibrary.CURRENT = alias
        logger.debug('modifying robot framework namespace')
        self.ROBOT_NAMESPACE_BRIDGE.re_import_formslibrary()
        logger.info('connected to started application at %s' % url)

    def _initialize_remote_libraries(self, alias, url):
        swinglibrary = Remote(url)
        logger.debug('remote swinglibrary instantiated')
        services = Remote(url + '/services')
        logger.debug('remote services instantiated')
        self.REMOTES[alias] = [swinglibrary, services]

    def _get_agent_address(self, name_pattern, accept_old):
        while True:
            if not REMOTE_AGENTS_LIST.get(accept_old):
                REMOTE_AGENTS_LIST.agent_received.clear()
            REMOTE_AGENTS_LIST.agent_received.wait(timeout=self.TIMEOUT)
            if not REMOTE_AGENTS_LIST.agent_received.isSet():
                raise FormsLibraryTimeoutError('Agent port not received before timeout')
            for address, name, age in reversed(REMOTE_AGENTS_LIST.get(accept_old)):
                if name_pattern is None or name_pattern in name:
                    REMOTE_AGENTS_LIST.remove(address, name, age)
                    return address
            time.sleep(0.1)

    def _ping_until_timeout(self, timeout):
        timeout = float(timeout)
        delta = min(0.1, timeout)
        endtime = timeout+time.time()
        while endtime > time.time():
            self._run_from_services('ping')
            time.sleep(delta)

    def _run_from_services(self, kw, *args, **kwargs):
        return self.REMOTES[FormsLibrary.CURRENT][1].run_keyword(kw, args, kwargs)

    @run_keyword_variant(resolve=1)
    def ensure_application_should_close(self, timeout, kw, *args):
        """ Runs the given keyword and waits until timeout for the application to close .
        If the application doesn't close, the keyword will take a screenshot and close the application
        and after that it will fail.
        In many cases calling the keyword that will close the application under test brakes the remote connection.
        This exception is ignored as it is expected by this keyword.
        Other exceptions will fail this keyword as expected.
        """
        with self._run_and_ignore_connection_lost():
            BuiltIn().run_keyword(kw, *args)
        try:
            self._application_should_be_closed(timeout=timestr_to_secs(timeout))
        except FormsLibraryTimeoutError as t:
            logger.warn('Application is not closed before timeout - killing application')
            self._take_screenshot()
            self.system_exit()
            raise

    def _take_screenshot(self):
        logdir = self._get_log_dir()
        filepath = os.path.join(logdir, 'formslibrary-screenshot%s.png' % int(time.time()*1000))
        self._run_from_services('takeScreenshot', filepath)
        logger.info('<img src="%s"></img>' % get_link_path(filepath, logdir), html=True)

    def _get_log_dir(self):
        variables = BuiltIn().get_variables()
        logfile = variables['${LOG FILE}']
        if logfile != 'NONE':
            return os.path.dirname(logfile)
        return variables['${OUTPUTDIR}']

    def _application_should_be_closed(self, timeout):
        with self._run_and_ignore_connection_lost():
            self._ping_until_timeout(timeout)
            raise FormsLibraryTimeoutError('Application was not closed before timeout')

    @contextmanager
    def _run_and_ignore_connection_lost(self):
        try:
            yield
        except RuntimeError as r: # disconnection from remotelibrary
            if 'Connection to remote server broken:' in r.args[0]:
                logger.info('Connection died as expected')
                return
            raise
        except HandlerExecutionFailed as e: # disconnection from xmlrpc wrapped in robot keyword
            if any(elem in e.args[0] for elem in ('Connection to remote server broken:', 'ProtocolError')):
                logger.info('Connection died as expected')
                return
            raise
        except ProtocolError as r: # disconnection from xmlrpc in jython on some platforms
            logger.info('Connection died as expected')
            return

    def system_exit(self, exit_code=1):
        """ Uses the FormsLibrary java agent to call system exit for the current java application.
        """
        with self._run_and_ignore_connection_lost():
            self._run_from_services('systemExit', exit_code)

    def switch_to_application(self, alias):
        """Switches between applications that are known to FormsLibrary.
        The application is identified using the alias.
        Subsequent keywords will be passed on to this application."""
        FormsLibrary.CURRENT = alias

    def log_java_system_properties(self):
        """ log and return java properties and environment information from the current java application.
        """
        env = self._run_from_services('getEnvironment')
        logger.info(env)
        return env

    def get_keyword_names(self):
        # get_keyword names takes argument `attempts` which makes it
        # wait 0,1,2,3,4,...,attempts-1 seconds in those attempts
        # we want to make total wait time to be close to original TIMEOUT
        # to do it we find minimal n which satisfies ((n)*(n-1))/2 >= TIMEOUT
        # solution is ceil(sqrt(TIMEOUT*2*4+1)/2+0.5)
        attempts = int(math.ceil(math.sqrt(FormsLibrary.TIMEOUT*2*4+1)/2+0.5))
        overrided_keywords = ['startApplication', 'launchApplication', 'startApplicationInSeparateThread']  
        
        if self.current:
            return FormsLibrary.KEYWORDS + [kw for
                                      kw in self.current.get_keyword_names(attempts=attempts)
                                      if kw not in overrided_keywords]
        return FormsLibrary.KEYWORDS + [kw for kw in FormsLibrary_Keywords.keywords
                                              if kw not in overrided_keywords]


    def get_keyword_arguments(self, name):
        if name in FormsLibrary.KEYWORDS:
            return self._get_args(name)
        if self.current:
            return self.current.get_keyword_arguments(name)
        return FormsLibrary_Keywords.keyword_arguments[name]

    def _get_args(self, method_name):
        spec = inspect.getargspec(getattr(self, method_name))
        args = spec[0][1:]
        if spec[3]:
            for i, item in enumerate(reversed(spec[3])):
                args[-i-1] = args[-i-1]+'='+str(item)
        if spec[1]:
            args += ['*'+spec[1]]
        if spec[2]:
            args += ['**'+spec[2]]
        return args

    def get_keyword_documentation(self, name):
        if name == '__intro__':
            return FormsLibrary.__doc__
        if name in FormsLibrary.KEYWORDS or name == '__init__':
            return getattr(self, name).__doc__
        if self.current:
        	return self.current.get_keyword_documentation(name)
        return FormsLibrary_Keywords.keyword_documentation[name]

    def run_keyword(self, name, arguments, kwargs):
		if unicode(name).startswith(u"capture"):
			path = self.current.run_keyword(name, arguments, kwargs)
			defaultLogger.info(path, html=True)
			logger.info('<a href="%s"><img src="%s" width="800px"></a>' % (path, path), html=True)
			logger.info("<a href=\"%s\">Screenshot</a> saved." % (path), html=True)
			return path
		if name in FormsLibrary.KEYWORDS:
			return getattr(self, name)(*arguments, **kwargs)        
		if self.current:
			return self.current.run_keyword(name, arguments, kwargs)
		if name in FormsLibrary_Keywords.keywords:
			raise Exception("To use this keyword, you need to connect to the application first.")
Пример #16
0
 def __init__(self):
     Screenshot.__init__(self,"C:\\WebServer\\Results")
     XML.__init__(self,use_lxml=True)
     Process.__init__(self)
Пример #17
0
class RemoteSwingLibrary(object):
    """Robot Framework library leveraging Java-agents to run [https://github.com/robotframework/SwingLibrary|SwingLibrary]
    keywords on Java-processes.

    To take the library in to use add remoteswinglibrary-[version].jar to PYTHONPATH.

    The library contains a simple socket server to communicate with Java agents. When taking the library into use,
    you can specify the port this server uses. Providing the port is optional. If you do not provide one,
    RemoteSwingLibrary will ask the OS for an unused port.

    Keywords directly offered by this library on top of SwingLibrary keywords are:
    - [#Application Started|Application Started]
    - [#Ensure Application Should Close|Ensure Application Should Close]
    - [#Log Java System Properties|Log Java System Properties]
    - [#Reinitiate|Reinitiate]
    - [#Set Java Tool Options|Set Java Tool Options]
    - [#Start Application|Start Application]
    - [#Switch To Application|Switch To Application]
    - [#System Exit|System Exit]

    RemoteSwingLibrary also introduces two global variables that can be used during testing:
    - ${REMOTESWINGLIBRARYPATH} the location of the remoteswinglibrary jar file.
    - ${REMOTESWINGLIBRARYPORT} port used by the agents to communicate with the library - this is needed if a java agent
    is started for example from another machine.

    [https://github.com/ombre42/jrobotremoteserver|jrobotremoteserver]
    that is used by RemoteSwingLibrary also offers a keyword:
    - [#Stop Remote Server|Stop Remote Server]

    Following SwingLibrary Keywords are not available through RemoteSwingLibrary:
    - Launch Application
    - SwingLibrary version of Start Application
    - Start Application In Separate Thread

    NOTE! [#Get Table Cell Property|Get Table Cell Property] will return the string representation of that property
    and not the actual object. Complex objects are not passed through Remote library interface.

    Examples:
    | * Settings * |
    | Library | RemoteSwingLibrary |
    | * Test Cases * |
    | Testing java application |
    | | Start Application | myjavaapp | java -jar myjava.jar |
    | | Select Window  | My App |
    | | Ensure Application Should Close | 15 seconds | Push Button | Exit |


    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    KEYWORDS = [
        'system_exit', 'start_application', 'application_started',
        'switch_to_application', 'ensure_application_should_close',
        'log_java_system_properties', 'set_java_tool_options', 'reinitiate'
    ]
    REMOTES = {}
    CURRENT = None
    PROCESS = Process()
    TIMEOUT = 60
    PORT = None
    DEBUG = None
    AGENT_PATH = os.path.abspath(os.path.dirname(__file__))
    _output_dir = ''

    def __init__(self, port=0, debug=False):
        """
        *port*: optional port for the server receiving connections from remote agents

        *debug*: optional flag that will start agent in mode with more logging for troubleshooting (set to TRUE to enable)

        NOTE! RemoteSwingLibrary is a so called Global Scope library. This means when it is imported once it will be
        available until end of robot run. Parameters used in imports from others suites will be ignored.
        If you need to change import options between suites, please use *Reinitiate* keyword.

        """
        if RemoteSwingLibrary.DEBUG is None:
            RemoteSwingLibrary.DEBUG = _tobool(debug)
        if RemoteSwingLibrary.PORT is None:
            RemoteSwingLibrary.PORT = self._start_port_server(int(port))
        try:
            BuiltIn().set_global_variable(
                '\${REMOTESWINGLIBRARYPATH}',
                self._escape_path(RemoteSwingLibrary.AGENT_PATH))
            BuiltIn().set_global_variable('\${REMOTESWINGLIBRARYPORT}',
                                          RemoteSwingLibrary.PORT)
            self._output_dir = BuiltIn().get_variable_value('${OUTPUTDIR}')
        except RobotNotRunningError:
            pass

    def reinitiate(self, port=0, debug=False):
        """
        Restarts RemoteSwingLibrary with new import parameters.
        """
        RemoteSwingLibrary.CURRENT = None
        global REMOTE_AGENTS_LIST
        REMOTE_AGENTS_LIST = AgentList()
        RemoteSwingLibrary.PORT = None
        RemoteSwingLibrary.DEBUG = None
        RemoteSwingLibrary.TIMEOUT = 60
        self.__init__(port, debug)

    @property
    def current(self):
        if not self.CURRENT:
            return None
        return self.REMOTES[self.CURRENT][0]

    def _start_port_server(self, port):
        address = ('0.0.0.0', int(port))
        server = SocketServer.TCPServer(address, SimpleServer)
        server.allow_reuse_address = True
        #t = threading.Thread(name="RemoteSwingLibrary registration server thread",
        #                     target=server.serve_forever)
        t = threading.Thread(
            name="RemoteSwingLibrary registration server thread",
            target=server.serve_forever,
            args=(0.01, ))
        t.setDaemon(True)
        t.start()
        return server.server_address[1]

    def _create_env(self, close_security_dialogs=False, remote_port=0):
        agent_command = '-javaagent:"%s"=127.0.0.1:%s' % (
            RemoteSwingLibrary.AGENT_PATH, RemoteSwingLibrary.PORT)
        if int(remote_port):
            agent_command += ':APPORT=%s' % remote_port
        if RemoteSwingLibrary.DEBUG:
            agent_command += ':DEBUG'
        if _tobool(close_security_dialogs):
            agent_command += ':CLOSE_SECURITY_DIALOGS'
        self._agent_command = agent_command
        logger.info(agent_command)

    def _escape_path(self, text):
        return text.replace("\\", "\\\\")

    @contextmanager
    def _agent_java_tool_options(self, close_security_dialogs, remote_port):
        old_tool_options = os.environ.get('JAVA_TOOL_OPTIONS', '')
        old_options = os.environ.get('_JAVA_OPTIONS', '')
        logger.debug("Picked old JAVA_TOOL_OPTIONS='%s'" % old_tool_options)
        logger.debug("Picked old _JAVA_OPTIONS='%s'" % old_options)
        self.set_java_tool_options(close_security_dialogs, remote_port)
        try:
            yield
        finally:
            os.environ['JAVA_TOOL_OPTIONS'] = old_tool_options
            os.environ['_JAVA_OPTIONS'] = old_options
            logger.debug("Returned old JAVA_TOOL_OPTIONS='%s'" %
                         old_tool_options)
            logger.debug("Returned old _JAVA_OPTIONS='%s'" % old_options)

    def set_java_tool_options(self,
                              close_security_dialogs=True,
                              remote_port=0):
        """Sets the JAVA_TOOL_OPTIONS to include RemoteSwingLibrary Agent and
        the _JAVA_OPTIONS to set a temporary policy granting all permissions.

        RemoteSwingLibrary Agent is normally enabled by `Start Application` by
        setting the JAVA_TOOL_OPTIONS environment variable only during
        that keyword call. So java processes started by other commands wont
        normally use the RemoteSwingLibrary Agent. This keyword sets that same
        environment variable to be used always. So all java processes started
        after this will use the Agent. This methods also creates temporary
        Java policy file which grants all permissions. This file is set as
        policy for each java command call.
        """
        close_security_dialogs = _tobool(close_security_dialogs)
        self._create_env(close_security_dialogs, remote_port)
        os.environ['JAVA_TOOL_OPTIONS'] = self._agent_command
        logger.debug("Set JAVA_TOOL_OPTIONS='%s'" % self._agent_command)
        with tempfile.NamedTemporaryFile(prefix='grant_all_',
                                         suffix='.policy',
                                         delete=False) as t:
            text = b"""
                grant {
                    permission java.security.AllPermission;
                };
                """
            t.write(text)
        java_policy = '-Djava.security.policy="%s"' % t.name
        os.environ['_JAVA_OPTIONS'] = java_policy
        logger.debug("Set _JAVA_OPTIONS='%s'" % java_policy)

    def start_application(self,
                          alias,
                          command,
                          timeout=60,
                          name_contains="",
                          close_security_dialogs=False,
                          remote_port=0):
        """Starts the process in the `command` parameter  on the host operating system.
        The given alias is stored to identify the started application in RemoteSwingLibrary.

        *timeout* (default 60) is timeout in seconds.
        *name_contains* is a text that must be part of the name of the java process that we are connecting to.
        *name_contains* helps in situations where multiple java-processes are started.
        To see the name of the connecting java agents run tests with --loglevel DEBUG.
        *remote_port* forces RSL agent to run on specific port, this is useful if you want to
        connect to this application later from another robot run.

        """
        close_security_dialogs = _tobool(close_security_dialogs)
        stdout = "remote_stdout_" + str(uuid.uuid4()) + '.txt'
        stderr = "remote_stderr_" + str(uuid.uuid4()) + '.txt'
        logger.info('<a href="%s">Link to stdout</a>' % stdout, html=True)
        logger.info('<a href="%s">Link to stderr</a>' % stderr, html=True)
        REMOTE_AGENTS_LIST.set_received_to_old()
        with self._agent_java_tool_options(close_security_dialogs,
                                           remote_port):
            self.PROCESS.start_process(command,
                                       alias=alias,
                                       shell=True,
                                       stdout=self._output(stdout),
                                       stderr=self._output(stderr))
        try:
            self._application_started(alias,
                                      timeout,
                                      name_contains,
                                      remote_port,
                                      accept_old=False)
        except TimeoutError:
            raise
        except Exception:
            logger.info("Failed to start application: %s" %
                        traceback.format_exc())
            # FIXME: this may hang, how is that possible?
            result = self.PROCESS.wait_for_process(timeout=0.01)
            if result:
                logger.info('STDOUT: %s' % result.stdout)
                logger.info('STDERR: %s' % result.stderr)
            else:
                logger.info(
                    "Process is running, but application startup failed")
            raise

    def _output(self, filename):
        return os.path.join(self._output_dir, filename)

    def application_started(self,
                            alias,
                            timeout=60,
                            name_contains="",
                            remote_port=0,
                            remote_host="127.0.0.1"):
        """Detects new RemoteSwingLibrary Java-agents in applications that are started without
        using the Start Application -keyword. The given alias is stored
        to identify the started application in RemoteSwingLibrary.
        Subsequent keywords will be passed on to this application. Agents in application
        started in previous robot runs can't be detected automatically, so you have to use *remote_port* parameter.
        """
        self._application_started(alias, timeout, name_contains, remote_port,
                                  remote_host)

    def _wait_for_api(self, url):
        logger.info('waiting for api at %s' % url)
        attempts = int(RemoteSwingLibrary.TIMEOUT)
        for i in range(attempts):
            try:
                result = self._run_from_services('ping')
                logger.info('api is ready')
                return result
            except Exception as err:
                error = err
            time.sleep(1)
        raise RuntimeError('Connecting to api at %s has failed: %s' %
                           (url, error))

    def _application_started(self,
                             alias,
                             timeout=60,
                             name_contains="",
                             remote_port=0,
                             remote_host="127.0.0.1",
                             accept_old=True):
        RemoteSwingLibrary.TIMEOUT = timestr_to_secs(timeout)
        if remote_port:
            url = '%s:%s' % (remote_host, remote_port)
            REMOTE_AGENTS_LIST.remove(url)
        else:
            url = self._get_agent_address(name_contains, accept_old)
        logger.info('connecting to started application at %s' % url)
        self._initialize_remote_libraries(alias, url)
        RemoteSwingLibrary.CURRENT = alias
        self._wait_for_api(url)
        logger.info('connected to started application at %s' % url)

    def _initialize_remote_libraries(self, alias, url):
        swinglibrary = Remote(url)
        logger.debug('remote swinglibrary instantiated')
        services = Remote(url + '/services')
        logger.debug('remote services instantiated')
        self.REMOTES[alias] = [swinglibrary, services]

    def _get_agent_address(self, name_pattern, accept_old):
        while True:
            if not REMOTE_AGENTS_LIST.get(accept_old):
                REMOTE_AGENTS_LIST.agent_received.clear()
            REMOTE_AGENTS_LIST.agent_received.wait(
                timeout=RemoteSwingLibrary.TIMEOUT)
            if not REMOTE_AGENTS_LIST.agent_received.isSet():
                raise RemoteSwingLibraryTimeoutError(
                    'Agent port not received before timeout')
            for address, name, age in reversed(
                    REMOTE_AGENTS_LIST.get(accept_old)):
                if name_pattern is None or name_pattern in name:
                    REMOTE_AGENTS_LIST.remove(address)
                    return address
            time.sleep(0.1)

    def _ping_until_timeout(self, timeout):
        timeout = float(timeout)
        delta = min(0.1, timeout)
        endtime = timeout + time.time()
        while endtime > time.time():
            self._run_from_services('ping')
            time.sleep(delta)

    def _run_from_services(self, kw, *args, **kwargs):
        return self.REMOTES[RemoteSwingLibrary.CURRENT][1].run_keyword(
            kw, args, kwargs)

    @run_keyword_variant(resolve=1)
    def ensure_application_should_close(self, timeout, kw, *args):
        """ Runs the given keyword and waits until timeout for the application to close .
        If the application doesn't close, the keyword will take a screenshot and close the application
        and after that it will fail.
        In many cases calling the keyword that will close the application under test brakes the remote connection.
        This exception is ignored as it is expected by this keyword.
        Other exceptions will fail this keyword as expected.
        """
        with self._run_and_ignore_connection_lost():
            BuiltIn().run_keyword(kw, *args)
        try:
            self._application_should_be_closed(
                timeout=timestr_to_secs(timeout))
        except RemoteSwingLibraryTimeoutError as t:
            logger.warn(
                'Application is not closed before timeout - killing application'
            )
            self._take_screenshot()
            self.system_exit()
            raise

    def _take_screenshot(self):
        logdir = self._get_log_dir()
        filepath = os.path.join(
            logdir,
            'remoteswinglibrary-screenshot%s.png' % int(time.time() * 1000))
        self._run_from_services('takeScreenshot', filepath)
        logger.info('<img src="%s"></img>' % get_link_path(filepath, logdir),
                    html=True)

    def _get_log_dir(self):
        variables = BuiltIn().get_variables()
        logfile = variables['${LOG FILE}']
        if logfile != 'NONE':
            return os.path.dirname(logfile)
        return variables['${OUTPUTDIR}']

    def _application_should_be_closed(self, timeout):
        with self._run_and_ignore_connection_lost():
            self._ping_until_timeout(timeout)
            raise RemoteSwingLibraryTimeoutError(
                'Application was not closed before timeout')

    @contextmanager
    def _run_and_ignore_connection_lost(self):
        try:
            yield
        except RuntimeError as r:  # disconnection from remotelibrary
            if 'Connection to remote server broken:' in r.args[0]:
                logger.info('Connection died as expected')
                return
            raise
        except HandlerExecutionFailed as e:  # disconnection from xmlrpc wrapped in robot keyword
            if any(elem in e.args[0]
                   for elem in ('Connection to remote server broken:',
                                'ProtocolError')):
                logger.info('Connection died as expected')
                return
            raise
        except ProtocolError as r:  # disconnection from xmlrpc in jython on some platforms
            logger.info('Connection died as expected')
            return

    def system_exit(self, exit_code=1):
        """ Uses the RemoteSwingLibrary java agent to call system exit for the current java application.
        """
        with self._run_and_ignore_connection_lost():
            self._run_from_services('systemExit', exit_code)
# try closing the attached process (if started with start_application) to close any open file handles
        try:
            self.PROCESS.wait_for_process(handle=RemoteSwingLibrary.CURRENT,
                                          timeout=0.01)
        except:
            return

    def switch_to_application(self, alias):
        """Switches between applications that are known to RemoteSwingLibrary.
        The application is identified using the alias.
        Subsequent keywords will be passed on to this application."""
        RemoteSwingLibrary.CURRENT = alias

    def log_java_system_properties(self):
        """ log and return java properties and environment information from the current java application.
        """
        env = self._run_from_services('getEnvironment')
        logger.info(env)
        return env

    def get_keyword_names(self):
        overrided_keywords = [
            'startApplication', 'launchApplication',
            'startApplicationInSeparateThread'
        ]
        return RemoteSwingLibrary.KEYWORDS + [
            kw for kw in swinglibrary.keywords if kw not in overrided_keywords
        ]

    def get_keyword_arguments(self, name):
        if name in RemoteSwingLibrary.KEYWORDS:
            return self._get_args(name)
        return swinglibrary.keyword_arguments[name]

    def _get_args(self, method_name):
        spec = inspect.getargspec(getattr(self, method_name))
        args = spec[0][1:]
        if spec[3]:
            for i, item in enumerate(reversed(spec[3])):
                args[-i - 1] = args[-i - 1] + '=' + str(item)
        if spec[1]:
            args += ['*' + spec[1]]
        if spec[2]:
            args += ['**' + spec[2]]
        return args

    def get_keyword_documentation(self, name):
        if name == '__intro__':
            return RemoteSwingLibrary.__doc__
        if name in RemoteSwingLibrary.KEYWORDS or name == '__init__':
            return getattr(self, name).__doc__
        return swinglibrary.keyword_documentation[name]

    def run_keyword(self, name, arguments, kwargs):
        if name in RemoteSwingLibrary.KEYWORDS:
            return getattr(self, name)(*arguments, **kwargs)
        if self.current:
            return self.current.run_keyword(name, arguments, kwargs)
        if name in swinglibrary.keywords:
            raise Exception(
                "To use this keyword you need to connect to application first."
            )
Пример #18
0
class RemoteSwingLibrary(object):
    """RemoteSwingLibrary is a Robot Framework library leveraging Java-agents to run
    [https://github.com/robotframework/SwingLibrary|SwingLibrary] keywords on Java-processes.

    To take the library into use add ``remoteswinglibrary-[version].jar`` to ``PYTHONPATH``.

    The library contains a simple socket server to communicate with Java agents. When taking the library into use,
    you can specify the port this server uses. Providing the port is optional. If you do not provide one,
    RemoteSwingLibrary will ask the OS for an unused port.

    Keywords directly offered by this library on top of SwingLibrary keywords are:
    - `Application Started`
    - `Ensure Application Should Close`
    - `Log Java System Properties`
    - `Reinitiate`
    - `Set Java Tool Options`
    - `Start Application`
    - `Switch To Application`
    - `System Exit`

    RemoteSwingLibrary also introduces two global variables that can be used during testing:
    - ``${REMOTESWINGLIBRARYPATH}`` the location of the remoteswinglibrary jar file.
    - ``${REMOTESWINGLIBRARYPORT}`` port used by the agents to communicate with the library - this is needed if a java
    agent is started for example from another machine.

    The following SwingLibrary keywords are not available through RemoteSwingLibrary:
    - `Launch Application`
    - SwingLibrary version of `Start Application`
    - `Start Application In Separate Thread`

     *Note:* `Get Table Cell Property` will return the string representation of that property
    and not the actual object. Complex objects are not passed through Remote library interface.

    = Locating components =
    Most of the keywords that operate on a visible component take an argument named ``identifier``, which is used to
    locate the element. The first matching element is operated on, according to these rules:

    - If the ``identifier`` is a number, it is used as a zero-based index for the particular component type in the
     current context. Using indices is, however, fragile and is strongly discouraged.
    - If the ``identifier`` matches to internal name of a component (set using ``setName`` method in Java code),
    that component is chosen.
    - For components that have visible text (e.g. buttons), ``identifier`` is also matched against that.
    - Text field keywords also support accessing awt-text fields by prefixing the identifier with ``awt=``.

    Keyword `List Components in Context` lists all components and their names and indices in a given context.

    = Regular expressions =

    More information about Java regular expressions and patterns can be found here:
    http://java.sun.com/docs/books/tutorial/essential/regex/ and here:
    http://java.sun.com/javase/7/docs/api/java/util/regex/Pattern.html.

    = Example =

    | ***** Settings *****
    | Documentation          This example demonstrates starting a Java application
    | ...                    using RemoteSwingLibrary
    |
    | Library                `RemoteSwingLibrary`
    |
    | ***** Test Cases *****
    | Testing Java application
    |     `Start Application`                myjavaapp   java -jar myjava.jar
    |     `Select Window`                    My App
    |     `Ensure Application Should Close`  15 seconds  Push Button            Exit

    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = __version__
    KEYWORDS = [
        'system_exit', 'start_application', 'application_started',
        'switch_to_application', 'ensure_application_should_close',
        'log_java_system_properties', 'set_java_tool_options', 'reinitiate'
    ]
    REMOTES = {}
    CURRENT = None
    PROCESS = Process()
    TIMEOUT = 60
    PORT = None
    DEBUG = None
    AGENT_PATH = os.path.abspath(os.path.dirname(__file__))
    POLICY_FILE = None
    _output_dir = ''
    JAVA9_OR_NEWER = False

    def _remove_policy_file(self):
        if self.POLICY_FILE and os.path.isfile(self.POLICY_FILE):
            os.remove(self.POLICY_FILE)
            self.POLICY_FILE = None

    def _java9_or_newer(self):
        try:
            version = float(self._read_java_version())
        except:
            logger.warn(
                'Failed to auto-detect Java version. Assuming Java is older than 9. To run in newer Java'
                'compatibility mode set java9_or_newer=True when importing the library.'
            )
            return False
        return version >= 1.9

    @staticmethod
    def read_python_path_env():
        if 'PYTHONPATH' in os.environ:
            classpath = os.environ['PYTHONPATH'].split(os.pathsep)
            for path in classpath:
                if 'remoteswinglibrary' in path.lower():
                    return str(path)
        return None

    @staticmethod
    def _read_java_version():
        def read_sys_path():
            for item in sys.path:
                if 'remoteswinglibrary' in item.lower(
                ) and '.jar' in item.lower():
                    return str(item)
            return None

        def construct_classpath(location):
            classpath = location
            if 'CLASSPATH' in os.environ:
                classpath += os.pathsep + os.environ['CLASSPATH']
            return classpath

        location = RemoteSwingLibrary.read_python_path_env() or read_sys_path()
        if location:
            os.environ['CLASSPATH'] = construct_classpath(location)

        p = subprocess.Popen(
            ['java', 'org.robotframework.remoteswinglibrary.ReadJavaVersion'],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            env=os.environ)
        version, err = p.communicate()
        return version

    def __init__(self, port=0, debug=False, java9_or_newer='auto-detect'):
        """
        ``port``: optional port for the server receiving connections from remote agents

        ``debug``: optional flag that will start agent in mode with more logging for troubleshooting
        (set to ``True`` to enable)

        ``java9_or_newer``: optional flag that by default will try to automatically detect if Java version is greater
        than 8. If it fails to detect, manually set it to ``True`` or ``False`` accordingly. If the value is different
        from ``true``, ``1`` or ``yes`` (case insensitive) it will default to ``False``.

        *Note:* RemoteSwingLibrary is a so called Global Scope library. This means when it is imported once it will be
        available until end of robot run. Parameters used in imports from others suites will be ignored.
        If you need to change import options between suites, please use `Reinitiate` keyword.

        """

        self._initiate(port, debug, java9_or_newer)

    def _initiate(self, port=0, debug=False, java9_or_newer='auto-detect'):
        if RemoteSwingLibrary.DEBUG is None:
            RemoteSwingLibrary.DEBUG = _tobool(debug)
        if RemoteSwingLibrary.PORT is None:
            RemoteSwingLibrary.PORT = self._start_port_server(int(port))
        if java9_or_newer == 'auto-detect':
            RemoteSwingLibrary.JAVA9_OR_NEWER = _tobool(self._java9_or_newer())
        elif _tobool(java9_or_newer):
            RemoteSwingLibrary.JAVA9_OR_NEWER = True
        try:
            if '__pyclasspath__' in RemoteSwingLibrary.AGENT_PATH:
                RemoteSwingLibrary.AGENT_PATH = RemoteSwingLibrary.read_python_path_env(
                )
            BuiltIn().set_global_variable(
                '\${REMOTESWINGLIBRARYPATH}',
                self._escape_path(RemoteSwingLibrary.AGENT_PATH))
            BuiltIn().set_global_variable('\${REMOTESWINGLIBRARYPORT}',
                                          RemoteSwingLibrary.PORT)
            self._output_dir = BuiltIn().get_variable_value('${OUTPUTDIR}')
        except RobotNotRunningError:
            pass

    def reinitiate(self, port=0, debug=False, java9_or_newer='auto-detect'):
        """
        Restarts RemoteSwingLibrary with new import parameters.
        """
        RemoteSwingLibrary.CURRENT = None
        global REMOTE_AGENTS_LIST
        REMOTE_AGENTS_LIST = AgentList()
        RemoteSwingLibrary.PORT = None
        RemoteSwingLibrary.DEBUG = None
        RemoteSwingLibrary.TIMEOUT = 60
        self._remove_policy_file()

        self._initiate(port, debug, java9_or_newer)

    @property
    def current(self):
        if not self.CURRENT:
            return None
        return self.REMOTES[self.CURRENT][0]

    def _start_port_server(self, port):
        address = ('0.0.0.0', int(port))
        server = SocketServer.TCPServer(address, SimpleServer)
        server.allow_reuse_address = True
        # t = threading.Thread(name="RemoteSwingLibrary registration server thread",
        #                     target=server.serve_forever)
        t = threading.Thread(
            name="RemoteSwingLibrary registration server thread",
            target=server.serve_forever,
            args=(0.01, ))
        t.setDaemon(True)
        t.start()
        return server.server_address[1]

    def _create_env(self,
                    close_security_dialogs=False,
                    remote_port=0,
                    dir_path=None,
                    custom=None):
        agent_command = '-javaagent:"%s"=127.0.0.1:%s' % (
            RemoteSwingLibrary.AGENT_PATH, RemoteSwingLibrary.PORT)
        if int(remote_port):
            agent_command += ':APPORT=%s' % remote_port
        if custom:
            agent_command += ':CUSTOM=%s' % custom
        if RemoteSwingLibrary.DEBUG:
            agent_command += ':DEBUG'
        if _tobool(close_security_dialogs):
            agent_command += ':CLOSE_SECURITY_DIALOGS'
        if is_truthy(dir_path):
            dir_path = os.path.abspath(dir_path)
            agent_command += ':DIR_PATH:%s' % dir_path
        self._agent_command = agent_command
        logger.info(agent_command)

    def _escape_path(self, text):
        return text.replace("\\", "\\\\")

    @contextmanager
    def _agent_java_tool_options(self, close_security_dialogs, remote_port,
                                 dir_path, custom):
        old_tool_options = os.environ.get('JAVA_TOOL_OPTIONS', '')
        old_options = os.environ.get('_JAVA_OPTIONS', '')
        logger.debug("Picked old JAVA_TOOL_OPTIONS='%s'" % old_tool_options)
        logger.debug("Picked old _JAVA_OPTIONS='%s'" % old_options)
        self.set_java_tool_options(close_security_dialogs, remote_port,
                                   dir_path, custom)
        try:
            yield
        finally:
            os.environ['JAVA_TOOL_OPTIONS'] = old_tool_options
            os.environ['_JAVA_OPTIONS'] = old_options
            logger.debug("Returned old JAVA_TOOL_OPTIONS='%s'" %
                         old_tool_options)
            logger.debug("Returned old _JAVA_OPTIONS='%s'" % old_options)

    def set_java_tool_options(self,
                              close_security_dialogs=True,
                              remote_port=0,
                              dir_path=None,
                              custom=None):
        """Sets the ``JAVA_TOOL_OPTIONS`` to include RemoteSwingLibrary Agent and
        the ``_JAVA_OPTIONS`` to set a temporary policy granting all permissions.

        RemoteSwingLibrary Agent is normally enabled by `Start Application` by
        setting the ``JAVA_TOOL_OPTIONS`` environment variable only during
        that keyword call.

        Java processes started by other commands won't
        normally use the RemoteSwingLibrary agent. This keyword sets that same
        environment variable to be always used. So all java processes started
        after this will use the agent. This method also creates temporary
        Java policy file which grants all permissions and is set as
        policy for each java command call.
        """
        close_security_dialogs = _tobool(close_security_dialogs)
        en_us_locale = "-Duser.language=en -Duser.country=US "
        self._create_env(close_security_dialogs, remote_port, dir_path, custom)
        logger.info("Java version > 9: " +
                    str(RemoteSwingLibrary.JAVA9_OR_NEWER))
        if RemoteSwingLibrary.JAVA9_OR_NEWER is True:
            self._agent_command += ' --add-exports=java.desktop/sun.awt=ALL-UNNAMED'
        os.environ['JAVA_TOOL_OPTIONS'] = en_us_locale + self._agent_command
        logger.debug("Set JAVA_TOOL_OPTIONS='%s%s'" %
                     (en_us_locale, self._agent_command))
        with tempfile.NamedTemporaryFile(prefix='grant_all_',
                                         suffix='.policy',
                                         delete=False) as t:
            self.POLICY_FILE = t.name
            text = b"""
                grant {
                    permission java.security.AllPermission;
                };
                """
            t.write(text)
        java_policy = '-Djava.security.policy="%s"' % t.name
        os.environ['_JAVA_OPTIONS'] = java_policy
        logger.debug("Set _JAVA_OPTIONS='%s'" % java_policy)

    def start_application(self,
                          alias,
                          command,
                          timeout=60,
                          name_contains="",
                          close_security_dialogs=False,
                          remote_port=0,
                          dir_path=None,
                          stdout=None,
                          stderr=None,
                          custom=None):
        """Starts the process in the ``command`` parameter  on the host operating system.
        The given ``alias`` is stored to identify the started application in RemoteSwingLibrary.

        ``timeout`` (default 60) is timeout in seconds.

        ``name_contains`` is a text that must be part of the name of the java process that we are connecting to.
        It helps in situations where multiple java-processes are started.

        To see the name of the connecting java agents run tests with ``--loglevel DEBUG``.

        ``remote_port`` forces RSL agent to run on specific port, this is useful if you want to
        connect to this application later from another robot run.

        ``dir_path`` is the path where security dialogs screenshots are saved. It is working both with relative
        and absolute path. If ``dir_path`` is not specified the screenshots will not be taken.

        ``stdout`` is the path where to write stdout to.

        ``stderr`` is the path where to write stderr to.
    
        ``custom`` is a customizable field that can be set when starting the Java agent.

        This keyword returns the remote port of the connection.
        """
        close_security_dialogs = _tobool(close_security_dialogs)

        now = datetime.datetime.now()
        current_date = now.strftime("%Y%m%d%H%M%S")

        if stdout is None:
            stdout_file_name = current_date + ".remoteswinglibrary" + ".out"
            stdout = self._output(stdout_file_name)
        (stdout_dir, stdout_file_name) = os.path.split(stdout)
        if not os.path.exists(stdout_dir):
            os.makedirs(stdout_dir)

        if stderr is None:
            stderr_file_name = current_date + ".remoteswinglibrary" + ".err"
            stderr = self._output(stderr_file_name)
        if stderr != "STDOUT":
            (stderr_dir, stderr_file_name) = os.path.split(stderr)
            if not os.path.exists(stderr_dir):
                os.makedirs(stderr_dir)

        logger.info('<a href="%s">Link to stdout</a>' % stdout, html=True)
        logger.info('<a href="%s">Link to stderr</a>' % stderr, html=True)
        REMOTE_AGENTS_LIST.set_received_to_old()
        with self._agent_java_tool_options(close_security_dialogs, remote_port,
                                           dir_path, custom):
            self.PROCESS.start_process(command,
                                       alias=alias,
                                       shell=True,
                                       stdout=stdout,
                                       stderr=stderr)
        try:
            self._application_started(alias,
                                      timeout,
                                      name_contains,
                                      remote_port,
                                      accept_old=False)
        except TimeoutError:
            raise
        except Exception:
            logger.info("Failed to start application: %s" %
                        traceback.format_exc())
            # FIXME: this may hang, how is that possible?
            result = self.PROCESS.wait_for_process(timeout=0.01)
            if result:
                logger.info('STDOUT: %s' % result.stdout)
                logger.info('STDERR: %s' % result.stderr)
            else:
                logger.info(
                    "Process is running, but application startup failed")
            raise
        finally:
            self._remove_policy_file()
        return RemoteSwingLibrary.PORT

    def _output(self, filename):
        return os.path.join(self._output_dir, filename)

    def application_started(self,
                            alias,
                            timeout=60,
                            name_contains="",
                            remote_port=0,
                            remote_host="127.0.0.1"):
        """Detects new RemoteSwingLibrary Java-agents in applications that are started without
        using the `Start Application` keyword. The given ``alias`` is stored
        to identify the started application in RemoteSwingLibrary.
        Subsequent keywords will be passed on to this application. Agents in application
        started in previous robot runs can't be detected automatically, so you have to use ``remote_port`` parameter.
        """
        self._application_started(alias, timeout, name_contains, remote_port,
                                  remote_host)

    def _wait_for_api(self, url):
        logger.info('waiting for api at %s' % url)
        attempts = int(RemoteSwingLibrary.TIMEOUT)
        for i in range(attempts):
            try:
                result = self._run_from_services('ping')
                logger.info('api is ready')
                return result
            except Exception as err:
                error = err
            time.sleep(1)
        raise RuntimeError('Connecting to api at %s has failed: %s' %
                           (url, error))

    def _application_started(self,
                             alias,
                             timeout=60,
                             name_contains="",
                             remote_port=0,
                             remote_host="127.0.0.1",
                             accept_old=True):
        RemoteSwingLibrary.TIMEOUT = timestr_to_secs(timeout)
        if remote_port:
            url = '%s:%s' % (remote_host, remote_port)
            REMOTE_AGENTS_LIST.remove(url)
        else:
            url = self._get_agent_address(name_contains, accept_old)
        logger.info('connecting to started application at %s' % url)
        self._initialize_remote_libraries(alias, url)
        RemoteSwingLibrary.CURRENT = alias
        self._wait_for_api(url)
        logger.info('connected to started application at %s' % url)
        self._remove_policy_file()

    def _initialize_remote_libraries(self, alias, url):
        swinglibrary = Remote(url)
        logger.debug('remote swinglibrary instantiated')
        services = Remote(url + '/services')
        logger.debug('remote services instantiated')
        self.REMOTES[alias] = [swinglibrary, services]

    def _get_agent_address(self, name_pattern, accept_old):
        while True:
            if not REMOTE_AGENTS_LIST.get(accept_old):
                REMOTE_AGENTS_LIST.agent_received.clear()
            REMOTE_AGENTS_LIST.agent_received.wait(
                timeout=RemoteSwingLibrary.TIMEOUT)
            if not REMOTE_AGENTS_LIST.agent_received.isSet():
                raise RemoteSwingLibraryTimeoutError(
                    'Agent port not received before timeout')
            for address, name, age in reversed(
                    REMOTE_AGENTS_LIST.get(accept_old)):
                if name_pattern is None or name_pattern in name:
                    REMOTE_AGENTS_LIST.remove(address)
                    return address
            time.sleep(0.1)

    def _ping_until_timeout(self, timeout):
        timeout = float(timeout)
        delta = min(0.5, timeout)
        endtime = timeout + time.time()
        while endtime > time.time():
            self._run_from_services('ping')
            time.sleep(delta)

    def _run_from_services(self, kw, *args, **kwargs):
        return self.REMOTES[RemoteSwingLibrary.CURRENT][1].run_keyword(
            kw, args, kwargs)

    @run_keyword_variant(resolve=1)
    def ensure_application_should_close(self, timeout, kw, *args):
        """ Runs the given keyword and waits until timeout for the application to close.
        If the application doesn't close, the keyword will take a screenshot and close the application
        and after that it will fail.
        In many cases calling the keyword that will close the application under test brakes the remote connection.
        This exception is ignored as it is expected by this keyword.
        Other exceptions will fail this keyword as expected.
        """
        with self._run_and_ignore_connection_lost():
            BuiltIn().run_keyword(kw, *args)
        try:
            self._application_should_be_closed(
                timeout=timestr_to_secs(timeout))
        except RemoteSwingLibraryTimeoutError as t:
            logger.warn(
                'Application is not closed before timeout - killing application'
            )
            self._take_screenshot()
            self.system_exit()
            raise

    def _take_screenshot(self):
        logdir = self.get_log_dir()
        screenshotdir = logdir + "/" + "remote-screenshots"
        if not os.path.exists(screenshotdir):
            os.makedirs(screenshotdir)

        filepath = os.path.join(
            screenshotdir, 'remote-screenshot%s.png' %
            re.sub('[:. ]', '-', str(datetime.datetime.now())))
        self._run_from_services('takeScreenshot', filepath)
        logger.info('<img src="%s"></img>' % get_link_path(filepath, logdir),
                    html=True)

    @staticmethod
    def get_log_dir():
        variables = BuiltIn().get_variables()
        logfile = variables['${LOG FILE}']
        if logfile != 'NONE':
            return os.path.dirname(logfile)
        return variables['${OUTPUTDIR}']

    def _application_should_be_closed(self, timeout):
        with self._run_and_ignore_connection_lost():
            self._ping_until_timeout(timeout)
            raise RemoteSwingLibraryTimeoutError(
                'Application was not closed before timeout')

    @contextmanager
    def _run_and_ignore_connection_lost(self):
        try:
            yield
        except RuntimeError as r:  # disconnection from remotelibrary
            if 'Connection to remote server broken:' in r.args[0]:
                logger.info('Connection died as expected')
                return
            raise
        except HandlerExecutionFailed as e:  # disconnection from xmlrpc wrapped in robot keyword
            if any(elem in e.args[0]
                   for elem in ('Connection to remote server broken:',
                                'ProtocolError')):
                logger.info('Connection died as expected')
                return
            raise
        except ProtocolError as r:  # disconnection from xmlrpc in jython on some platforms
            logger.info('Connection died as expected')
            return

    def system_exit(self, exit_code=1):
        """ Uses the RemoteSwingLibrary java agent to call system exit for the current java application.
        """
        with self._run_and_ignore_connection_lost():
            self._run_from_services('systemExit', exit_code)
            # try closing the attached process (if started with start_application) to close any open file handles
        try:
            self.PROCESS.wait_for_process(handle=RemoteSwingLibrary.CURRENT,
                                          timeout=0.01)
        except:
            return

    def switch_to_application(self, alias):
        """Switches between applications that are known to RemoteSwingLibrary.
        The application is identified using the ``alias``.
        Subsequent keywords will be passed on to this application."""
        RemoteSwingLibrary.CURRENT = alias

    def log_java_system_properties(self):
        """Log and return java properties and environment information from the current java application.
        """
        env = self._run_from_services('getEnvironment')
        logger.info(env)
        if IS_PYTHON3 and not is_string(env):
            return env.decode('utf-8')
        return env

    def get_keyword_names(self):
        overrided_keywords = [
            'startApplication', 'launchApplication',
            'startApplicationInSeparateThread'
        ]
        return RemoteSwingLibrary.KEYWORDS + [
            kw for kw in swinglibrary.keywords if kw not in overrided_keywords
        ]

    def get_keyword_arguments(self, name):
        if name in RemoteSwingLibrary.KEYWORDS:
            return self._get_args(name)
        return swinglibrary.keyword_arguments[name]

    def _get_args(self, method_name):
        spec = inspect.getargspec(getattr(self, method_name))
        args = spec[0][1:]
        if spec[3]:
            for i, item in enumerate(reversed(spec[3])):
                args[-i - 1] = args[-i - 1] + '=' + str(item)
        if spec[1]:
            args += ['*' + spec[1]]
        if spec[2]:
            args += ['**' + spec[2]]
        return args

    def get_keyword_documentation(self, name):
        if name == '__intro__':
            return RemoteSwingLibrary.__doc__
        if name in RemoteSwingLibrary.KEYWORDS or name == '__init__':
            return getattr(self, name).__doc__
        return swinglibrary.keyword_documentation[name]

    def run_keyword(self, name, args, kwargs=None):
        if name in RemoteSwingLibrary.KEYWORDS:
            return getattr(self, name)(*args, **(kwargs or {}))
        if self.current:
            return self.current.run_keyword(name, args, kwargs)
        if name in swinglibrary.keywords:
            raise Exception(
                "To use this keyword you need to connect to application first."
            )