• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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