OLD | NEW |
(Empty) | |
| 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 |
OLD | NEW |