1# Copyright 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 glob
6import imp
7import logging
8import os
9import socket
10import sys
11
12import py_utils as catapult_util  # pylint: disable=import-error
13
14
15IsRunningOnCrosDevice = ( # pylint: disable=invalid-name
16    catapult_util.IsRunningOnCrosDevice)
17GetCatapultDir = catapult_util.GetCatapultDir # pylint: disable=invalid-name
18
19
20def GetBaseDir():
21  main_module = sys.modules['__main__']
22  if hasattr(main_module, '__file__'):
23    return os.path.dirname(os.path.abspath(main_module.__file__))
24  else:
25    return os.getcwd()
26
27
28def GetCatapultThirdPartyDir():
29  return os.path.normpath(os.path.join(GetCatapultDir(), 'third_party'))
30
31
32def GetTelemetryDir():
33  return os.path.normpath(os.path.join(
34      os.path.abspath(__file__), '..', '..', '..'))
35
36
37def GetTelemetryThirdPartyDir():
38  return os.path.join(GetTelemetryDir(), 'third_party')
39
40
41def GetUnittestDataDir():
42  return os.path.join(GetTelemetryDir(), 'telemetry', 'internal', 'testing')
43
44
45def GetChromiumSrcDir():
46  return os.path.normpath(os.path.join(GetTelemetryDir(), '..', '..', '..'))
47
48
49_COUNTER = [0]
50
51
52def _GetUniqueModuleName():
53  _COUNTER[0] += 1
54  return "page_set_module_" + str(_COUNTER[0])
55
56
57def GetPythonPageSetModule(file_path):
58  return imp.load_source(_GetUniqueModuleName(), file_path)
59
60
61def GetUnreservedAvailableLocalPort():
62  """Returns an available port on the system.
63
64  WARNING: This method does not reserve the port it returns, so it may be used
65  by something else before you get to use it. This can lead to flake.
66  """
67  # AF_INET restricts port to IPv4 addresses.
68  # SOCK_STREAM means that it is a TCP socket.
69  tmp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
70  # Setting SOL_SOCKET + SO_REUSEADDR to 1 allows the reuse of local addresses,
71  # tihs is so sockets do not fail to bind for being in the CLOSE_WAIT state.
72  tmp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
73  tmp.bind(('', 0))
74  port = tmp.getsockname()[1]
75  tmp.close()
76
77  return port
78
79
80def GetBuildDirectories(chrome_root=None):
81  """Yields all combination of Chromium build output directories."""
82  # chrome_root can be set to something else via --chrome-root.
83  if not chrome_root:
84    chrome_root = GetChromiumSrcDir()
85
86  # CHROMIUM_OUTPUT_DIR can be set by --chromium-output-directory.
87  output_dir = os.environ.get('CHROMIUM_OUTPUT_DIR')
88  if output_dir:
89    if os.path.isabs(output_dir):
90      yield output_dir
91    else:
92      yield os.path.join(chrome_root, output_dir)
93  elif os.path.exists('build.ninja'):
94    yield os.getcwd()
95  else:
96    out_dir = os.environ.get('CHROMIUM_OUT_DIR')
97    if out_dir:
98      build_dirs = [out_dir]
99    else:
100      build_dirs = ['build',
101                    'out',
102                    'xcodebuild']
103
104    build_types = ['Debug', 'Debug_x64', 'Release', 'Release_x64', 'Default']
105
106    for build_dir in build_dirs:
107      for build_type in build_types:
108        yield os.path.join(chrome_root, build_dir, build_type)
109
110
111def GetUsedBuildDirectory(browser_directory=None, chrome_root=None):
112  """Gets the build directory that's likely being used.
113
114  Args:
115    browser_directory: The path to the directory the browser is stored in, i.e.
116        what is returned by a PossibleBrowser's browser_directory property.
117    chrome_root: A path to the Chromium src root. Defaults to the value returned
118        by GetChromiumSrcDir().
119
120  Returns:
121    A path to the directory that most likely contains the build artifacts for
122    the browser being used.
123  """
124  # This is expected to not exist on platforms that run remotely, e.g. Android
125  # and CrOS. In cases where this exists but doesn't point to a build directory,
126  # such as using stable Chrome, none of the other possible directories will be
127  # valid, either, so this is as good as any.
128  if browser_directory is not None and os.path.exists(browser_directory):
129    return browser_directory
130  # Otherwise, check if any of the other known directories exist, returning the
131  # first one and defaulting to the current directory.
132  build_dir = os.getcwd()
133  for b in GetBuildDirectories(chrome_root=chrome_root):
134    if os.path.exists(b):
135      build_dir = b
136      break
137  return build_dir
138
139
140def FindLatestApkOnHost(chrome_root, apk_name):
141  """Chooses the path to the APK that was updated latest.
142
143  If CHROMIUM_OUTPUT_DIR environment variable is set, the search is limited to
144  checking one APK file in that directory.
145
146  Args:
147    chrome_root: Path to chrome src/.
148    apk_name: The name of the APK file to find (example: 'MapsWebApk.apk').
149  Returns:
150    The absolute path to the latest APK found.
151  """
152  found_apk_path = None
153  latest_mtime = 0
154  for build_path in GetBuildDirectories(chrome_root):
155    apk_path = os.path.join(build_path, 'apks', apk_name)
156    if os.path.exists(apk_path):
157      mtime = os.path.getmtime(apk_path)
158      if mtime > latest_mtime:
159        latest_mtime = mtime
160        found_apk_path = apk_path
161  return found_apk_path
162
163
164def GetBuildDirFromHostApkPath(apk_path):
165  """Finds the build directory for an APK if possible.
166
167  Args:
168    apk_path: A string containing a path to an APK.
169
170  Returns:
171    A string containing a path to the build directory for the APK, or None if
172    it cannot be determined.
173  """
174  if not apk_path:
175    return None
176  parent_dir = os.path.basename(os.path.dirname(apk_path))
177  # Locally built regular APKs should be in out/Foo/apks/, while locally built
178  # bundle APKs should be in out/Foo/bin/. We want out/Foo in this case.
179  if parent_dir == 'apks' or parent_dir == 'bin':
180    return os.path.dirname(os.path.dirname(apk_path))
181  # Otherwise, we're probably a prebuilt binary.
182  return None
183
184
185def GetSequentialFileName(base_name):
186  """Returns the next sequential file name based on |base_name| and the
187  existing files. base_name should not contain extension.
188  e.g: if base_name is /tmp/test, and /tmp/test_000.json,
189  /tmp/test_001.mp3 exist, this returns /tmp/test_002. In case no
190  other sequential file name exist, this will return /tmp/test_000
191  """
192  name, ext = os.path.splitext(base_name)
193  assert ext == '', 'base_name cannot contain file extension.'
194  index = 0
195  while True:
196    output_name = '%s_%03d' % (name, index)
197    if not glob.glob(output_name + '.*'):
198      break
199    index = index + 1
200  return output_name
201
202
203def LogExtraDebugInformation(*args):
204  """Call methods to obtain and log additional debug information.
205
206  Example usage:
207
208      def RunCommandWhichOutputsUsefulInfo():
209        '''Output of some/useful_command'''
210        return subprocess.check_output(
211            ['bin/some/useful_command', '--with', 'args']).splitlines()
212
213      def ReadFileWithUsefulInfo():
214        '''Contents of that debug.info file'''
215        with open('/path/to/that/debug.info') as f:
216          for line in f:
217            yield line
218
219      LogExtraDebugInformation(
220          RunCommandWhichOutputsUsefulInfo,
221          ReadFileWithUsefulInfo
222      )
223
224  Args:
225    Each arg is expected to be a function (or method) to be called with no
226    arguments and returning a sequence of lines with debug info to be logged.
227    The docstring of the function is also logged to provide further context.
228    Exceptions raised during the call are ignored (but also logged), so it's
229    OK to make calls which may fail.
230  """
231  # For local testing you may switch this to e.g. logging.WARNING
232  level = logging.DEBUG
233  if logging.getLogger().getEffectiveLevel() > level:
234    logging.warning('Increase verbosity to see more debug information.')
235    return
236
237  logging.log(level, '== Dumping possibly useful debug information ==')
238  for get_debug_lines in args:
239    logging.log(level, '- %s:', get_debug_lines.__doc__)
240    try:
241      for line in get_debug_lines():
242        logging.log(level, '  - %s', line)
243    except Exception:  # pylint: disable=broad-except
244      logging.log(level, 'Ignoring exception raised during %s.',
245                  get_debug_lines.__name__, exc_info=True)
246  logging.log(level, '===============================================')
247