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 8 Usage: 9 python endure_setup.py [option] 10 11 We use <ENDURE_DIR> to refer to the root directory in which Chrome Endure 12 is set up. By default, <ENDURE_DIR> is the current working directory. 13 14 First, run: 15 >python endure_setup.py 16 This command will automatically setup Chrome Endure in <ENDURE_DIR>. 17 18 Next, 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 \\ 22 The above commands runs a Chrome Endure test for 30 seconds and saves 23 the results to <ENDURE_DIR>/chrome_graph. 24 25 Last, to view the graphs, run another script endure_server.py 26 within <ENDURE_DIR> to start a local HTTP server that serves 27 the graph directory, see endure_server.py for details. 28 29 Use python endure_setup.py --help for more options. 30 31 This 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 36 Supported platforms: Linux and Linux_x64. 37 """ 38 39 import logging 40 import optparse 41 import os 42 import platform 43 import shutil 44 import subprocess 45 import sys 46 import urllib 47 import urllib2 48 import zipfile 49 50 URLS = {'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 59 class SetupError(Exception): 60 """Catch errors in setting up Chrome Endure.""" 61 pass 62 63 64 class 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 72 def 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 127 def 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 142 def 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 164 def 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 188 def 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 209 def 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 234 def 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 260 def 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 282 def 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 304 def 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 335 if '__main__' == __name__: 336 logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.DEBUG) 337 sys.exit(Main(sys.argv[1:])) 338