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