Home | History | Annotate | Download | only in testrunner
      1 #!/usr/bin/python2.4
      2 #
      3 #
      4 # Copyright 2007, The Android Open Source Project
      5 #
      6 # Licensed under the Apache License, Version 2.0 (the "License");
      7 # you may not use this file except in compliance with the License.
      8 # You may obtain a copy of the License at
      9 #
     10 #     http://www.apache.org/licenses/LICENSE-2.0
     11 #
     12 # Unless required by applicable law or agreed to in writing, software
     13 # distributed under the License is distributed on an "AS IS" BASIS,
     14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 # See the License for the specific language governing permissions and
     16 # limitations under the License.
     17 
     18 # System imports
     19 import os
     20 import signal
     21 import subprocess
     22 import threading
     23 import time
     24 
     25 # local imports
     26 import errors
     27 import logger
     28 
     29 _abort_on_error = False
     30 
     31 def SetAbortOnError(abort=True):
     32   """Sets behavior of RunCommand to throw AbortError if command process returns
     33   a negative error code"""
     34   global _abort_on_error
     35   _abort_on_error = abort
     36 
     37 def RunCommand(cmd, timeout_time=None, retry_count=3, return_output=True,
     38                stdin_input=None):
     39   """Spawn and retry a subprocess to run the given shell command.
     40 
     41   Args:
     42     cmd: shell command to run
     43     timeout_time: time in seconds to wait for command to run before aborting.
     44     retry_count: number of times to retry command
     45     return_output: if True return output of command as string. Otherwise,
     46       direct output of command to stdout.
     47     stdin_input: data to feed to stdin
     48   Returns:
     49     output of command
     50   """
     51   result = None
     52   while True:
     53     try:
     54       result = RunOnce(cmd, timeout_time=timeout_time,
     55                        return_output=return_output, stdin_input=stdin_input)
     56     except errors.WaitForResponseTimedOutError:
     57       if retry_count == 0:
     58         raise
     59       retry_count -= 1
     60       logger.Log("No response for %s, retrying" % cmd)
     61     else:
     62       # Success
     63       return result
     64 
     65 def RunOnce(cmd, timeout_time=None, return_output=True, stdin_input=None):
     66   """Spawns a subprocess to run the given shell command.
     67 
     68   Args:
     69     cmd: shell command to run
     70     timeout_time: time in seconds to wait for command to run before aborting.
     71     return_output: if True return output of command as string. Otherwise,
     72       direct output of command to stdout.
     73     stdin_input: data to feed to stdin
     74   Returns:
     75     output of command
     76   Raises:
     77     errors.WaitForResponseTimedOutError if command did not complete within
     78       timeout_time seconds.
     79     errors.AbortError is command returned error code and SetAbortOnError is on.
     80   """
     81   start_time = time.time()
     82   so = []
     83   pid = []
     84   global _abort_on_error, error_occurred
     85   error_occurred = False
     86 
     87   def Run():
     88     global error_occurred
     89     if return_output:
     90       output_dest = subprocess.PIPE
     91     else:
     92       # None means direct to stdout
     93       output_dest = None
     94     if stdin_input:
     95       stdin_dest = subprocess.PIPE
     96     else:
     97       stdin_dest = None
     98     pipe = subprocess.Popen(
     99         cmd,
    100         executable='/bin/bash',
    101         stdin=stdin_dest,
    102         stdout=output_dest,
    103         stderr=subprocess.STDOUT,
    104         shell=True)
    105     pid.append(pipe.pid)
    106     try:
    107       output = pipe.communicate(input=stdin_input)[0]
    108       if output is not None and len(output) > 0:
    109         so.append(output)
    110     except OSError, e:
    111       logger.SilentLog("failed to retrieve stdout from: %s" % cmd)
    112       logger.Log(e)
    113       so.append("ERROR")
    114       error_occurred = True
    115     if pipe.returncode:
    116       logger.SilentLog("Error: %s returned %d error code" %(cmd,
    117           pipe.returncode))
    118       error_occurred = True
    119 
    120   t = threading.Thread(target=Run)
    121   t.start()
    122 
    123   break_loop = False
    124   while not break_loop:
    125     if not t.isAlive():
    126       break_loop = True
    127 
    128     # Check the timeout
    129     if (not break_loop and timeout_time is not None
    130         and time.time() > start_time + timeout_time):
    131       try:
    132         os.kill(pid[0], signal.SIGKILL)
    133       except OSError:
    134         # process already dead. No action required.
    135         pass
    136 
    137       logger.SilentLog("about to raise a timeout for: %s" % cmd)
    138       raise errors.WaitForResponseTimedOutError
    139     if not break_loop:
    140       time.sleep(0.1)
    141 
    142   t.join()
    143   output = "".join(so)
    144   if _abort_on_error and error_occurred:
    145     raise errors.AbortError(msg=output)
    146 
    147   return "".join(so)
    148 
    149 
    150 def RunHostCommand(binary, valgrind=False):
    151   """Run a command on the host (opt using valgrind).
    152 
    153   Runs the host binary and returns the exit code.
    154   If successfull, the output (stdout and stderr) are discarded,
    155   but printed in case of error.
    156   The command can be run under valgrind in which case all the
    157   output are always discarded.
    158 
    159   Args:
    160     binary: full path of the file to be run.
    161     valgrind: If True the command will be run under valgrind.
    162 
    163   Returns:
    164     The command exit code (int)
    165   """
    166   if not valgrind:
    167     subproc = subprocess.Popen(binary, stdout=subprocess.PIPE,
    168                                stderr=subprocess.STDOUT)
    169     subproc.wait()
    170     if subproc.returncode != 0:         # In case of error print the output
    171       print subproc.communicate()[0]
    172     return subproc.returncode
    173   else:
    174     # Need the full path to valgrind to avoid other versions on the system.
    175     subproc = subprocess.Popen(["/usr/bin/valgrind", "--tool=memcheck",
    176                                 "--leak-check=yes", "-q", binary],
    177                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    178     # Cannot rely on the retcode of valgrind. Instead look for an empty output.
    179     valgrind_out = subproc.communicate()[0].strip()
    180     if valgrind_out:
    181       print valgrind_out
    182       return 1
    183     else:
    184       return 0
    185 
    186 
    187 def HasValgrind():
    188   """Check that /usr/bin/valgrind exists.
    189 
    190   We look for the fullpath to avoid picking up 'alternative' valgrind
    191   on the system.
    192 
    193   Returns:
    194     True if a system valgrind was found.
    195   """
    196   return os.path.exists("/usr/bin/valgrind")
    197