Fix build break
[chromium-blink-merge.git] / tools / valgrind / common.py
bloba333fdeeb913033f5875f58f9f286000bfbc774c
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.
5 import logging
6 import platform
7 import os
8 import signal
9 import subprocess
10 import sys
11 import time
14 class NotImplementedError(Exception):
15 pass
18 class TimeoutError(Exception):
19 pass
22 def _print_line(line, flush=True):
23 # Printing to a text file (including stdout) on Windows always winds up
24 # using \r\n automatically. On buildbot, this winds up being read by a master
25 # running on Linux, so we manually convert crlf to '\n'
26 print line.rstrip() + '\n',
27 if flush:
28 sys.stdout.flush()
31 def RunSubprocessInBackground(proc):
32 """Runs a subprocess in the background. Returns a handle to the process."""
33 logging.info("running %s in the background" % " ".join(proc))
34 return subprocess.Popen(proc)
37 def RunSubprocess(proc, timeout=0, detach=False, background=False):
38 """ Runs a subprocess, until it finishes or |timeout| is exceeded and the
39 process is killed with taskkill. A |timeout| <= 0 means no timeout.
41 Args:
42 proc: list of process components (exe + args)
43 timeout: how long to wait before killing, <= 0 means wait forever
44 detach: Whether to pass the DETACHED_PROCESS argument to CreateProcess
45 on Windows. This is used by Purify subprocesses on buildbot which
46 seem to get confused by the parent console that buildbot sets up.
47 """
49 logging.info("running %s, timeout %d sec" % (" ".join(proc), timeout))
50 if detach:
51 # see MSDN docs for "Process Creation Flags"
52 DETACHED_PROCESS = 0x8
53 p = subprocess.Popen(proc, creationflags=DETACHED_PROCESS)
54 else:
55 # For non-detached processes, manually read and print out stdout and stderr.
56 # By default, the subprocess is supposed to inherit these from its parent,
57 # however when run under buildbot, it seems unable to read data from a
58 # grandchild process, so we have to read the child and print the data as if
59 # it came from us for buildbot to read it. We're not sure why this is
60 # necessary.
61 # TODO(erikkay): should we buffer stderr and stdout separately?
62 p = subprocess.Popen(proc, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
64 logging.info("started subprocess")
66 # How long to wait (in seconds) before printing progress log messages.
67 progress_delay = 300
68 progress_delay_time = time.time() + progress_delay
69 did_timeout = False
70 if timeout > 0:
71 wait_until = time.time() + timeout
72 while p.poll() is None and not did_timeout:
73 if not detach:
74 line = p.stdout.readline()
75 while line and not did_timeout:
76 _print_line(line)
77 line = p.stdout.readline()
78 if timeout > 0:
79 did_timeout = time.time() > wait_until
80 else:
81 # When we detach, blocking on reading stdout doesn't work, so we sleep
82 # a short time and poll.
83 time.sleep(0.5)
84 if time.time() >= progress_delay_time:
85 # Force output on a periodic basis to avoid getting killed off by the
86 # buildbot.
87 # TODO(erikkay): I'd prefer a less obtrusive 'print ".",' with a flush
88 # but because of how we're doing subprocesses, this doesn't appear to
89 # work reliably.
90 logging.info("%s still running..." % os.path.basename(proc[0]))
91 progress_delay_time = time.time() + progress_delay
92 if timeout > 0:
93 did_timeout = time.time() > wait_until
95 if did_timeout:
96 logging.info("process timed out")
97 else:
98 logging.info("process ended, did not time out")
100 if did_timeout:
101 if IsWindows():
102 subprocess.call(["taskkill", "/T", "/F", "/PID", str(p.pid)])
103 else:
104 # Does this kill all children, too?
105 os.kill(p.pid, signal.SIGINT)
106 logging.error("KILLED %d" % p.pid)
107 # Give the process a chance to actually die before continuing
108 # so that cleanup can happen safely.
109 time.sleep(1.0)
110 logging.error("TIMEOUT waiting for %s" % proc[0])
111 raise TimeoutError(proc[0])
112 elif not detach:
113 for line in p.stdout.readlines():
114 _print_line(line, False)
115 if not IsMac(): # stdout flush fails on Mac
116 logging.info("flushing stdout")
117 p.stdout.flush()
119 logging.info("collecting result code")
120 result = p.poll()
121 if result:
122 logging.error("%s exited with non-zero result code %d" % (proc[0], result))
123 return result
126 def IsLinux():
127 return sys.platform.startswith('linux')
130 def IsMac():
131 return sys.platform.startswith('darwin')
134 def IsWindows():
135 return sys.platform == 'cygwin' or sys.platform.startswith('win')
138 def WindowsVersionName():
139 """Returns the name of the Windows version if it is known, or None.
141 Possible return values are: xp, vista, 7, 8, or None
143 if sys.platform == 'cygwin':
144 # Windows version number is hiding in system name. Looks like:
145 # CYGWIN_NT-6.1-WOW64
146 try:
147 version_str = platform.uname()[0].split('-')[1]
148 except:
149 return None
150 elif sys.platform.startswith('win'):
151 # Normal Windows version string. Mine: 6.1.7601
152 version_str = platform.version()
153 else:
154 return None
156 parts = version_str.split('.')
157 try:
158 major = int(parts[0])
159 minor = int(parts[1])
160 except:
161 return None # Can't parse, unknown version.
163 if major == 5:
164 return 'xp'
165 elif major == 6 and minor == 0:
166 return 'vista'
167 elif major == 6 and minor == 1:
168 return '7'
169 elif major == 6 and minor == 2:
170 return '8' # Future proof. ;)
171 return None
174 def PlatformNames():
175 """Return an array of string to be used in paths for the platform
176 (e.g. suppressions, gtest filters, ignore files etc.)
177 The first element of the array describes the 'main' platform
179 if IsLinux():
180 return ['linux']
181 if IsMac():
182 return ['mac']
183 if IsWindows():
184 names = ['win32']
185 version_name = WindowsVersionName()
186 if version_name is not None:
187 names.append('win-%s' % version_name)
188 return names
189 raise NotImplementedError('Unknown platform "%s".' % sys.platform)
192 def PutEnvAndLog(env_name, env_value):
193 os.putenv(env_name, env_value)
194 logging.info('export %s=%s', env_name, env_value)
196 def BoringCallers(mangled, use_re_wildcards):
197 """Return a list of 'boring' function names (optinally mangled)
198 with */? wildcards (optionally .*/.).
199 Boring = we drop off the bottom of stack traces below such functions.
202 need_mangling = [
203 # Don't show our testing framework:
204 ("testing::Test::Run", "_ZN7testing4Test3RunEv"),
205 ("testing::TestInfo::Run", "_ZN7testing8TestInfo3RunEv"),
206 ("testing::internal::Handle*ExceptionsInMethodIfSupported*",
207 "_ZN7testing8internal3?Handle*ExceptionsInMethodIfSupported*"),
209 # Depend on scheduling:
210 ("MessageLoop::Run", "_ZN11MessageLoop3RunEv"),
211 ("MessageLoop::RunTask", "_ZN11MessageLoop7RunTask*"),
212 ("RunnableMethod*", "_ZN14RunnableMethod*"),
213 ("DispatchToMethod*", "_Z*16DispatchToMethod*"),
214 ("base::internal::Invoker*::DoInvoke*",
215 "_ZN4base8internal8Invoker*DoInvoke*"), # Invoker{1,2,3}
216 ("base::internal::RunnableAdapter*::Run*",
217 "_ZN4base8internal15RunnableAdapter*Run*"),
220 ret = []
221 for pair in need_mangling:
222 ret.append(pair[1 if mangled else 0])
224 ret += [
225 # Also don't show the internals of libc/pthread.
226 "start_thread",
227 "main",
228 "BaseThreadInitThunk",
231 if use_re_wildcards:
232 for i in range(0, len(ret)):
233 ret[i] = ret[i].replace('*', '.*').replace('?', '.')
235 return ret
237 def NormalizeWindowsPath(path):
238 """If we're using Cygwin Python, turn the path into a Windows path.
240 Don't turn forward slashes into backslashes for easier copy-pasting and
241 escaping.
243 TODO(rnk): If we ever want to cut out the subprocess invocation, we can use
244 _winreg to get the root Cygwin directory from the registry key:
245 HKEY_LOCAL_MACHINE\SOFTWARE\Cygwin\setup\rootdir.
247 if sys.platform.startswith("cygwin"):
248 p = subprocess.Popen(["cygpath", "-m", path],
249 stdout=subprocess.PIPE,
250 stderr=subprocess.PIPE)
251 (out, err) = p.communicate()
252 if err:
253 logging.warning("WARNING: cygpath error: %s", err)
254 return out.strip()
255 else:
256 return path
258 ############################
259 # Common output format code
261 def PrintUsedSuppressionsList(suppcounts):
262 """ Prints out the list of used suppressions in a format common to all the
263 memory tools. If the list is empty, prints nothing and returns False,
264 otherwise True.
266 suppcounts: a dictionary of used suppression counts,
267 Key -> name, Value -> count.
269 if not suppcounts:
270 return False
272 print "-----------------------------------------------------"
273 print "Suppressions used:"
274 print " count name"
275 for (name, count) in sorted(suppcounts.items(), key=lambda (k,v): (v,k)):
276 print "%7d %s" % (count, name)
277 print "-----------------------------------------------------"
278 sys.stdout.flush()
279 return True