2
0

common.py 7.7 KB

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