Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 """Automate the setup process of Chrome Endure environment. | |
| 2 | |
| 3 Usage: | |
| 4 ./endure.py [fetch|server] [option] | |
| 5 | |
| 6 Command "fetch" will automatically setup Chrome Endure environment. | |
| 7 The script will do the following things: | |
| 8 1) Set up depot_tool if necessary. | |
| 9 2) Check out python code which Chrome Endure depends on. | |
| 10 3) Check out pre-built binary of Chrome, PyAuto lib and chrome driver | |
| 11 4) Check out chrome graphing file which are needed to show the test results. | |
| 12 | |
| 13 After fetching Chrome Endure, you should be able to run the endure test. Try: | |
| 14 TEST_LENGTH=60 LOCAL_GRAPH='./chrome_graph' | |
| 15 python ./src/chrome/test/functional/perf_endure.py | |
| 16 perf_endure.ChromeEndureGmailTest.testGmailComposeDiscard | |
| 17 The above commands runs an Chrome Endure test for 60 seconds and saves | |
| 18 the results to ./chrome_graph. LOCAL_GRAPH is the directory where you have | |
| 19 chrome graphing files. By default, it is <DIR_OF_THIS_SCIRPT>/chrome_endure. | |
| 20 | |
| 21 Command "server" will start a local HTTP server to serve the directory which | |
| 22 contains the result data files. Run this command after you have test results | |
| 23 output to <DIR_OF_THIS_SCRIPT>/chrome_endure and a server will be started with | |
| 24 a port that is automatically picked. You can then view the result graphs | |
| 25 via http://localhost:<GIVEN_PORT>. | |
| 26 | |
| 27 Use ./endure [fetch|server] --help for more options. | |
| 28 """ | |
| 29 | |
| 30 import BaseHTTPServer | |
| 31 import optparse | |
| 32 import os | |
| 33 import platform | |
| 34 import shutil | |
| 35 import SimpleHTTPServer | |
| 36 import subprocess | |
| 37 import sys | |
| 38 import urllib | |
| 39 import urllib2 | |
| 40 import zipfile | |
| 41 | |
| 42 | |
| 43 class SetupError(Exception): | |
| 44 """Catch errors in setting up Chrome Endure.""" | |
| 45 | |
| 46 def __init__(self, label, message): | |
| 47 super(SetupError, self).__init__() | |
| 48 self.label = label | |
| 49 self.message = message | |
| 50 | |
| 51 def __str__(self): | |
| 52 return self.label+'\n'+str(self.message) | |
| 53 | |
| 54 | |
| 55 class PlainHelpFormatter(optparse.IndentedHelpFormatter): | |
| 56 """Format the help message of this script.""" | |
| 57 | |
| 58 def format_description(self, description): | |
| 59 if description: | |
| 60 return description + '\n' | |
| 61 else: | |
| 62 return '' | |
| 63 | |
| 64 | |
| 65 class CmdFetch(object): | |
| 66 """Fetch Chrome Endure. | |
| 67 | |
| 68 Usage: | |
| 69 ./endure.py fetch [options] | |
| 70 Examples: | |
| 71 ./endure.py fetch | |
| 72 Fetch latest version of Chrome Endure to the same directory as this script. | |
| 73 | |
| 74 ./endure.py fetch --endure-dir=/home/user/endure_dir --revision=18043 | |
| 75 Fetch revision 18043 to /home/user/endure_dir | |
| 76 """ | |
| 77 _URLS = {'depot_tools': ('http://src.chromium.org' | |
| 78 '/chrome/trunk/tools/depot_tools'), | |
| 79 'pyauto': ('https://src.chromium.org/' | |
| 80 'chrome/trunk/src/chrome/test/functional.DEPS'), | |
| 81 'binary': ('http://commondatastorage.googleapis.com/' | |
| 82 'chromium-browser-continuous/{os_type}/{revision}'), | |
| 83 # TODO(fdeng): change to an external address after graph code | |
| 84 # is checked in | |
| 85 'graph': ('http://www.corp.google.com/' | |
| 86 '~dennisjeffrey/chrome_perf/local_graphs.zip'), | |
| 87 } | |
| 88 NAME = 'fetch' | |
| 89 DESCRIPTION = 'Fetch Chrome Endure.' | |
| 90 | |
| 91 def _ParseArgs(self, argv): | |
| 92 parser = optparse.OptionParser( | |
| 93 usage='%%prog %s [options]' % self.NAME, | |
| 94 formatter=PlainHelpFormatter(), | |
| 95 description=self.__doc__) | |
| 96 parser.add_option( | |
| 97 '-d', '--endure-dir', type='string', default=os.path.dirname(__file__), | |
| 98 help='Directory in which to setup or update.') | |
| 99 parser.add_option( | |
| 100 '-r', '--revision', type='string', default=None, | |
| 101 help='Revision of Chrome tree to get Chrome Endure.') | |
| 102 return parser.parse_args(argv) | |
| 103 | |
| 104 def Run(self, argv): | |
| 105 """Run this command.""" | |
| 106 options, _ = self._ParseArgs(argv) | |
| 107 self._endure_dir = os.path.abspath(options.endure_dir) | |
| 108 self._revision = options.revision | |
| 109 if not self._revision: | |
| 110 self._revision = self._GetLatestRevision(self._GetCurrentOSType()) | |
| 111 self._os_type = self._GetCurrentOSType() | |
| 112 self._depot_dir = os.path.join(self._endure_dir, 'depot_tools') | |
| 113 self._gclient = os.path.join(self._depot_dir, 'gclient') | |
| 114 self._fetch_py = os.path.join(self._endure_dir, 'src', 'chrome', | |
| 115 'test', 'pyautolib', | |
| 116 'fetch_prebuilt_pyauto.py') | |
| 117 self._binary_dir = os.path.join(self._endure_dir, 'src', 'out', 'Release') | |
| 118 self._graph_dir = os.path.join(self._endure_dir, 'chrome_graph') | |
| 119 | |
| 120 if not os.path.isdir(self._endure_dir): | |
| 121 os.makedirs(self._endure_dir) | |
| 122 | |
| 123 print 'Checking depot tools...' | |
| 124 self._FetchDepot() | |
| 125 print 'Fetching PyAuto (python code)...' | |
| 126 self._FetchPyAuto(self._revision) | |
| 127 print 'Fetching binaries(chrome, pyautolib, chrome driver)...' | |
| 128 self._FetchBinaries(self._os_type, self._revision) | |
| 129 # TODO(fdeng): remove this after it is check into the chrome tree. | |
| 130 print 'Fetching chrome graphing files...' | |
| 131 self._FetchGraph() | |
| 132 return 0 | |
| 133 | |
| 134 def _FetchDepot(self): | |
| 135 """Fetch depot_tool if not installed in the system.""" | |
| 136 try: | |
| 137 subprocess.call(['gclient', '--version']) | |
| 138 self._gclient = 'gclient' | |
| 139 except OSError: | |
| 140 try: | |
| 141 # TODO(fdeng): how can I know gclient is not installed | |
| 142 # without checking OSError, a better way? | |
| 143 url = self._URLS['depot_tools'] | |
| 144 print 'Fetching depot tools: %s' % url | |
| 145 subprocess.call(['svn', 'co', self._URLS['depot_tools'], | |
| 146 self._depot_dir]) | |
| 147 subprocess.call([self._gclient, '--version']) | |
| 148 except Exception as e: | |
| 149 raise SetupError('Unable to set up depot tools:', str(e)) | |
| 150 | |
| 151 def _FetchPyAuto(self, revision): | |
| 152 """Use gclient to fetch python code.""" | |
| 153 cur_dir = os.getcwd() | |
| 154 try: | |
| 155 os.chdir(self._endure_dir) | |
| 156 # gclient config | |
| 157 config_cmd = [self._gclient, 'config', self._URLS['pyauto']] | |
| 158 code = subprocess.call(config_cmd) | |
| 159 if code != 0: | |
| 160 raise Exception('Running "%s" failed.' % ' '.join(config_cmd)) | |
| 161 # gclient sync | |
| 162 sync_cmd = [self._gclient, 'sync'] | |
| 163 if revision: | |
| 164 sync_cmd.extend(['--revision', 'functional.DEPS@'+(revision)]) | |
|
fdeng1
2012/08/07 22:19:45
Here the script tries to run "gclient sync --revis
dennis_jeffrey
2012/08/08 23:41:07
As discussed offline, I think it'll be fine for no
| |
| 165 code = subprocess.call(sync_cmd) | |
| 166 if code != 0: | |
| 167 raise Exception('Running "%s" failed.' % ' '.join(sync_cmd)) | |
| 168 except Exception as e: | |
| 169 raise SetupError('Unable to fetch PyAuto code', str(e)) | |
| 170 finally: | |
| 171 os.chdir(cur_dir) | |
| 172 | |
| 173 def _FetchBinaries(self, os_type, revision): | |
| 174 """Get the prebuilt binaries from continuous build archive.""" | |
| 175 try: | |
| 176 if not os.path.exists(self._fetch_py): | |
| 177 raise Exception( | |
| 178 'Unable to find %s, did fetching python code succeed?' | |
| 179 % self._fetch_py) | |
| 180 print 'Cleaning %s' % self._binary_dir | |
| 181 if os.path.exists(self._binary_dir): | |
| 182 shutil.rmtree(self._binary_dir) | |
| 183 print 'Downloading...' | |
| 184 cmd = [self._fetch_py, '-d', self._binary_dir, | |
| 185 self._URLS['binary'].format(os_type=os_type, revision=revision)] | |
| 186 code = subprocess.call(cmd) | |
| 187 if code != 0: | |
| 188 raise Exception('Running "%s" failed.' % ' '.join(cmd)) | |
| 189 except Exception as e: | |
| 190 raise SetupError( | |
| 191 'Unable to fetch binaries for chrome/pyauto/chrome_driver:', str(e)) | |
| 192 | |
| 193 def _FetchGraph(self): | |
| 194 """Fetch graph code.""" | |
| 195 try: | |
| 196 graph_zip = urllib.urlretrieve(self._URLS['graph'])[0] | |
| 197 if os.path.exists(self._graph_dir): | |
| 198 print 'Cleaning %s, ' % self._graph_dir, | |
| 199 print 'data files(.dat) will be preserved.' | |
| 200 files = os.listdir(self._graph_dir) | |
| 201 for f in files: | |
| 202 path = os.path.join(self._graph_dir, f) | |
| 203 if os.path.isdir(path): | |
| 204 shutil.rmtree(path) | |
| 205 elif os.path.splitext(f) != 'dat': | |
| 206 os.remove(path) | |
| 207 else: | |
| 208 os.mkdir(self._graph_dir) | |
| 209 self._UnzipFilenameToDir(graph_zip, self._graph_dir) | |
| 210 except Exception as e: | |
| 211 raise SetupError('Unable to fetch graph files:', str(e)) | |
| 212 | |
| 213 def _GetCurrentOSType(self): | |
| 214 """Get a string representation for the current os. | |
| 215 | |
| 216 Returns: | |
| 217 'Mac', 'Win', 'Linux', or 'Linux_64' | |
| 218 | |
| 219 Raises: | |
| 220 RuntimeError: if os can't be identified. | |
| 221 """ | |
| 222 if sys.platform == 'darwin': | |
| 223 os_type = 'Mac' | |
| 224 if sys.platform == 'win32': | |
| 225 os_type = 'Win' | |
| 226 if sys.platform.startswith('linux'): | |
| 227 os_type = 'Linux' | |
| 228 if platform.architecture()[0] == '64bit': | |
| 229 os_type += '_x64' | |
| 230 else: | |
| 231 raise RuntimeError('Unknown platform') | |
| 232 return os_type | |
| 233 | |
| 234 def _GetLatestRevision(self, os_type): | |
| 235 """Figure out the latest revision number of the prebuilt binary archive. | |
| 236 | |
| 237 Args: | |
| 238 os_type: 'Mac', 'Win', 'Linux', or 'Linux_64' | |
| 239 | |
| 240 Returns: | |
| 241 A string of latest revision number or None on fail. | |
| 242 """ | |
| 243 last_change_url = ('http://commondatastorage.googleapis.com/' | |
| 244 'chromium-browser-continuous/%s/LAST_CHANGE' % os_type) | |
| 245 response = urllib2.urlopen(last_change_url) | |
| 246 last_change = response.read() | |
| 247 if not last_change: | |
| 248 print 'Unable to get latest revision number from %s' % last_change_url | |
| 249 return None | |
| 250 return last_change | |
| 251 | |
| 252 @classmethod | |
| 253 def _UnzipFilenameToDir(cls, filename, directory): | |
| 254 """Unzip |filename| to directory |directory|. | |
| 255 | |
| 256 This works with as low as python2.4 (used on win). | |
| 257 """ | |
| 258 zf = zipfile.ZipFile(filename) | |
| 259 pushd = os.getcwd() | |
| 260 if not os.path.isdir(directory): | |
| 261 os.mkdir(directory) | |
| 262 os.chdir(directory) | |
| 263 # Extract files. | |
| 264 for info in zf.infolist(): | |
| 265 name = info.filename | |
| 266 print name | |
| 267 if name.endswith('/'): # dir | |
| 268 if not os.path.isdir(name): | |
| 269 os.makedirs(name) | |
| 270 else: # file | |
| 271 directory = os.path.dirname(name) | |
| 272 if directory and not os.path.isdir(directory): | |
| 273 os.makedirs(directory) | |
| 274 out = open(name, 'wb') | |
| 275 out.write(zf.read(name)) | |
| 276 out.close() | |
| 277 # Set permissions. Permission info in external_attr is shifted 16 bits. | |
| 278 os.chmod(name, info.external_attr >> 16L) | |
| 279 os.chdir(pushd) | |
| 280 | |
| 281 | |
| 282 class CmdServer(object): | |
| 283 """Start an http server which serves the Chrome Endure test results. | |
| 284 | |
| 285 Usage: | |
| 286 ./endure server [options] | |
| 287 Examples: | |
| 288 ./endure.py server | |
| 289 By default, it will serve the directory <DIR_OF_THIS_SCIRPT>/chrome_graph | |
| 290 where DIR_OF_THIS_SCIRPT is the directory of this script (endure.py). | |
| 291 | |
| 292 ./endure.py server --graph-dir=/home/user/Document/graph_dir | |
| 293 Use --graph-dir if you have your graph directory at a different place. | |
| 294 """ | |
| 295 NAME = 'server' | |
| 296 DESCRIPTION = 'Start an http server for viewing the' \ | |
| 297 'tests results from a browser.' | |
| 298 | |
| 299 def _ParseArgs(self, argv): | |
| 300 parser = optparse.OptionParser( | |
| 301 usage='%%prog %s [options]' % self.NAME, | |
| 302 formatter=PlainHelpFormatter(), | |
| 303 description=self.__doc__) | |
| 304 parser.add_option( | |
| 305 '-g', '--graph-dir', type='string', | |
| 306 default=os.path.join(os.path.dirname(__file__), 'chrome_graph'), | |
| 307 help='The directory that contains graphing files' \ | |
| 308 'and data files of test results') | |
| 309 return parser.parse_args(argv) | |
| 310 | |
| 311 def Run(self, argv): | |
| 312 """Run this command.""" | |
| 313 options, _ = self._ParseArgs(argv) | |
| 314 self._graph_dir = os.path.abspath(options.graph_dir) | |
| 315 cur_dir = os.getcwd() | |
| 316 os.chdir(self._graph_dir) | |
| 317 httpd = BaseHTTPServer.HTTPServer(('', 0), | |
| 318 EndureHTTPRequestHandler) | |
| 319 try: | |
| 320 print 'Serving %s at port %d' % (self._graph_dir, httpd.server_port) | |
| 321 print 'View test results at http://localhost:%d' % httpd.server_port | |
| 322 print 'Press Ctrl-C to stop the server.' | |
| 323 httpd.serve_forever() | |
| 324 except KeyboardInterrupt: | |
| 325 print 'Shutting down ...' | |
| 326 httpd.shutdown() | |
| 327 finally: | |
| 328 os.chdir(cur_dir) | |
| 329 return 0 | |
| 330 | |
| 331 | |
| 332 class EndureHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): | |
|
fdeng1
2012/08/07 22:09:43
I tried to reuse the TestServer in pyautolib.py. I
dennis_jeffrey
2012/08/08 23:41:07
I think it's fine to have our own basic test serve
| |
| 333 """A simple HTTP request handler for showing Chrome Endure test results.""" | |
| 334 | |
| 335 def send_head(self): | |
|
fdeng1
2012/08/07 22:09:43
I wish I didn't have to override this function. Bu
dennis_jeffrey
2012/08/08 23:41:07
As discussed offline, let's no longer override thi
fdeng1
2012/08/09 03:26:16
I am able to solve the problem by modifying the se
dennis_jeffrey
2012/08/09 17:16:27
Great! I made the change on the live chrome endur
| |
| 336 """Override send_head so it won't redirect if path is not ended with "/". | |
| 337 | |
| 338 Common code for GET and HEAD commands. This sends the response | |
| 339 code and MIME headers. | |
| 340 | |
| 341 Returns: | |
| 342 either a file object (which has to be copied | |
| 343 to the outputfile by the caller unless the command was HEAD, | |
| 344 and must be closed by the caller under all circumstances), or | |
| 345 None, in which case the caller has nothing further to do. | |
| 346 """ | |
| 347 path = self.translate_path(self.path) | |
| 348 f = None | |
| 349 if os.path.isdir(path): | |
| 350 for index in 'index.html', 'index.htm': | |
| 351 index = os.path.join(path, index) | |
| 352 if os.path.exists(index): | |
| 353 path = index | |
| 354 break | |
| 355 else: | |
| 356 return self.list_directory(path) | |
| 357 ctype = self.guess_type(path) | |
| 358 try: | |
| 359 # Always read in binary mode. Opening files in text mode may cause | |
| 360 # newline translations, making the actual size of the content | |
| 361 # transmitted *less* than the content-length! | |
| 362 f = open(path, 'rb') | |
| 363 except IOError: | |
| 364 self.send_error(404, 'File not found') | |
| 365 return None | |
| 366 self.send_response(200) | |
| 367 self.send_header('Content-type', ctype) | |
| 368 fs = os.fstat(f.fileno()) | |
| 369 self.send_header('Content-Length', str(fs[6])) | |
| 370 self.send_header('Last-Modified', self.date_time_string(fs.st_mtime)) | |
| 371 self.end_headers() | |
| 372 return f | |
| 373 | |
| 374 | |
| 375 class CmdHelp(object): | |
| 376 """Print a list of commands or help for a specific command.""" | |
| 377 NAME = 'help' | |
| 378 DESCRIPTION = __doc__ | |
| 379 | |
| 380 def __init__(self, cmds): | |
| 381 """Initialize help command. | |
| 382 | |
| 383 Args: | |
| 384 cmds: Commands for which help information will be printed. | |
| 385 """ | |
| 386 self.cmds = cmds | |
| 387 | |
| 388 def Run(self, args): | |
| 389 """Run this command.""" | |
| 390 if len(args) == 1: | |
| 391 return Main(args + ['--help']) | |
| 392 print 'Usage:\n ./endure.py [options] command' | |
| 393 print 'Commands are:' | |
| 394 for cmd in self.cmds: | |
| 395 print '\t%s\t\t%s' % (cmd.NAME, cmd.DESCRIPTION) | |
| 396 | |
| 397 print 'Examples:' | |
| 398 print ' ./endure.py %s' % CmdFetch.NAME | |
| 399 return 0 | |
| 400 | |
| 401 | |
| 402 def Main(argv): | |
| 403 if argv and argv[0] == CmdFetch.NAME: | |
| 404 command = CmdFetch() | |
| 405 elif argv and argv[0] == CmdServer.NAME: | |
| 406 command = CmdServer() | |
| 407 else: | |
| 408 cmds = [CmdFetch, CmdServer, CmdHelp] | |
| 409 command = CmdHelp(cmds) | |
| 410 return command.Run(argv[1:]) | |
| 411 | |
| 412 | |
| 413 if '__main__' == __name__: | |
| 414 sys.exit(Main(sys.argv[1:])) | |
| OLD | NEW |