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