Robot Framework
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 os
17 import signal as signal_module
18 import subprocess
19 import time
20 from tempfile import TemporaryFile
21 
22 from robot.utils import (abspath, cmdline2list, ConnectionCache, console_decode,
23  console_encode, is_list_like, is_pathlike, is_string,
24  is_truthy, NormalizedDict, secs_to_timestr, system_decode,
25  system_encode, timestr_to_secs, WINDOWS)
26 from robot.version import get_version
27 from robot.api import logger
28 
29 
30 
318 class Process:
319  ROBOT_LIBRARY_SCOPE = 'GLOBAL'
320  ROBOT_LIBRARY_VERSION = get_version()
321  TERMINATE_TIMEOUT = 30
322  KILL_TIMEOUT = 10
323 
324  def __init__(self):
325  self._processes_processes = ConnectionCache('No active process.')
326  self._results_results = {}
327 
328 
357  def run_process(self, command, *arguments, **configuration):
358  current = self._processes_processes.current
359  timeout = configuration.pop('timeout', None)
360  on_timeout = configuration.pop('on_timeout', 'terminate')
361  try:
362  handle = self.start_processstart_process(command, *arguments, **configuration)
363  return self.wait_for_processwait_for_process(handle, timeout, on_timeout)
364  finally:
365  self._processes_processes.current = current
366 
367 
404  def start_process(self, command, *arguments, **configuration):
405  conf = ProcessConfiguration(**configuration)
406  command = conf.get_command(command, list(arguments))
407  self._log_start_log_start(command, conf)
408  process = subprocess.Popen(command, **conf.popen_config)
409  self._results_results[process] = ExecutionResult(process, **conf.result_config)
410  self._processes_processes.register(process, alias=conf.alias)
411  return self._processes_processes.current
412 
413  def _log_start(self, command, config):
414  if is_list_like(command):
415  command = self.join_command_linejoin_command_line(command)
416  logger.info('Starting process:\n%s' % system_decode(command))
417  logger.debug('Process configuration:\n%s' % config)
418 
419 
425  def is_process_running(self, handle=None):
426  return self._processes_processes[handle].poll() is None
427 
428 
434  def process_should_be_running(self, handle=None,
435  error_message='Process is not running.'):
436  if not self.is_process_runningis_process_running(handle):
437  raise AssertionError(error_message)
438 
439 
445  def process_should_be_stopped(self, handle=None,
446  error_message='Process is running.'):
447  if self.is_process_runningis_process_running(handle):
448  raise AssertionError(error_message)
449 
450 
498  def wait_for_process(self, handle=None, timeout=None, on_timeout='continue'):
499  process = self._processes_processes[handle]
500  logger.info('Waiting for process to complete.')
501  timeout = self._get_timeout_get_timeout(timeout)
502  if timeout > 0:
503  if not self._process_is_stopped_process_is_stopped(process, timeout):
504  logger.info('Process did not complete in %s.'
505  % secs_to_timestr(timeout))
506  return self._manage_process_timeout_manage_process_timeout(handle, on_timeout.lower())
507  return self._wait_wait(process)
508 
509  def _get_timeout(self, timeout):
510  if (is_string(timeout) and timeout.upper() == 'NONE') or not timeout:
511  return -1
512  return timestr_to_secs(timeout)
513 
514  def _manage_process_timeout(self, handle, on_timeout):
515  if on_timeout == 'terminate':
516  return self.terminate_processterminate_process(handle)
517  elif on_timeout == 'kill':
518  return self.terminate_processterminate_process(handle, kill=True)
519  else:
520  logger.info('Leaving process intact.')
521  return None
522 
523  def _wait(self, process):
524  result = self._results_results[process]
525  result.rc = process.wait() or 0
526  result.close_streams()
527  logger.info('Process completed.')
528  return result
529 
530 
560  def terminate_process(self, handle=None, kill=False):
561  process = self._processes_processes[handle]
562  if not hasattr(process, 'terminate'):
563  raise RuntimeError('Terminating processes is not supported '
564  'by this Python version.')
565  terminator = self._kill_kill if is_truthy(kill) else self._terminate_terminate
566  try:
567  terminator(process)
568  except OSError:
569  if not self._process_is_stopped_process_is_stopped(process, self.KILL_TIMEOUTKILL_TIMEOUT):
570  raise
571  logger.debug('Ignored OSError because process was stopped.')
572  return self._wait_wait(process)
573 
574  def _kill(self, process):
575  logger.info('Forcefully killing process.')
576  if hasattr(os, 'killpg'):
577  os.killpg(process.pid, signal_module.SIGKILL)
578  else:
579  process.kill()
580  if not self._process_is_stopped_process_is_stopped(process, self.KILL_TIMEOUTKILL_TIMEOUT):
581  raise RuntimeError('Failed to kill process.')
582 
583  def _terminate(self, process):
584  logger.info('Gracefully terminating process.')
585  # Sends signal to the whole process group both on POSIX and on Windows
586  # if supported by the interpreter.
587  if hasattr(os, 'killpg'):
588  os.killpg(process.pid, signal_module.SIGTERM)
589  elif hasattr(signal_module, 'CTRL_BREAK_EVENT'):
590  process.send_signal(signal_module.CTRL_BREAK_EVENT)
591  else:
592  process.terminate()
593  if not self._process_is_stopped_process_is_stopped(process, self.TERMINATE_TIMEOUTTERMINATE_TIMEOUT):
594  logger.info('Graceful termination failed.')
595  self._kill_kill(process)
596 
597 
606  def terminate_all_processes(self, kill=False):
607  for handle in range(1, len(self._processes_processes) + 1):
608  if self.is_process_runningis_process_running(handle):
609  self.terminate_processterminate_process(handle, kill=kill)
610  self.__init____init__()
611 
612 
638  def send_signal_to_process(self, signal, handle=None, group=False):
639  if os.sep == '\\':
640  raise RuntimeError('This keyword does not work on Windows.')
641  process = self._processes_processes[handle]
642  signum = self._get_signal_number_get_signal_number(signal)
643  logger.info('Sending signal %s (%d).' % (signal, signum))
644  if is_truthy(group) and hasattr(os, 'killpg'):
645  os.killpg(process.pid, signum)
646  elif hasattr(process, 'send_signal'):
647  process.send_signal(signum)
648  else:
649  raise RuntimeError('Sending signals is not supported '
650  'by this Python version.')
651 
652  def _get_signal_number(self, int_or_name):
653  try:
654  return int(int_or_name)
655  except ValueError:
656  return self._convert_signal_name_to_number_convert_signal_name_to_number(int_or_name)
657 
659  try:
660  return getattr(signal_module,
661  name if name.startswith('SIG') else 'SIG' + name)
662  except AttributeError:
663  raise RuntimeError("Unsupported signal '%s'." % name)
664 
665 
673  def get_process_id(self, handle=None):
674  return self._processes_processes[handle].pid
675 
676 
684  def get_process_object(self, handle=None):
685  return self._processes_processes[handle]
686 
687 
726  def get_process_result(self, handle=None, rc=False, stdout=False,
727  stderr=False, stdout_path=False, stderr_path=False):
728  result = self._results_results[self._processes_processes[handle]]
729  if result.rc is None:
730  raise RuntimeError('Getting results of unfinished processes '
731  'is not supported.')
732  attributes = self._get_result_attributes_get_result_attributes(result, rc, stdout, stderr,
733  stdout_path, stderr_path)
734  if not attributes:
735  return result
736  elif len(attributes) == 1:
737  return attributes[0]
738  return attributes
739 
740  def _get_result_attributes(self, result, *includes):
741  attributes = (result.rc, result.stdout, result.stderr,
742  result.stdout_path, result.stderr_path)
743  includes = (is_truthy(incl) for incl in includes)
744  return tuple(attr for attr, incl in zip(attributes, includes) if incl)
745 
746 
758  def switch_process(self, handle):
759  self._processes_processes.switch(handle)
760 
761  def _process_is_stopped(self, process, timeout):
762  stopped = lambda: process.poll() is not None
763  max_time = time.time() + timeout
764  while time.time() <= max_time and not stopped():
765  time.sleep(min(0.1, timeout))
766  return stopped()
767 
768 
780  def split_command_line(self, args, escaping=False):
781  return cmdline2list(args, escaping=escaping)
782 
783 
796  def join_command_line(self, *args):
797  if len(args) == 1 and is_list_like(args[0]):
798  args = args[0]
799  return subprocess.list2cmdline(args)
800 
801 
803 
804  def __init__(self, process, stdout, stderr, stdin=None, rc=None,
805  output_encoding=None):
806  self._process_process = process
807  self.stdout_pathstdout_path = self._get_path_get_path(stdout)
808  self.stderr_pathstderr_path = self._get_path_get_path(stderr)
809  self.rcrc = rc
810  self._output_encoding_output_encoding = output_encoding
811  self._stdout_stdout = None
812  self._stderr_stderr = None
813  self._custom_streams_custom_streams = [stream for stream in (stdout, stderr, stdin)
814  if self._is_custom_stream_is_custom_stream(stream)]
815 
816  def _get_path(self, stream):
817  return stream.name if self._is_custom_stream_is_custom_stream(stream) else None
818 
819  def _is_custom_stream(self, stream):
820  return stream not in (subprocess.PIPE, subprocess.STDOUT, None)
821 
822  @property
823  stdout = property
824 
825  def stdout(self):
826  if self._stdout_stdout is None:
827  self._read_stdout_read_stdout()
828  return self._stdout_stdout
829 
830  @property
831  stderr = property
832 
833  def stderr(self):
834  if self._stderr_stderr is None:
835  self._read_stderr_read_stderr()
836  return self._stderr_stderr
837 
838  def _read_stdout(self):
839  self._stdout_stdout = self._read_stream_read_stream(self.stdout_pathstdout_path, self._process_process.stdout)
840 
841  def _read_stderr(self):
842  self._stderr_stderr = self._read_stream_read_stream(self.stderr_pathstderr_path, self._process_process.stderr)
843 
844  def _read_stream(self, stream_path, stream):
845  if stream_path:
846  stream = open(stream_path, 'rb')
847  elif not self._is_open_is_open(stream):
848  return ''
849  try:
850  content = stream.read()
851  except IOError:
852  content = ''
853  finally:
854  if stream_path:
855  stream.close()
856  return self._format_output_format_output(content)
857 
858  def _is_open(self, stream):
859  return stream and not stream.closed
860 
861  def _format_output(self, output):
862  output = console_decode(output, self._output_encoding_output_encoding)
863  output = output.replace('\r\n', '\n')
864  if output.endswith('\n'):
865  output = output[:-1]
866  return output
867 
868  def close_streams(self):
869  standard_streams = self._get_and_read_standard_streams_get_and_read_standard_streams(self._process_process)
870  for stream in standard_streams + self._custom_streams_custom_streams:
871  if self._is_open_is_open(stream):
872  stream.close()
873 
874  def _get_and_read_standard_streams(self, process):
875  stdin, stdout, stderr = process.stdin, process.stdout, process.stderr
876  if stdout:
877  self._read_stdout_read_stdout()
878  if stderr:
879  self._read_stderr_read_stderr()
880  return [stdin, stdout, stderr]
881 
882  def __str__(self):
883  return '<result object with rc %d>' % self.rcrc
884 
885 
887 
888  def __init__(self, cwd=None, shell=False, stdout=None, stderr=None, stdin='PIPE',
889  output_encoding='CONSOLE', alias=None, env=None, **rest):
890  self.cwdcwd = os.path.normpath(cwd) if cwd else abspath('.')
891  self.shellshell = is_truthy(shell)
892  self.aliasalias = alias
893  self.output_encodingoutput_encoding = output_encoding
894  self.stdout_streamstdout_stream = self._new_stream_new_stream(stdout)
895  self.stderr_streamstderr_stream = self._get_stderr_get_stderr(stderr, stdout, self.stdout_streamstdout_stream)
896  self.stdin_streamstdin_stream = self._get_stdin_get_stdin(stdin)
897  self.envenv = self._construct_env_construct_env(env, rest)
898 
899  def _new_stream(self, name):
900  if name == 'DEVNULL':
901  return open(os.devnull, 'w')
902  if name:
903  path = os.path.normpath(os.path.join(self.cwdcwd, name))
904  return open(path, 'w')
905  return subprocess.PIPE
906 
907  def _get_stderr(self, stderr, stdout, stdout_stream):
908  if stderr and stderr in ['STDOUT', stdout]:
909  if stdout_stream != subprocess.PIPE:
910  return stdout_stream
911  return subprocess.STDOUT
912  return self._new_stream_new_stream(stderr)
913 
914  def _get_stdin(self, stdin):
915  if is_pathlike(stdin):
916  stdin = str(stdin)
917  elif not is_string(stdin):
918  return stdin
919  elif stdin.upper() == 'NONE':
920  return None
921  elif stdin == 'PIPE':
922  return subprocess.PIPE
923  path = os.path.normpath(os.path.join(self.cwdcwd, stdin))
924  if os.path.isfile(path):
925  return open(path)
926  stdin_file = TemporaryFile()
927  stdin_file.write(console_encode(stdin, self.output_encodingoutput_encoding, force=True))
928  stdin_file.seek(0)
929  return stdin_file
930 
931  def _construct_env(self, env, extra):
932  env = self._get_initial_env_get_initial_env(env, extra)
933  if env is None:
934  return None
935  if WINDOWS:
936  env = NormalizedDict(env, spaceless=False)
937  self._add_to_env_add_to_env(env, extra)
938  if WINDOWS:
939  env = dict((key.upper(), env[key]) for key in env)
940  return env
941 
942  def _get_initial_env(self, env, extra):
943  if env:
944  return dict((system_encode(k), system_encode(env[k])) for k in env)
945  if extra:
946  return os.environ.copy()
947  return None
948 
949  def _add_to_env(self, env, extra):
950  for key in extra:
951  if not key.startswith('env:'):
952  raise RuntimeError("Keyword argument '%s' is not supported by "
953  "this keyword." % key)
954  env[system_encode(key[4:])] = system_encode(extra[key])
955 
956  def get_command(self, command, arguments):
957  command = [system_encode(item) for item in [command] + arguments]
958  if not self.shellshell:
959  return command
960  if arguments:
961  return subprocess.list2cmdline(command)
962  return command[0]
963 
964  @property
965  popen_config = property
966 
967  def popen_config(self):
968  config = {'stdout': self.stdout_streamstdout_stream,
969  'stderr': self.stderr_streamstderr_stream,
970  'stdin': self.stdin_streamstdin_stream,
971  'shell': self.shellshell,
972  'cwd': self.cwdcwd,
973  'env': self.envenv}
974  # Close file descriptors regardless the Python version:
975  # https://github.com/robotframework/robotframework/issues/2794
976  if not WINDOWS:
977  config['close_fds'] = True
978  self._add_process_group_config_add_process_group_config(config)
979  return config
980 
981  def _add_process_group_config(self, config):
982  if hasattr(os, 'setsid'):
983  config['preexec_fn'] = os.setsid
984  if hasattr(subprocess, 'CREATE_NEW_PROCESS_GROUP'):
985  config['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
986 
987  @property
988  result_config = property
989 
990  def result_config(self):
991  return {'stdout': self.stdout_streamstdout_stream,
992  'stderr': self.stderr_streamstderr_stream,
993  'stdin': self.stdin_streamstdin_stream,
994  'output_encoding': self.output_encodingoutput_encoding}
995 
996  def __str__(self):
997  return """\
998 cwd: %s
999 shell: %s
1000 stdout: %s
1001 stderr: %s
1002 stdin: %s
1003 alias: %s
1004 env: %s""" % (self.cwdcwd,
1005  self.shellshell,
1006  self._stream_name_stream_name(self.stdout_streamstdout_stream),
1007  self._stream_name_stream_name(self.stderr_streamstderr_stream),
1008  self._stream_name_stream_name(self.stdin_streamstdin_stream),
1009  self.aliasalias,
1010  self.envenv)
1011 
1012  def _stream_name(self, stream):
1013  if hasattr(stream, 'name'):
1014  return stream.name
1015  return {subprocess.PIPE: 'PIPE',
1016  subprocess.STDOUT: 'STDOUT',
1017  None: 'None'}.get(stream, stream)
def __init__(self, process, stdout, stderr, stdin=None, rc=None, output_encoding=None)
Definition: Process.py:805
def _get_and_read_standard_streams(self, process)
Definition: Process.py:874
def _read_stream(self, stream_path, stream)
Definition: Process.py:844
def _get_stderr(self, stderr, stdout, stdout_stream)
Definition: Process.py:907
def __init__(self, cwd=None, shell=False, stdout=None, stderr=None, stdin='PIPE', output_encoding='CONSOLE', alias=None, env=None, **rest)
Definition: Process.py:889
def _get_initial_env(self, env, extra)
Definition: Process.py:942
def get_command(self, command, arguments)
Definition: Process.py:956
Robot Framework library for running processes.
Definition: Process.py:318
def _manage_process_timeout(self, handle, on_timeout)
Definition: Process.py:514
def _terminate(self, process)
Definition: Process.py:583
def get_process_id(self, handle=None)
Returns the process ID (pid) of the process as an integer.
Definition: Process.py:673
def start_process(self, command, *arguments, **configuration)
Starts a new process on background.
Definition: Process.py:404
def _get_signal_number(self, int_or_name)
Definition: Process.py:652
def run_process(self, command, *arguments, **configuration)
Runs a process and waits for it to complete.
Definition: Process.py:357
def _log_start(self, command, config)
Definition: Process.py:413
def _get_result_attributes(self, result, *includes)
Definition: Process.py:740
def _process_is_stopped(self, process, timeout)
Definition: Process.py:761
def terminate_all_processes(self, kill=False)
Terminates all still running processes started by this library.
Definition: Process.py:606
def is_process_running(self, handle=None)
Checks is the process running or not.
Definition: Process.py:425
def _convert_signal_name_to_number(self, name)
Definition: Process.py:658
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:498
def process_should_be_stopped(self, handle=None, error_message='Process is running.')
Verifies that the process is not running.
Definition: Process.py:446
def _kill(self, process)
Definition: Process.py:574
def terminate_process(self, handle=None, kill=False)
Stops the process gracefully or forcefully.
Definition: Process.py:560
def switch_process(self, handle)
Makes the specified process the current active process.
Definition: Process.py:758
def send_signal_to_process(self, signal, handle=None, group=False)
Sends the given signal to the specified process.
Definition: Process.py:638
def _wait(self, process)
Definition: Process.py:523
def _get_timeout(self, timeout)
Definition: Process.py:509
def join_command_line(self, *args)
Joins arguments into one command line string.
Definition: Process.py:796
def get_process_object(self, handle=None)
Return the underlying subprocess.Popen object.
Definition: Process.py:684
def split_command_line(self, args, escaping=False)
Splits command line string into a list of arguments.
Definition: Process.py:780
def process_should_be_running(self, handle=None, error_message='Process is not running.')
Verifies that the process is running.
Definition: Process.py:435
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:727
def cmdline2list(args, escaping=False)
def console_decode(string, encoding=CONSOLE_ENCODING)
Decodes bytes from console encoding to Unicode.
Definition: encoding.py:39
def system_encode(string)
Definition: encoding.py:86
def system_decode(string)
Definition: encoding.py:82
def console_encode(string, encoding=None, errors='replace', stream=sys.__stdout__, force=False)
Encodes the given string so that it can be used in the console.
Definition: encoding.py:61
def abspath(path, case_normalize=False)
Replacement for os.path.abspath with some enhancements and bug fixes.
Definition: robotpath.py:65
def secs_to_timestr(secs, compact=False)
Converts time in seconds to a string representation.
Definition: robottime.py:151
def timestr_to_secs(timestr, round_to=3, accept_plain_values=True)
Parses time strings like '1h 10s', '01:00:10' and '42' and returns seconds.
Definition: robottime.py:55
def is_truthy(item)
Returns True or False depending on is the item considered true or not.
Definition: robottypes.py:162
def is_list_like(item)
Definition: robottypes.py:66
def get_version(naked=False)
Definition: version.py:24