Robot Framework Integrated Development Environment (RIDE)
Process.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import ctypes
17 import os
18 import subprocess
19 import time
20 import signal as signal_module
21 
22 from robotide.lib.robot.utils import (ConnectionCache, abspath, cmdline2list, console_decode,
23  is_list_like, is_truthy, NormalizedDict, py2to3,
24  secs_to_timestr, system_decode, system_encode,
25  timestr_to_secs, IRONPYTHON, JYTHON, WINDOWS)
26 from robotide.lib.robot.version import get_version
27 from robotide.lib.robot.api import logger
28 
29 
30 
292 class Process():
293  ROBOT_LIBRARY_SCOPE = 'GLOBAL'
294  ROBOT_LIBRARY_VERSION = get_version()
295  TERMINATE_TIMEOUT = 30
296  KILL_TIMEOUT = 10
297 
298  def __init__(self):
299  self._processes_processes = ConnectionCache('No active process.')
300  self._results_results = {}
301 
302 
331  def run_process(self, command, *arguments, **configuration):
332  current = self._processes_processes.current
333  timeout = configuration.pop('timeout', None)
334  on_timeout = configuration.pop('on_timeout', 'terminate')
335  try:
336  handle = self.start_processstart_process(command, *arguments, **configuration)
337  return self.wait_for_processwait_for_process(handle, timeout, on_timeout)
338  finally:
339  self._processes_processes.current = current
340 
341 
354  def start_process(self, command, *arguments, **configuration):
355  conf = ProcessConfiguration(**configuration)
356  command = conf.get_command(command, list(arguments))
357  self._log_start_log_start(command, conf)
358  process = subprocess.Popen(command, **conf.popen_config)
359  self._results_results[process] = ExecutionResult(process, **conf.result_config)
360  return self._processes_processes.register(process, alias=conf.alias)
361 
362  def _log_start(self, command, config):
363  if is_list_like(command):
364  command = self.join_command_linejoin_command_line(command)
365  logger.info(u'Starting process:\n%s' % system_decode(command))
366  logger.debug(u'Process configuration:\n%s' % config)
367 
368 
374  def is_process_running(self, handle=None):
375  return self._processes_processes[handle].poll() is None
376 
377 
383  def process_should_be_running(self, handle=None,
384  error_message='Process is not running.'):
385  if not self.is_process_runningis_process_running(handle):
386  raise AssertionError(error_message)
387 
388 
394  def process_should_be_stopped(self, handle=None,
395  error_message='Process is running.'):
396  if self.is_process_runningis_process_running(handle):
397  raise AssertionError(error_message)
398 
399 
442  def wait_for_process(self, handle=None, timeout=None, on_timeout='continue'):
443  process = self._processes_processes[handle]
444  logger.info('Waiting for process to complete.')
445  if timeout:
446  timeout = timestr_to_secs(timeout)
447  if not self._process_is_stopped_process_is_stopped(process, timeout):
448  logger.info('Process did not complete in %s.'
449  % secs_to_timestr(timeout))
450  return self._manage_process_timeout_manage_process_timeout(handle, on_timeout.lower())
451  return self._wait_wait(process)
452 
453  def _manage_process_timeout(self, handle, on_timeout):
454  if on_timeout == 'terminate':
455  return self.terminate_processterminate_process(handle)
456  elif on_timeout == 'kill':
457  return self.terminate_processterminate_process(handle, kill=True)
458  else:
459  logger.info('Leaving process intact.')
460  return None
461 
462  def _wait(self, process):
463  result = self._results_results[process]
464  result.rc = process.wait() or 0
465  result.close_streams()
466  logger.info('Process completed.')
467  return result
468 
469 
502  def terminate_process(self, handle=None, kill=False):
503  process = self._processes_processes[handle]
504  if not hasattr(process, 'terminate'):
505  raise RuntimeError('Terminating processes is not supported '
506  'by this Python version.')
507  terminator = self._kill_kill if is_truthy(kill) else self._terminate_terminate
508  try:
509  terminator(process)
510  except OSError:
511  if not self._process_is_stopped_process_is_stopped(process, self.KILL_TIMEOUTKILL_TIMEOUT):
512  raise
513  logger.debug('Ignored OSError because process was stopped.')
514  return self._wait_wait(process)
515 
516  def _kill(self, process):
517  logger.info('Forcefully killing process.')
518  if hasattr(os, 'killpg'):
519  os.killpg(process.pid, signal_module.SIGKILL)
520  else:
521  process.kill()
522  if not self._process_is_stopped_process_is_stopped(process, self.KILL_TIMEOUTKILL_TIMEOUT):
523  raise RuntimeError('Failed to kill process.')
524 
525  def _terminate(self, process):
526  logger.info('Gracefully terminating process.')
527  # Sends signal to the whole process group both on POSIX and on Windows
528  # if supported by the interpreter.
529  if hasattr(os, 'killpg'):
530  os.killpg(process.pid, signal_module.SIGTERM)
531  elif hasattr(signal_module, 'CTRL_BREAK_EVENT'):
532  if IRONPYTHON:
533  # https://ironpython.codeplex.com/workitem/35020
534  ctypes.windll.kernel32.GenerateConsoleCtrlEvent(
535  signal_module.CTRL_BREAK_EVENT, process.pid)
536  else:
537  process.send_signal(signal_module.CTRL_BREAK_EVENT)
538  else:
539  process.terminate()
540  if not self._process_is_stopped_process_is_stopped(process, self.TERMINATE_TIMEOUTTERMINATE_TIMEOUT):
541  logger.info('Graceful termination failed.')
542  self._kill_kill(process)
543 
544 
553  def terminate_all_processes(self, kill=False):
554  for handle in range(1, len(self._processes_processes) + 1):
555  if self.is_process_runningis_process_running(handle):
556  self.terminate_processterminate_process(handle, kill=kill)
557  self.__init____init__()
558 
559 
586  def send_signal_to_process(self, signal, handle=None, group=False):
587  if os.sep == '\\':
588  raise RuntimeError('This keyword does not work on Windows.')
589  process = self._processes_processes[handle]
590  signum = self._get_signal_number_get_signal_number(signal)
591  logger.info('Sending signal %s (%d).' % (signal, signum))
592  if is_truthy(group) and hasattr(os, 'killpg'):
593  os.killpg(process.pid, signum)
594  elif hasattr(process, 'send_signal'):
595  process.send_signal(signum)
596  else:
597  raise RuntimeError('Sending signals is not supported '
598  'by this Python version.')
599 
600  def _get_signal_number(self, int_or_name):
601  try:
602  return int(int_or_name)
603  except ValueError:
604  return self._convert_signal_name_to_number_convert_signal_name_to_number(int_or_name)
605 
607  try:
608  return getattr(signal_module,
609  name if name.startswith('SIG') else 'SIG' + name)
610  except AttributeError:
611  raise RuntimeError("Unsupported signal '%s'." % name)
612 
613 
620  def get_process_id(self, handle=None):
621  return self._processes_processes[handle].pid
622 
623 
627  def get_process_object(self, handle=None):
628  return self._processes_processes[handle]
629 
630 
669  def get_process_result(self, handle=None, rc=False, stdout=False,
670  stderr=False, stdout_path=False, stderr_path=False):
671  result = self._results_results[self._processes_processes[handle]]
672  if result.rc is None:
673  raise RuntimeError('Getting results of unfinished processes '
674  'is not supported.')
675  attributes = self._get_result_attributes_get_result_attributes(result, rc, stdout, stderr,
676  stdout_path, stderr_path)
677  if not attributes:
678  return result
679  elif len(attributes) == 1:
680  return attributes[0]
681  return attributes
682 
683  def _get_result_attributes(self, result, *includes):
684  attributes = (result.rc, result.stdout, result.stderr,
685  result.stdout_path, result.stderr_path)
686  includes = (is_truthy(incl) for incl in includes)
687  return tuple(attr for attr, incl in zip(attributes, includes) if incl)
688 
689 
701  def switch_process(self, handle):
702  self._processes_processes.switch(handle)
703 
704  def _process_is_stopped(self, process, timeout):
705  stopped = lambda: process.poll() is not None
706  max_time = time.time() + timeout
707  while time.time() <= max_time and not stopped():
708  time.sleep(min(0.1, timeout))
709  return stopped()
710 
711 
725  def split_command_line(self, args, escaping=False):
726  return cmdline2list(args, escaping=escaping)
727 
728 
743  def join_command_line(self, *args):
744  if len(args) == 1 and is_list_like(args[0]):
745  args = args[0]
746  return subprocess.list2cmdline(args)
747 
748 
750 
751  def __init__(self, process, stdout, stderr, rc=None, output_encoding=None):
752  self._process_process = process
753  self.stdout_pathstdout_path = self._get_path_get_path(stdout)
754  self.stderr_pathstderr_path = self._get_path_get_path(stderr)
755  self.rcrc = rc
756  self._output_encoding_output_encoding = output_encoding
757  self._stdout_stdout = None
758  self._stderr_stderr = None
759  self._custom_streams_custom_streams = [stream for stream in (stdout, stderr)
760  if self._is_custom_stream_is_custom_stream(stream)]
761 
762  def _get_path(self, stream):
763  return stream.name if self._is_custom_stream_is_custom_stream(stream) else None
764 
765  def _is_custom_stream(self, stream):
766  return stream not in (subprocess.PIPE, subprocess.STDOUT)
767 
768  @property
769  stdout = property
770 
771  def stdout(self):
772  if self._stdout_stdout is None:
773  self._read_stdout_read_stdout()
774  return self._stdout_stdout
775 
776  @property
777  stderr = property
778 
779  def stderr(self):
780  if self._stderr_stderr is None:
781  self._read_stderr_read_stderr()
782  return self._stderr_stderr
783 
784  def _read_stdout(self):
785  self._stdout_stdout = self._read_stream_read_stream(self.stdout_pathstdout_path, self._process_process.stdout)
786 
787  def _read_stderr(self):
788  self._stderr_stderr = self._read_stream_read_stream(self.stderr_pathstderr_path, self._process_process.stderr)
789 
790  def _read_stream(self, stream_path, stream):
791  if stream_path:
792  stream = open(stream_path, 'rb')
793  elif not self._is_open_is_open(stream):
794  return ''
795  try:
796  content = stream.read()
797  except IOError: # http://bugs.jython.org/issue2218
798  return ''
799  finally:
800  if stream_path:
801  stream.close()
802  return self._format_output_format_output(content)
803 
804  def _is_open(self, stream):
805  return stream and not stream.closed
806 
807  def _format_output(self, output):
808  output = console_decode(output, self._output_encoding_output_encoding, force=True)
809  output = output.replace('\r\n', '\n')
810  if output.endswith('\n'):
811  output = output[:-1]
812  return output
813 
814  def close_streams(self):
815  standard_streams = self._get_and_read_standard_streams_get_and_read_standard_streams(self._process_process)
816  for stream in standard_streams + self._custom_streams_custom_streams:
817  if self._is_open_is_open(stream):
818  stream.close()
819 
820  def _get_and_read_standard_streams(self, process):
821  stdin, stdout, stderr = process.stdin, process.stdout, process.stderr
822  if stdout:
823  self._read_stdout_read_stdout()
824  if stderr:
825  self._read_stderr_read_stderr()
826  return [stdin, stdout, stderr]
827 
828  def __str__(self):
829  return '<result object with rc %d>' % self.rcrc
830 
831 
832 @py2to3
834 
835  def __init__(self, cwd=None, shell=False, stdout=None, stderr=None,
836  output_encoding='CONSOLE', alias=None, env=None, **rest):
837  self.cwdcwd = self._get_cwd_get_cwd(cwd)
838  self.stdout_streamstdout_stream = self._new_stream_new_stream(stdout)
839  self.stderr_streamstderr_stream = self._get_stderr_get_stderr(stderr, stdout, self.stdout_streamstdout_stream)
840  self.shellshell = is_truthy(shell)
841  self.aliasalias = alias
842  self.output_encodingoutput_encoding = output_encoding
843  self.envenv = self._construct_env_construct_env(env, rest)
844 
845  def _get_cwd(self, cwd):
846  if cwd:
847  return cwd.replace('/', os.sep)
848  return abspath('.')
849 
850  def _new_stream(self, name):
851  if name:
852  name = name.replace('/', os.sep)
853  return open(os.path.join(self.cwdcwd, name), 'w')
854  return subprocess.PIPE
855 
856  def _get_stderr(self, stderr, stdout, stdout_stream):
857  if stderr and stderr in ['STDOUT', stdout]:
858  if stdout_stream != subprocess.PIPE:
859  return stdout_stream
860  return subprocess.STDOUT
861  return self._new_stream_new_stream(stderr)
862 
863  def _construct_env(self, env, extra):
864  env = self._get_initial_env_get_initial_env(env, extra)
865  if env is None:
866  return None
867  if WINDOWS:
868  env = NormalizedDict(env, spaceless=False)
869  self._add_to_env_add_to_env(env, extra)
870  if WINDOWS:
871  env = dict((key.upper(), env[key]) for key in env)
872  return env
873 
874  def _get_initial_env(self, env, extra):
875  if env:
876  return dict((system_encode(k), system_encode(env[k])) for k in env)
877  if extra:
878  return os.environ.copy()
879  return None
880 
881  def _add_to_env(self, env, extra):
882  for key in extra:
883  if not key.startswith('env:'):
884  raise RuntimeError("Keyword argument '%s' is not supported by "
885  "this keyword." % key)
886  env[system_encode(key[4:])] = system_encode(extra[key])
887 
888  def get_command(self, command, arguments):
889  command = [system_encode(item) for item in [command] + arguments]
890  if not self.shellshell:
891  return command
892  if arguments:
893  return subprocess.list2cmdline(command)
894  return command[0]
895 
896  @property
897  popen_config = property
898 
899  def popen_config(self):
900  config = {'stdout': self.stdout_streamstdout_stream,
901  'stderr': self.stderr_streamstderr_stream,
902  'stdin': subprocess.PIPE,
903  'shell': self.shellshell,
904  'cwd': self.cwdcwd,
905  'env': self.envenv}
906  # Close file descriptors regardless the Python version:
907  # https://github.com/robotframework/robotframework/issues/2794
908  if not WINDOWS:
909  config['close_fds'] = True
910  if not JYTHON:
911  self._add_process_group_config_add_process_group_config(config)
912  return config
913 
914  def _add_process_group_config(self, config):
915  if hasattr(os, 'setsid'):
916  config['preexec_fn'] = os.setsid
917  if hasattr(subprocess, 'CREATE_NEW_PROCESS_GROUP'):
918  config['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
919 
920  @property
921  result_config = property
922 
923  def result_config(self):
924  return {'stdout': self.stdout_streamstdout_stream,
925  'stderr': self.stderr_streamstderr_stream,
926  'output_encoding': self.output_encodingoutput_encoding}
927 
928  def __unicode__(self):
929  return """\
930 cwd: %s
931 shell: %s
932 stdout: %s
933 stderr: %s
934 alias: %s
935 env: %s""" % (self.cwdcwd, self.shellshell, self._stream_name_stream_name(self.stdout_streamstdout_stream),
936  self._stream_name_stream_name(self.stderr_streamstderr_stream), self.aliasalias, self.envenv)
937 
938  def _stream_name(self, stream):
939  if hasattr(stream, 'name'):
940  return stream.name
941  return {subprocess.PIPE: 'PIPE',
942  subprocess.STDOUT: 'STDOUT'}.get(stream, stream)
def _read_stream(self, stream_path, stream)
Definition: Process.py:790
def __init__(self, process, stdout, stderr, rc=None, output_encoding=None)
Definition: Process.py:751
def _get_stderr(self, stderr, stdout, stdout_stream)
Definition: Process.py:856
def __init__(self, cwd=None, shell=False, stdout=None, stderr=None, output_encoding='CONSOLE', alias=None, env=None, **rest)
Definition: Process.py:836
Robot Framework test library for running processes.
Definition: Process.py:292
def terminate_process(self, handle=None, kill=False)
Stops the process gracefully or forcefully.
Definition: Process.py:502
def split_command_line(self, args, escaping=False)
Splits command line string into a list of arguments.
Definition: Process.py:725
def get_process_result(self, handle=None, rc=False, stdout=False, stderr=False, stdout_path=False, stderr_path=False)
Returns the specified result object or some of its attributes.
Definition: Process.py:670
def terminate_all_processes(self, kill=False)
Terminates all still running processes started by this library.
Definition: Process.py:553
def process_should_be_stopped(self, handle=None, error_message='Process is running.')
Verifies that the process is not running.
Definition: Process.py:395
def run_process(self, command, *arguments, **configuration)
Runs a process and waits for it to complete.
Definition: Process.py:331
def join_command_line(self, *args)
Joins arguments into one command line string.
Definition: Process.py:743
def _process_is_stopped(self, process, timeout)
Definition: Process.py:704
def start_process(self, command, *arguments, **configuration)
Starts a new process on background.
Definition: Process.py:354
def _get_signal_number(self, int_or_name)
Definition: Process.py:600
def get_process_id(self, handle=None)
Returns the process ID (pid) of the process as an integer.
Definition: Process.py:620
def _manage_process_timeout(self, handle, on_timeout)
Definition: Process.py:453
def send_signal_to_process(self, signal, handle=None, group=False)
Sends the given signal to the specified process.
Definition: Process.py:586
def _get_result_attributes(self, result, *includes)
Definition: Process.py:683
def wait_for_process(self, handle=None, timeout=None, on_timeout='continue')
Waits for the process to complete or to reach the given timeout.
Definition: Process.py:442
def switch_process(self, handle)
Makes the specified process the current active process.
Definition: Process.py:701
def _log_start(self, command, config)
Definition: Process.py:362
def process_should_be_running(self, handle=None, error_message='Process is not running.')
Verifies that the process is running.
Definition: Process.py:384
def get_process_object(self, handle=None)
Return the underlying subprocess.Popen object.
Definition: Process.py:627
def is_process_running(self, handle=None)
Checks is the process running or not.
Definition: Process.py:374
def cmdline2list(args, escaping=False)
def system_decode(string)
Decodes bytes from system (e.g.
Definition: encoding.py:81
def console_decode(string, encoding=CONSOLE_ENCODING, force=False)
Decodes bytes from console encoding to Unicode.
Definition: encoding.py:45
def system_encode(string, errors='replace')
Encodes Unicode to system encoding (e.g.
Definition: encoding.py:84
def abspath(path, case_normalize=False)
Replacement for os.path.abspath with some enhancements and bug fixes.
Definition: robotpath.py:87
def secs_to_timestr(secs, compact=False)
Converts time in seconds to a string representation.
Definition: robottime.py:126
def timestr_to_secs(timestr, round_to=3)
Parses time like '1h 10s', '01:00:10' or '42' and returns seconds.
Definition: robottime.py:45
def is_truthy(item)
Returns True or False depending is the item considered true or not.
Definition: robottypes.py:49
def get_version(naked=False)
Definition: version.py:24