Home | History | Annotate | Download | only in android_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 tempfile
     23 import threading
     24 import time
     25 
     26 # local imports
     27 import errors
     28 import logger
     29 
     30 _abort_on_error = False
     31 
     32 def SetAbortOnError(abort=True):
     33   """Sets behavior of RunCommand to throw AbortError if command process returns
     34   a negative error code"""
     35   global _abort_on_error
     36   _abort_on_error = abort
     37 
     38 def RunCommand(cmd, timeout_time=None, retry_count=3, return_output=True,
     39                stdin_input=None):
     40   """Spawn and retry a subprocess to run the given shell command.
     41 
     42   Args:
     43     cmd: shell command to run
     44     timeout_time: time in seconds to wait for command to run before aborting.
     45     retry_count: number of times to retry command
     46     return_output: if True return output of command as string. Otherwise,
     47       direct output of command to stdout.
     48     stdin_input: data to feed to stdin
     49   Returns:
     50     output of command
     51   """
     52   result = None
     53   while True:
     54     try:
     55       result = RunOnce(cmd, timeout_time=timeout_time,
     56                        return_output=return_output, stdin_input=stdin_input)
     57     except errors.WaitForResponseTimedOutError:
     58       if retry_count == 0:
     59         raise
     60       retry_count -= 1
     61       logger.Log("No response for %s, retrying" % cmd)
     62     else:
     63       # Success
     64       return result
     65 
     66 def RunOnce(cmd, timeout_time=None, return_output=True, stdin_input=None):
     67   """Spawns a subprocess to run the given shell command.
     68 
     69   Args:
     70     cmd: shell command to run
     71     timeout_time: time in seconds to wait for command to run before aborting.
     72     return_output: if True return output of command as string. Otherwise,
     73       direct output of command to stdout.
     74     stdin_input: data to feed to stdin
     75   Returns:
     76     output of command
     77   Raises:
     78     errors.WaitForResponseTimedOutError if command did not complete within
     79       timeout_time seconds.
     80     errors.AbortError is command returned error code and SetAbortOnError is on.
     81   """
     82   start_time = time.time()
     83   so = []
     84   global _abort_on_error, error_occurred
     85   error_occurred = False
     86 
     87   if return_output:
     88     output_dest = tempfile.TemporaryFile(bufsize=0)
     89   else:
     90     # None means direct to stdout
     91     output_dest = None
     92   if stdin_input:
     93     stdin_dest = subprocess.PIPE
     94   else:
     95     stdin_dest = None
     96   pipe = subprocess.Popen(
     97       cmd,
     98       executable='/bin/bash',
     99       stdin=stdin_dest,
    100       stdout=output_dest,
    101       stderr=subprocess.STDOUT,
    102       shell=True, close_fds=True,
    103       preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL))
    104 
    105   def Run():
    106     global error_occurred
    107     try:
    108       pipe.communicate(input=stdin_input)
    109       output = None
    110       if return_output:
    111         output_dest.seek(0)
    112         output = output_dest.read()
    113         output_dest.close()
    114       if output is not None and len(output) > 0:
    115         so.append(output)
    116     except OSError, e:
    117       logger.SilentLog("failed to retrieve stdout from: %s" % cmd)
    118       logger.Log(e)
    119       so.append("ERROR")
    120       error_occurred = True
    121     if pipe.returncode:
    122       logger.SilentLog("Error: %s returned %d error code" %(cmd,
    123           pipe.returncode))
    124       error_occurred = True
    125 
    126   t = threading.Thread(target=Run)
    127   t.start()
    128   t.join(timeout_time)
    129   if t.isAlive():
    130     try:
    131       pipe.kill()
    132     except OSError:
    133       # Can't kill a dead process.
    134       pass
    135     finally:
    136       logger.SilentLog("about to raise a timeout for: %s" % cmd)
    137       raise errors.WaitForResponseTimedOutError
    138 
    139   output = "".join(so)
    140   if _abort_on_error and error_occurred:
    141     raise errors.AbortError(msg=output)
    142 
    143   return "".join(so)
    144 
    145 
    146 def RunHostCommand(binary, valgrind=False):
    147   """Run a command on the host (opt using valgrind).
    148 
    149   Runs the host binary and returns the exit code.
    150   If successfull, the output (stdout and stderr) are discarded,
    151   but printed in case of error.
    152   The command can be run under valgrind in which case all the
    153   output are always discarded.
    154 
    155   Args:
    156     binary: full path of the file to be run.
    157     valgrind: If True the command will be run under valgrind.
    158 
    159   Returns:
    160     The command exit code (int)
    161   """
    162   if not valgrind:
    163     subproc = subprocess.Popen(binary, stdout=subprocess.PIPE,
    164                                stderr=subprocess.STDOUT)
    165     subproc.wait()
    166     if subproc.returncode != 0:         # In case of error print the output
    167       print subproc.communicate()[0]
    168     return subproc.returncode
    169   else:
    170     # Need the full path to valgrind to avoid other versions on the system.
    171     subproc = subprocess.Popen(["/usr/bin/valgrind", "--tool=memcheck",
    172                                 "--leak-check=yes", "-q", binary],
    173                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    174     # Cannot rely on the retcode of valgrind. Instead look for an empty output.
    175     valgrind_out = subproc.communicate()[0].strip()
    176     if valgrind_out:
    177       print valgrind_out
    178       return 1
    179     else:
    180       return 0
    181 
    182 
    183 def HasValgrind():
    184   """Check that /usr/bin/valgrind exists.
    185 
    186   We look for the fullpath to avoid picking up 'alternative' valgrind
    187   on the system.
    188 
    189   Returns:
    190     True if a system valgrind was found.
    191   """
    192   return os.path.exists("/usr/bin/valgrind")
    193