253 lines
7.5 KiB
Python
253 lines
7.5 KiB
Python
|
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||
|
# Use of this source code is governed by a BSD-style license that can be
|
||
|
# found in the LICENSE file.
|
||
|
|
||
|
import logging
|
||
|
import platform
|
||
|
import os
|
||
|
import signal
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import time
|
||
|
|
||
|
|
||
|
class NotImplementedError(Exception):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class TimeoutError(Exception):
|
||
|
pass
|
||
|
|
||
|
|
||
|
def RunSubprocessInBackground(proc):
|
||
|
"""Runs a subprocess in the background. Returns a handle to the process."""
|
||
|
logging.info("running %s in the background" % " ".join(proc))
|
||
|
return subprocess.Popen(proc)
|
||
|
|
||
|
|
||
|
def RunSubprocess(proc, timeout=0):
|
||
|
""" Runs a subprocess, until it finishes or |timeout| is exceeded and the
|
||
|
process is killed with taskkill. A |timeout| <= 0 means no timeout.
|
||
|
|
||
|
Args:
|
||
|
proc: list of process components (exe + args)
|
||
|
timeout: how long to wait before killing, <= 0 means wait forever
|
||
|
"""
|
||
|
|
||
|
logging.info("running %s, timeout %d sec" % (" ".join(proc), timeout))
|
||
|
sys.stdout.flush()
|
||
|
sys.stderr.flush()
|
||
|
|
||
|
# Manually read and print out stdout and stderr.
|
||
|
# By default, the subprocess is supposed to inherit these from its parent,
|
||
|
# however when run under buildbot, it seems unable to read data from a
|
||
|
# grandchild process, so we have to read the child and print the data as if
|
||
|
# it came from us for buildbot to read it. We're not sure why this is
|
||
|
# necessary.
|
||
|
# TODO(erikkay): should we buffer stderr and stdout separately?
|
||
|
p = subprocess.Popen(proc, universal_newlines=True,
|
||
|
bufsize=0, # unbuffered
|
||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||
|
|
||
|
logging.info("started subprocess")
|
||
|
|
||
|
did_timeout = False
|
||
|
if timeout > 0:
|
||
|
wait_until = time.time() + timeout
|
||
|
while p.poll() is None and not did_timeout:
|
||
|
# Have to use readline rather than readlines() or "for line in p.stdout:",
|
||
|
# otherwise we get buffered even with bufsize=0.
|
||
|
line = p.stdout.readline()
|
||
|
while line and not did_timeout:
|
||
|
sys.stdout.write(line)
|
||
|
sys.stdout.flush()
|
||
|
line = p.stdout.readline()
|
||
|
if timeout > 0:
|
||
|
did_timeout = time.time() > wait_until
|
||
|
|
||
|
if did_timeout:
|
||
|
logging.info("process timed out")
|
||
|
else:
|
||
|
logging.info("process ended, did not time out")
|
||
|
|
||
|
if did_timeout:
|
||
|
if IsWindows():
|
||
|
subprocess.call(["taskkill", "/T", "/F", "/PID", str(p.pid)])
|
||
|
else:
|
||
|
# Does this kill all children, too?
|
||
|
os.kill(p.pid, signal.SIGINT)
|
||
|
logging.error("KILLED %d" % p.pid)
|
||
|
# Give the process a chance to actually die before continuing
|
||
|
# so that cleanup can happen safely.
|
||
|
time.sleep(1.0)
|
||
|
logging.error("TIMEOUT waiting for %s" % proc[0])
|
||
|
raise TimeoutError(proc[0])
|
||
|
else:
|
||
|
for line in p.stdout:
|
||
|
sys.stdout.write(line)
|
||
|
if not IsMac(): # stdout flush fails on Mac
|
||
|
logging.info("flushing stdout")
|
||
|
sys.stdout.flush()
|
||
|
|
||
|
logging.info("collecting result code")
|
||
|
result = p.poll()
|
||
|
if result:
|
||
|
logging.error("%s exited with non-zero result code %d" % (proc[0], result))
|
||
|
return result
|
||
|
|
||
|
|
||
|
def IsLinux():
|
||
|
return sys.platform.startswith('linux')
|
||
|
|
||
|
|
||
|
def IsMac():
|
||
|
return sys.platform.startswith('darwin')
|
||
|
|
||
|
|
||
|
def IsWindows():
|
||
|
return sys.platform == 'cygwin' or sys.platform.startswith('win')
|
||
|
|
||
|
|
||
|
def WindowsVersionName():
|
||
|
"""Returns the name of the Windows version if it is known, or None.
|
||
|
|
||
|
Possible return values are: xp, vista, 7, 8, or None
|
||
|
"""
|
||
|
if sys.platform == 'cygwin':
|
||
|
# Windows version number is hiding in system name. Looks like:
|
||
|
# CYGWIN_NT-6.1-WOW64
|
||
|
try:
|
||
|
version_str = platform.uname()[0].split('-')[1]
|
||
|
except:
|
||
|
return None
|
||
|
elif sys.platform.startswith('win'):
|
||
|
# Normal Windows version string. Mine: 6.1.7601
|
||
|
version_str = platform.version()
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
parts = version_str.split('.')
|
||
|
try:
|
||
|
major = int(parts[0])
|
||
|
minor = int(parts[1])
|
||
|
except:
|
||
|
return None # Can't parse, unknown version.
|
||
|
|
||
|
if major == 5:
|
||
|
return 'xp'
|
||
|
elif major == 6 and minor == 0:
|
||
|
return 'vista'
|
||
|
elif major == 6 and minor == 1:
|
||
|
return '7'
|
||
|
elif major == 6 and minor == 2:
|
||
|
return '8' # Future proof. ;)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def PlatformNames():
|
||
|
"""Return an array of string to be used in paths for the platform
|
||
|
(e.g. suppressions, gtest filters, ignore files etc.)
|
||
|
The first element of the array describes the 'main' platform
|
||
|
"""
|
||
|
if IsLinux():
|
||
|
return ['linux']
|
||
|
if IsMac():
|
||
|
return ['mac']
|
||
|
if IsWindows():
|
||
|
names = ['win32']
|
||
|
version_name = WindowsVersionName()
|
||
|
if version_name is not None:
|
||
|
names.append('win-%s' % version_name)
|
||
|
return names
|
||
|
raise NotImplementedError('Unknown platform "%s".' % sys.platform)
|
||
|
|
||
|
|
||
|
def PutEnvAndLog(env_name, env_value):
|
||
|
os.putenv(env_name, env_value)
|
||
|
logging.info('export %s=%s', env_name, env_value)
|
||
|
|
||
|
def BoringCallers(mangled, use_re_wildcards):
|
||
|
"""Return a list of 'boring' function names (optinally mangled)
|
||
|
with */? wildcards (optionally .*/.).
|
||
|
Boring = we drop off the bottom of stack traces below such functions.
|
||
|
"""
|
||
|
|
||
|
need_mangling = [
|
||
|
# Don't show our testing framework:
|
||
|
("testing::Test::Run", "_ZN7testing4Test3RunEv"),
|
||
|
("testing::TestInfo::Run", "_ZN7testing8TestInfo3RunEv"),
|
||
|
("testing::internal::Handle*ExceptionsInMethodIfSupported*",
|
||
|
"_ZN7testing8internal3?Handle*ExceptionsInMethodIfSupported*"),
|
||
|
|
||
|
# Depend on scheduling:
|
||
|
("MessageLoop::Run", "_ZN11MessageLoop3RunEv"),
|
||
|
("MessageLoop::RunTask", "_ZN11MessageLoop7RunTask*"),
|
||
|
("RunnableMethod*", "_ZN14RunnableMethod*"),
|
||
|
("DispatchToMethod*", "_Z*16DispatchToMethod*"),
|
||
|
("base::internal::Invoker*::DoInvoke*",
|
||
|
"_ZN4base8internal8Invoker*DoInvoke*"), # Invoker{1,2,3}
|
||
|
("base::internal::RunnableAdapter*::Run*",
|
||
|
"_ZN4base8internal15RunnableAdapter*Run*"),
|
||
|
]
|
||
|
|
||
|
ret = []
|
||
|
for pair in need_mangling:
|
||
|
ret.append(pair[1 if mangled else 0])
|
||
|
|
||
|
ret += [
|
||
|
# Also don't show the internals of libc/pthread.
|
||
|
"start_thread",
|
||
|
"main",
|
||
|
"BaseThreadInitThunk",
|
||
|
]
|
||
|
|
||
|
if use_re_wildcards:
|
||
|
for i in range(0, len(ret)):
|
||
|
ret[i] = ret[i].replace('*', '.*').replace('?', '.')
|
||
|
|
||
|
return ret
|
||
|
|
||
|
def NormalizeWindowsPath(path):
|
||
|
"""If we're using Cygwin Python, turn the path into a Windows path.
|
||
|
|
||
|
Don't turn forward slashes into backslashes for easier copy-pasting and
|
||
|
escaping.
|
||
|
|
||
|
TODO(rnk): If we ever want to cut out the subprocess invocation, we can use
|
||
|
_winreg to get the root Cygwin directory from the registry key:
|
||
|
HKEY_LOCAL_MACHINE\SOFTWARE\Cygwin\setup\rootdir.
|
||
|
"""
|
||
|
if sys.platform.startswith("cygwin"):
|
||
|
p = subprocess.Popen(["cygpath", "-m", path],
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.PIPE)
|
||
|
(out, err) = p.communicate()
|
||
|
if err:
|
||
|
logging.warning("WARNING: cygpath error: %s", err)
|
||
|
return out.strip()
|
||
|
else:
|
||
|
return path
|
||
|
|
||
|
############################
|
||
|
# Common output format code
|
||
|
|
||
|
def PrintUsedSuppressionsList(suppcounts):
|
||
|
""" Prints out the list of used suppressions in a format common to all the
|
||
|
memory tools. If the list is empty, prints nothing and returns False,
|
||
|
otherwise True.
|
||
|
|
||
|
suppcounts: a dictionary of used suppression counts,
|
||
|
Key -> name, Value -> count.
|
||
|
"""
|
||
|
if not suppcounts:
|
||
|
return False
|
||
|
|
||
|
print "-----------------------------------------------------"
|
||
|
print "Suppressions used:"
|
||
|
print " count name"
|
||
|
for (name, count) in sorted(suppcounts.items(), key=lambda (k,v): (v,k)):
|
||
|
print "%7d %s" % (count, name)
|
||
|
print "-----------------------------------------------------"
|
||
|
sys.stdout.flush()
|
||
|
return True
|