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.
14 class NotImplementedError(Exception):
18 class TimeoutError(Exception):
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',
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.
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.
49 logging
.info("running %s, timeout %d sec" % (" ".join(proc
), timeout
))
51 # see MSDN docs for "Process Creation Flags"
52 DETACHED_PROCESS
= 0x8
53 p
= subprocess
.Popen(proc
, creationflags
=DETACHED_PROCESS
)
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
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.
68 progress_delay_time
= time
.time() + progress_delay
71 wait_until
= time
.time() + timeout
72 while p
.poll() is None and not did_timeout
:
74 line
= p
.stdout
.readline()
75 while line
and not did_timeout
:
77 line
= p
.stdout
.readline()
79 did_timeout
= time
.time() > wait_until
81 # When we detach, blocking on reading stdout doesn't work, so we sleep
82 # a short time and poll.
84 if time
.time() >= progress_delay_time
:
85 # Force output on a periodic basis to avoid getting killed off by the
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
90 logging
.info("%s still running..." % os
.path
.basename(proc
[0]))
91 progress_delay_time
= time
.time() + progress_delay
93 did_timeout
= time
.time() > wait_until
96 logging
.info("process timed out")
98 logging
.info("process ended, did not time out")
102 subprocess
.call(["taskkill", "/T", "/F", "/PID", str(p
.pid
)])
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.
110 logging
.error("TIMEOUT waiting for %s" % proc
[0])
111 raise TimeoutError(proc
[0])
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")
119 logging
.info("collecting result code")
122 logging
.error("%s exited with non-zero result code %d" % (proc
[0], result
))
127 return sys
.platform
.startswith('linux')
131 return sys
.platform
.startswith('darwin')
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
147 version_str
= platform
.uname()[0].split('-')[1]
150 elif sys
.platform
.startswith('win'):
151 # Normal Windows version string. Mine: 6.1.7601
152 version_str
= platform
.version()
156 parts
= version_str
.split('.')
158 major
= int(parts
[0])
159 minor
= int(parts
[1])
161 return None # Can't parse, unknown version.
165 elif major
== 6 and minor
== 0:
167 elif major
== 6 and minor
== 1:
169 elif major
== 6 and minor
== 2:
170 return '8' # Future proof. ;)
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
185 version_name
= WindowsVersionName()
186 if version_name
is not None:
187 names
.append('win-%s' % version_name
)
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.
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*"),
221 for pair
in need_mangling
:
222 ret
.append(pair
[1 if mangled
else 0])
225 # Also don't show the internals of libc/pthread.
228 "BaseThreadInitThunk",
232 for i
in range(0, len(ret
)):
233 ret
[i
] = ret
[i
].replace('*', '.*').replace('?', '.')
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
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()
253 logging
.warning("WARNING: cygpath error: %s", err
)
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,
266 suppcounts: a dictionary of used suppression counts,
267 Key -> name, Value -> count.
272 print "-----------------------------------------------------"
273 print "Suppressions used:"
275 for (name
, count
) in sorted(suppcounts
.items(), key
=lambda (k
,v
): (v
,k
)):
276 print "%7d %s" % (count
, name
)
277 print "-----------------------------------------------------"