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