Home | History | Annotate | Download | only in valgrind
      1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import platform
      7 import os
      8 import signal
      9 import subprocess
     10 import sys
     11 import time
     12 
     13 
     14 class NotImplementedError(Exception):
     15   pass
     16 
     17 
     18 class TimeoutError(Exception):
     19   pass
     20 
     21 
     22 def RunSubprocessInBackground(proc):
     23   """Runs a subprocess in the background. Returns a handle to the process."""
     24   logging.info("running %s in the background" % " ".join(proc))
     25   return subprocess.Popen(proc)
     26 
     27 
     28 def RunSubprocess(proc, timeout=0):
     29   """ Runs a subprocess, until it finishes or |timeout| is exceeded and the
     30   process is killed with taskkill.  A |timeout| <= 0  means no timeout.
     31 
     32   Args:
     33     proc: list of process components (exe + args)
     34     timeout: how long to wait before killing, <= 0 means wait forever
     35   """
     36 
     37   logging.info("running %s, timeout %d sec" % (" ".join(proc), timeout))
     38   sys.stdout.flush()
     39   sys.stderr.flush()
     40 
     41   # Manually read and print out stdout and stderr.
     42   # By default, the subprocess is supposed to inherit these from its parent,
     43   # however when run under buildbot, it seems unable to read data from a
     44   # grandchild process, so we have to read the child and print the data as if
     45   # it came from us for buildbot to read it.  We're not sure why this is
     46   # necessary.
     47   # TODO(erikkay): should we buffer stderr and stdout separately?
     48   p = subprocess.Popen(proc, universal_newlines=True,
     49                        bufsize=0,  # unbuffered
     50                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     51 
     52   logging.info("started subprocess")
     53 
     54   did_timeout = False
     55   if timeout > 0:
     56     wait_until = time.time() + timeout
     57   while p.poll() is None and not did_timeout:
     58     # Have to use readline rather than readlines() or "for line in p.stdout:",
     59     # otherwise we get buffered even with bufsize=0.
     60     line = p.stdout.readline()
     61     while line and not did_timeout:
     62       sys.stdout.write(line)
     63       sys.stdout.flush()
     64       line = p.stdout.readline()
     65       if timeout > 0:
     66         did_timeout = time.time() > wait_until
     67 
     68   if did_timeout:
     69     logging.info("process timed out")
     70   else:
     71     logging.info("process ended, did not time out")
     72 
     73   if did_timeout:
     74     if IsWindows():
     75       subprocess.call(["taskkill", "/T", "/F", "/PID", str(p.pid)])
     76     else:
     77       # Does this kill all children, too?
     78       os.kill(p.pid, signal.SIGINT)
     79     logging.error("KILLED %d" % p.pid)
     80     # Give the process a chance to actually die before continuing
     81     # so that cleanup can happen safely.
     82     time.sleep(1.0)
     83     logging.error("TIMEOUT waiting for %s" % proc[0])
     84     raise TimeoutError(proc[0])
     85   else:
     86     for line in p.stdout:
     87       sys.stdout.write(line)
     88     if not IsMac():   # stdout flush fails on Mac
     89       logging.info("flushing stdout")
     90       sys.stdout.flush()
     91 
     92   logging.info("collecting result code")
     93   result = p.poll()
     94   if result:
     95     logging.error("%s exited with non-zero result code %d" % (proc[0], result))
     96   return result
     97 
     98 
     99 def IsLinux():
    100   return sys.platform.startswith('linux')
    101 
    102 
    103 def IsMac():
    104   return sys.platform.startswith('darwin')
    105 
    106 
    107 def IsWindows():
    108   return sys.platform == 'cygwin' or sys.platform.startswith('win')
    109 
    110 
    111 def WindowsVersionName():
    112   """Returns the name of the Windows version if it is known, or None.
    113 
    114   Possible return values are: xp, vista, 7, 8, or None
    115   """
    116   if sys.platform == 'cygwin':
    117     # Windows version number is hiding in system name.  Looks like:
    118     # CYGWIN_NT-6.1-WOW64
    119     try:
    120       version_str = platform.uname()[0].split('-')[1]
    121     except:
    122       return None
    123   elif sys.platform.startswith('win'):
    124     # Normal Windows version string.  Mine: 6.1.7601
    125     version_str = platform.version()
    126   else:
    127     return None
    128 
    129   parts = version_str.split('.')
    130   try:
    131     major = int(parts[0])
    132     minor = int(parts[1])
    133   except:
    134     return None  # Can't parse, unknown version.
    135 
    136   if major == 5:
    137     return 'xp'
    138   elif major == 6 and minor == 0:
    139     return 'vista'
    140   elif major == 6 and minor == 1:
    141     return '7'
    142   elif major == 6 and minor == 2:
    143     return '8'  # Future proof.  ;)
    144   return None
    145 
    146 
    147 def PlatformNames():
    148   """Return an array of string to be used in paths for the platform
    149   (e.g. suppressions, gtest filters, ignore files etc.)
    150   The first element of the array describes the 'main' platform
    151   """
    152   if IsLinux():
    153     return ['linux']
    154   if IsMac():
    155     return ['mac']
    156   if IsWindows():
    157     names = ['win32']
    158     version_name = WindowsVersionName()
    159     if version_name is not None:
    160       names.append('win-%s' % version_name)
    161     return names
    162   raise NotImplementedError('Unknown platform "%s".' % sys.platform)
    163 
    164 
    165 def PutEnvAndLog(env_name, env_value):
    166   os.putenv(env_name, env_value)
    167   logging.info('export %s=%s', env_name, env_value)
    168 
    169 def BoringCallers(mangled, use_re_wildcards):
    170   """Return a list of 'boring' function names (optinally mangled)
    171   with */? wildcards (optionally .*/.).
    172   Boring = we drop off the bottom of stack traces below such functions.
    173   """
    174 
    175   need_mangling = [
    176     # Don't show our testing framework:
    177     ("testing::Test::Run",     "_ZN7testing4Test3RunEv"),
    178     ("testing::TestInfo::Run", "_ZN7testing8TestInfo3RunEv"),
    179     ("testing::internal::Handle*ExceptionsInMethodIfSupported*",
    180      "_ZN7testing8internal3?Handle*ExceptionsInMethodIfSupported*"),
    181 
    182     # Depend on scheduling:
    183     ("MessageLoop::Run",     "_ZN11MessageLoop3RunEv"),
    184     ("MessageLoop::RunTask", "_ZN11MessageLoop7RunTask*"),
    185     ("RunnableMethod*",      "_ZN14RunnableMethod*"),
    186     ("DispatchToMethod*",    "_Z*16DispatchToMethod*"),
    187     ("base::internal::Invoker*::DoInvoke*",
    188      "_ZN4base8internal8Invoker*DoInvoke*"),  # Invoker{1,2,3}
    189     ("base::internal::RunnableAdapter*::Run*",
    190      "_ZN4base8internal15RunnableAdapter*Run*"),
    191   ]
    192 
    193   ret = []
    194   for pair in need_mangling:
    195     ret.append(pair[1 if mangled else 0])
    196 
    197   ret += [
    198     # Also don't show the internals of libc/pthread.
    199     "start_thread",
    200     "main",
    201     "BaseThreadInitThunk",
    202   ]
    203 
    204   if use_re_wildcards:
    205     for i in range(0, len(ret)):
    206       ret[i] = ret[i].replace('*', '.*').replace('?', '.')
    207 
    208   return ret
    209 
    210 def NormalizeWindowsPath(path):
    211   """If we're using Cygwin Python, turn the path into a Windows path.
    212 
    213   Don't turn forward slashes into backslashes for easier copy-pasting and
    214   escaping.
    215 
    216   TODO(rnk): If we ever want to cut out the subprocess invocation, we can use
    217   _winreg to get the root Cygwin directory from the registry key:
    218   HKEY_LOCAL_MACHINE\SOFTWARE\Cygwin\setup\rootdir.
    219   """
    220   if sys.platform.startswith("cygwin"):
    221     p = subprocess.Popen(["cygpath", "-m", path],
    222                          stdout=subprocess.PIPE,
    223                          stderr=subprocess.PIPE)
    224     (out, err) = p.communicate()
    225     if err:
    226       logging.warning("WARNING: cygpath error: %s", err)
    227     return out.strip()
    228   else:
    229     return path
    230 
    231 ############################
    232 # Common output format code
    233 
    234 def PrintUsedSuppressionsList(suppcounts):
    235   """ Prints out the list of used suppressions in a format common to all the
    236       memory tools. If the list is empty, prints nothing and returns False,
    237       otherwise True.
    238 
    239       suppcounts: a dictionary of used suppression counts,
    240                   Key -> name, Value -> count.
    241   """
    242   if not suppcounts:
    243     return False
    244 
    245   print "-----------------------------------------------------"
    246   print "Suppressions used:"
    247   print "  count name"
    248   for (name, count) in sorted(suppcounts.items(), key=lambda (k,v): (v,k)):
    249     print "%7d %s" % (count, name)
    250   print "-----------------------------------------------------"
    251   sys.stdout.flush()
    252   return True
    253