1#!/usr/bin/env python 2# Copyright (c) 2012 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Automate the setup process of Chrome Endure environment. 7 8Usage: 9 python endure_setup.py [option] 10 11We use <ENDURE_DIR> to refer to the root directory in which Chrome Endure 12is set up. By default, <ENDURE_DIR> is the current working directory. 13 14First, run: 15 >python endure_setup.py 16This command will automatically setup Chrome Endure in <ENDURE_DIR>. 17 18Next, run your first endure test by: 19 >TEST_LENGTH=30 LOCAL_PERF_DIR="<ENDURE_DIR>/chrome_graph" \\ 20 python <ENDURE_DIR>/src/chrome/test/functional/perf_endure.py \\ 21 perf_endure.ChromeEndureGmailTest.testGmailComposeDiscard \\ 22The above commands runs a Chrome Endure test for 30 seconds and saves 23the results to <ENDURE_DIR>/chrome_graph. 24 25Last, to view the graphs, run another script endure_server.py 26within <ENDURE_DIR> to start a local HTTP server that serves 27the graph directory, see endure_server.py for details. 28 29Use python endure_setup.py --help for more options. 30 31This script depends on the following modules 32(which will be downloaded automatically): 33 depot_tools 34 src/chrome/test/pyautolib/fetch_prebuilt_pyauto.py 35 36Supported platforms: Linux and Linux_x64. 37""" 38 39import logging 40import optparse 41import os 42import platform 43import shutil 44import subprocess 45import sys 46import urllib 47import urllib2 48import zipfile 49 50URLS = {'depot_tools': ('http://src.chromium.org' 51 '/chrome/trunk/tools/depot_tools'), 52 'pyauto': ('https://src.chromium.org/' 53 'chrome/trunk/src/chrome/test/functional.DEPS'), 54 'binary': ('http://commondatastorage.googleapis.com/' 55 'chromium-browser-continuous/{os_type}/{revision}'), 56 } 57 58 59class SetupError(Exception): 60 """Catch errors in setting up Chrome Endure.""" 61 pass 62 63 64class HelpFormatter(optparse.IndentedHelpFormatter): 65 """Format the help message of this script.""" 66 67 def format_description(self, description): 68 """Override to keep the original format of the description.""" 69 return description + '\n' if description else '' 70 71 72def Main(argv): 73 """Fetch Chrome Endure. 74 75 Usage: 76 python endure_setup.py [options] 77 78 Examples: 79 >python endure_setup.py 80 Fetch the latest version of Chrome Endure to the current 81 working directory. 82 83 >python endure_setup.py --endure-dir=/home/user/endure_dir 84 Fetch the latest version of Chrome Endure to /home/user/endure_dir. 85 """ 86 parser = optparse.OptionParser( 87 formatter=HelpFormatter(), description=Main.__doc__) 88 parser.add_option( 89 '-d', '--endure-dir', type='string', default=os.getcwd(), 90 help='Directory in which to setup or update. ' \ 91 'Default value is the current working directory.') 92 # TODO(fdeng): remove this option once the Chrome Endure 93 # graphing code is checked into chrome tree. 94 parser.add_option( 95 '-g', '--graph-zip-url', type='string', default=None, 96 help='URL to a zip file containing the chrome graphs.') 97 os_type = GetCurrentOSType() 98 if not os_type.startswith('Linux'): 99 raise SetupError('Only support Linux or Linux_x64, %s found' 100 % os_type) 101 options, _ = parser.parse_args(argv) 102 endure_dir = os.path.abspath(options.endure_dir) 103 depot_dir = os.path.join(endure_dir, 'depot_tools') 104 gclient = os.path.join(depot_dir, 'gclient') 105 fetch_py = os.path.join(endure_dir, 'src', 'chrome', 106 'test', 'pyautolib', 107 'fetch_prebuilt_pyauto.py') 108 binary_dir = os.path.join(endure_dir, 'src', 'out', 'Release') 109 graph_zip_url = options.graph_zip_url 110 graph_dir = os.path.join(endure_dir, 'chrome_graph') 111 112 if not os.path.isdir(endure_dir): 113 os.makedirs(endure_dir) 114 115 logging.info('Fetching depot tools...') 116 FetchDepot(depot_dir) 117 logging.info('Fetching PyAuto (python code)...') 118 FetchPyAuto(gclient, endure_dir) 119 logging.info('Fetching binaries(chrome, pyautolib, chrome driver)...') 120 FetchBinaries(fetch_py, binary_dir, os_type) 121 # TODO(fdeng): remove this after it is checked into the chrome tree. 122 logging.info('Fetching chrome graphing files...') 123 FetchGraph(graph_zip_url, graph_dir) 124 return 0 125 126 127def FetchDepot(depot_dir): 128 """Fetch depot_tools. 129 130 Args: 131 depot_dir: The directory where depot_tools will be checked out. 132 133 Raises: 134 SetupError: If fail. 135 """ 136 if subprocess.call(['svn', 'co', URLS['depot_tools'], depot_dir]) != 0: 137 raise SetupError('Error found when checking out depot_tools.') 138 if not CheckDepot(depot_dir): 139 raise SetupError('Could not get depot_tools.') 140 141 142def CheckDepot(depot_dir): 143 """Check that some expected depot_tools files exist. 144 145 Args: 146 depot_dir: The directory where depot_tools are checked out. 147 148 Returns: 149 True if check passes otherwise False. 150 """ 151 gclient = os.path.join(depot_dir, 'gclient') 152 gclient_py = os.path.join(depot_dir, 'gclient.py') 153 files = [gclient, gclient_py] 154 for f in files: 155 if not os.path.exists(f): 156 return False 157 try: 158 subprocess.call([gclient, '--version']) 159 except OSError: 160 return False 161 return True 162 163 164def FetchPyAuto(gclient, endure_dir): 165 """Use gclient to fetch python code. 166 167 Args: 168 gclient: The path to the gclient executable. 169 endure_dir: Directory where Chrome Endure and 170 its dependencies will be checked out. 171 172 Raises: 173 SetupError: if fails. 174 """ 175 cur_dir = os.getcwd() 176 os.chdir(endure_dir) 177 config_cmd = [gclient, 'config', URLS['pyauto']] 178 if subprocess.call(config_cmd) != 0: 179 raise SetupError('Running "%s" failed.' % ' '.join(config_cmd)) 180 sync_cmd = [gclient, 'sync'] 181 if subprocess.call(sync_cmd) != 0: 182 raise SetupError('Running "%s" failed.' % ' '.join(sync_cmd)) 183 CheckPyAuto(endure_dir) 184 logging.info('Sync PyAuto python code done.') 185 os.chdir(cur_dir) 186 187 188def CheckPyAuto(endure_dir): 189 """Sanity check for Chrome Endure code. 190 191 Args: 192 endure_dir: Directory of Chrome Endure and its dependencies. 193 194 Raises: 195 SetupError: If fails. 196 """ 197 fetch_py = os.path.join(endure_dir, 'src', 'chrome', 198 'test', 'pyautolib', 199 'fetch_prebuilt_pyauto.py') 200 pyauto_py = os.path.join(endure_dir, 'src', 201 'chrome', 'test', 202 'pyautolib', 'pyauto.py') 203 files = [fetch_py, pyauto_py] 204 for f in files: 205 if not os.path.exists(f): 206 raise SetupError('Checking %s failed.' % f) 207 208 209def FetchBinaries(fetch_py, binary_dir, os_type): 210 """Get the prebuilt binaries from continuous build archive. 211 212 Args: 213 fetch_py: Path to the script which fetches pre-built binaries. 214 binary_dir: Directory of the pre-built binaries. 215 os_type: 'Mac', 'Win', 'Linux', 'Linux_x64'. 216 217 Raises: 218 SetupError: If fails. 219 """ 220 revision = GetLatestRevision(os_type) 221 logging.info('Cleaning %s', binary_dir) 222 if os.path.exists(binary_dir): 223 shutil.rmtree(binary_dir) 224 logging.info('Downloading binaries...') 225 cmd = [fetch_py, '-d', binary_dir, 226 URLS['binary'].format( 227 os_type=os_type, revision=revision)] 228 if subprocess.call(cmd) == 0 and os.path.exists(binary_dir): 229 logging.info('Binaries at revision %s', revision) 230 else: 231 raise SetupError('Running "%s" failed.' % ' '.join(cmd)) 232 233 234def FetchGraph(graph_zip_url, graph_dir): 235 """Fetch graph code. 236 237 Args: 238 graph_zip_url: The url to a zip file containing the chrome graphs. 239 graph_dir: Directory of the chrome graphs. 240 241 Raises: 242 SetupError: if unable to retrive the zip file. 243 """ 244 # TODO(fdeng): remove this function once chrome graph 245 # is checked into chrome tree. 246 if not graph_zip_url: 247 logging.info( 248 'Skip fetching chrome graphs' + 249 ' since --graph-zip-url is not set.') 250 return 251 graph_zip = urllib.urlretrieve(graph_zip_url)[0] 252 if graph_zip is None or not os.path.exists(graph_zip): 253 raise SetupError('Unable to retrieve %s' % graph_zip_url) 254 if not os.path.exists(graph_dir): 255 os.mkdir(graph_dir) 256 UnzipFilenameToDir(graph_zip, graph_dir) 257 logging.info('Graph code is downloaded to %s', graph_dir) 258 259 260def GetCurrentOSType(): 261 """Get a string representation for the current OS. 262 263 Returns: 264 'Mac', 'Win', 'Linux', or 'Linux_64'. 265 266 Raises: 267 RuntimeError: if OS can't be identified. 268 """ 269 if sys.platform == 'darwin': 270 os_type = 'Mac' 271 if sys.platform == 'win32': 272 os_type = 'Win' 273 if sys.platform.startswith('linux'): 274 os_type = 'Linux' 275 if platform.architecture()[0] == '64bit': 276 os_type += '_x64' 277 else: 278 raise RuntimeError('Unknown platform') 279 return os_type 280 281 282def GetLatestRevision(os_type): 283 """Figure out the latest revision number of the prebuilt binary archive. 284 285 Args: 286 os_type: 'Mac', 'Win', 'Linux', or 'Linux_64'. 287 288 Returns: 289 A string of latest revision number. 290 291 Raises: 292 SetupError: If unable to get the latest revision number. 293 """ 294 last_change_url = ('http://commondatastorage.googleapis.com/' 295 'chromium-browser-continuous/%s/LAST_CHANGE' % os_type) 296 response = urllib2.urlopen(last_change_url) 297 last_change = response.read() 298 if not last_change: 299 raise SetupError('Unable to get the latest revision number from %s' % 300 last_change_url) 301 return last_change 302 303 304def UnzipFilenameToDir(filename, directory): 305 """Unzip |filename| to directory |directory|. 306 307 This works with as low as python2.4 (used on win). 308 (Code is adapted from fetch_prebuilt_pyauto.py) 309 """ 310 # TODO(fdeng): remove this function as soon as the Chrome Endure 311 # graphing code is checked into the chrome tree. 312 zf = zipfile.ZipFile(filename) 313 pushd = os.getcwd() 314 if not os.path.isdir(directory): 315 os.mkdir(directory) 316 os.chdir(directory) 317 # Extract files. 318 for info in zf.infolist(): 319 name = info.filename 320 if name.endswith('/'): # dir 321 if not os.path.isdir(name): 322 os.makedirs(name) 323 else: # file 324 directory = os.path.dirname(name) 325 if directory and not os.path.isdir(directory): 326 os.makedirs(directory) 327 out = open(name, 'wb') 328 out.write(zf.read(name)) 329 out.close() 330 # Set permissions. Permission info in external_attr is shifted 16 bits. 331 os.chmod(name, info.external_attr >> 16L) 332 os.chdir(pushd) 333 334 335if '__main__' == __name__: 336 logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.DEBUG) 337 sys.exit(Main(sys.argv[1:])) 338