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 9import logging 10import platform 11import os 12import signal 13import subprocess 14import sys 15import time 16 17 18class NotImplementedError(Exception): 19 pass 20 21 22class TimeoutError(Exception): 23 pass 24 25 26def 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 32def 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 103def IsLinux(): 104 return sys.platform.startswith('linux') 105 106 107def IsMac(): 108 return sys.platform.startswith('darwin') 109 110 111def IsWindows(): 112 return sys.platform == 'cygwin' or sys.platform.startswith('win') 113 114 115def 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 151def 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 169def PutEnvAndLog(env_name, env_value): 170 os.putenv(env_name, env_value) 171 logging.info('export %s=%s', env_name, env_value) 172 173def 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 214def 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 238def 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 257