Index: chrome/test/functional/perf/endure_setup.py |
diff --git a/chrome/test/functional/perf/endure_setup.py b/chrome/test/functional/perf/endure_setup.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1a87f1f6760d320e0fabf2e94c40a2fa32b7a39b |
--- /dev/null |
+++ b/chrome/test/functional/perf/endure_setup.py |
@@ -0,0 +1,414 @@ |
+"""Automate the setup process of Chrome Endure environment. |
+ |
+Usage: |
+ ./endure.py [fetch|server] [option] |
+ |
+Command "fetch" will automatically setup Chrome Endure environment. |
+The script will do the following things: |
+ 1) Set up depot_tool if necessary. |
+ 2) Check out python code which Chrome Endure depends on. |
+ 3) Check out pre-built binary of Chrome, PyAuto lib and chrome driver |
+ 4) Check out chrome graphing file which are needed to show the test results. |
+ |
+After fetching Chrome Endure, you should be able to run the endure test. Try: |
+ TEST_LENGTH=60 LOCAL_GRAPH='./chrome_graph' |
+ python ./src/chrome/test/functional/perf_endure.py |
+ perf_endure.ChromeEndureGmailTest.testGmailComposeDiscard |
+The above commands runs an Chrome Endure test for 60 seconds and saves |
+the results to ./chrome_graph. LOCAL_GRAPH is the directory where you have |
+chrome graphing files. By default, it is <DIR_OF_THIS_SCIRPT>/chrome_endure. |
+ |
+Command "server" will start a local HTTP server to serve the directory which |
+contains the result data files. Run this command after you have test results |
+output to <DIR_OF_THIS_SCRIPT>/chrome_endure and a server will be started with |
+a port that is automatically picked. You can then view the result graphs |
+via http://localhost:<GIVEN_PORT>. |
+ |
+Use ./endure [fetch|server] --help for more options. |
+""" |
+ |
+import BaseHTTPServer |
+import optparse |
+import os |
+import platform |
+import shutil |
+import SimpleHTTPServer |
+import subprocess |
+import sys |
+import urllib |
+import urllib2 |
+import zipfile |
+ |
+ |
+class SetupError(Exception): |
+ """Catch errors in setting up Chrome Endure.""" |
+ |
+ def __init__(self, label, message): |
+ super(SetupError, self).__init__() |
+ self.label = label |
+ self.message = message |
+ |
+ def __str__(self): |
+ return self.label+'\n'+str(self.message) |
+ |
+ |
+class PlainHelpFormatter(optparse.IndentedHelpFormatter): |
+ """Format the help message of this script.""" |
+ |
+ def format_description(self, description): |
+ if description: |
+ return description + '\n' |
+ else: |
+ return '' |
+ |
+ |
+class CmdFetch(object): |
+ """Fetch Chrome Endure. |
+ |
+ Usage: |
+ ./endure.py fetch [options] |
+ Examples: |
+ ./endure.py fetch |
+ Fetch latest version of Chrome Endure to the same directory as this script. |
+ |
+ ./endure.py fetch --endure-dir=/home/user/endure_dir --revision=18043 |
+ Fetch revision 18043 to /home/user/endure_dir |
+ """ |
+ _URLS = {'depot_tools': ('http://src.chromium.org' |
+ '/chrome/trunk/tools/depot_tools'), |
+ 'pyauto': ('https://src.chromium.org/' |
+ 'chrome/trunk/src/chrome/test/functional.DEPS'), |
+ 'binary': ('http://commondatastorage.googleapis.com/' |
+ 'chromium-browser-continuous/{os_type}/{revision}'), |
+ # TODO(fdeng): change to an external address after graph code |
+ # is checked in |
+ 'graph': ('http://www.corp.google.com/' |
+ '~dennisjeffrey/chrome_perf/local_graphs.zip'), |
+ } |
+ NAME = 'fetch' |
+ DESCRIPTION = 'Fetch Chrome Endure.' |
+ |
+ def _ParseArgs(self, argv): |
+ parser = optparse.OptionParser( |
+ usage='%%prog %s [options]' % self.NAME, |
+ formatter=PlainHelpFormatter(), |
+ description=self.__doc__) |
+ parser.add_option( |
+ '-d', '--endure-dir', type='string', default=os.path.dirname(__file__), |
+ help='Directory in which to setup or update.') |
+ parser.add_option( |
+ '-r', '--revision', type='string', default=None, |
+ help='Revision of Chrome tree to get Chrome Endure.') |
+ return parser.parse_args(argv) |
+ |
+ def Run(self, argv): |
+ """Run this command.""" |
+ options, _ = self._ParseArgs(argv) |
+ self._endure_dir = os.path.abspath(options.endure_dir) |
+ self._revision = options.revision |
+ if not self._revision: |
+ self._revision = self._GetLatestRevision(self._GetCurrentOSType()) |
+ self._os_type = self._GetCurrentOSType() |
+ self._depot_dir = os.path.join(self._endure_dir, 'depot_tools') |
+ self._gclient = os.path.join(self._depot_dir, 'gclient') |
+ self._fetch_py = os.path.join(self._endure_dir, 'src', 'chrome', |
+ 'test', 'pyautolib', |
+ 'fetch_prebuilt_pyauto.py') |
+ self._binary_dir = os.path.join(self._endure_dir, 'src', 'out', 'Release') |
+ self._graph_dir = os.path.join(self._endure_dir, 'chrome_graph') |
+ |
+ if not os.path.isdir(self._endure_dir): |
+ os.makedirs(self._endure_dir) |
+ |
+ print 'Checking depot tools...' |
+ self._FetchDepot() |
+ print 'Fetching PyAuto (python code)...' |
+ self._FetchPyAuto(self._revision) |
+ print 'Fetching binaries(chrome, pyautolib, chrome driver)...' |
+ self._FetchBinaries(self._os_type, self._revision) |
+ # TODO(fdeng): remove this after it is check into the chrome tree. |
+ print 'Fetching chrome graphing files...' |
+ self._FetchGraph() |
+ return 0 |
+ |
+ def _FetchDepot(self): |
+ """Fetch depot_tool if not installed in the system.""" |
+ try: |
+ subprocess.call(['gclient', '--version']) |
+ self._gclient = 'gclient' |
+ except OSError: |
+ try: |
+ # TODO(fdeng): how can I know gclient is not installed |
+ # without checking OSError, a better way? |
+ url = self._URLS['depot_tools'] |
+ print 'Fetching depot tools: %s' % url |
+ subprocess.call(['svn', 'co', self._URLS['depot_tools'], |
+ self._depot_dir]) |
+ subprocess.call([self._gclient, '--version']) |
+ except Exception as e: |
+ raise SetupError('Unable to set up depot tools:', str(e)) |
+ |
+ def _FetchPyAuto(self, revision): |
+ """Use gclient to fetch python code.""" |
+ cur_dir = os.getcwd() |
+ try: |
+ os.chdir(self._endure_dir) |
+ # gclient config |
+ config_cmd = [self._gclient, 'config', self._URLS['pyauto']] |
+ code = subprocess.call(config_cmd) |
+ if code != 0: |
+ raise Exception('Running "%s" failed.' % ' '.join(config_cmd)) |
+ # gclient sync |
+ sync_cmd = [self._gclient, 'sync'] |
+ if revision: |
+ 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
|
+ code = subprocess.call(sync_cmd) |
+ if code != 0: |
+ raise Exception('Running "%s" failed.' % ' '.join(sync_cmd)) |
+ except Exception as e: |
+ raise SetupError('Unable to fetch PyAuto code', str(e)) |
+ finally: |
+ os.chdir(cur_dir) |
+ |
+ def _FetchBinaries(self, os_type, revision): |
+ """Get the prebuilt binaries from continuous build archive.""" |
+ try: |
+ if not os.path.exists(self._fetch_py): |
+ raise Exception( |
+ 'Unable to find %s, did fetching python code succeed?' |
+ % self._fetch_py) |
+ print 'Cleaning %s' % self._binary_dir |
+ if os.path.exists(self._binary_dir): |
+ shutil.rmtree(self._binary_dir) |
+ print 'Downloading...' |
+ cmd = [self._fetch_py, '-d', self._binary_dir, |
+ self._URLS['binary'].format(os_type=os_type, revision=revision)] |
+ code = subprocess.call(cmd) |
+ if code != 0: |
+ raise Exception('Running "%s" failed.' % ' '.join(cmd)) |
+ except Exception as e: |
+ raise SetupError( |
+ 'Unable to fetch binaries for chrome/pyauto/chrome_driver:', str(e)) |
+ |
+ def _FetchGraph(self): |
+ """Fetch graph code.""" |
+ try: |
+ graph_zip = urllib.urlretrieve(self._URLS['graph'])[0] |
+ if os.path.exists(self._graph_dir): |
+ print 'Cleaning %s, ' % self._graph_dir, |
+ print 'data files(.dat) will be preserved.' |
+ files = os.listdir(self._graph_dir) |
+ for f in files: |
+ path = os.path.join(self._graph_dir, f) |
+ if os.path.isdir(path): |
+ shutil.rmtree(path) |
+ elif os.path.splitext(f) != 'dat': |
+ os.remove(path) |
+ else: |
+ os.mkdir(self._graph_dir) |
+ self._UnzipFilenameToDir(graph_zip, self._graph_dir) |
+ except Exception as e: |
+ raise SetupError('Unable to fetch graph files:', str(e)) |
+ |
+ def _GetCurrentOSType(self): |
+ """Get a string representation for the current os. |
+ |
+ Returns: |
+ 'Mac', 'Win', 'Linux', or 'Linux_64' |
+ |
+ Raises: |
+ RuntimeError: if os can't be identified. |
+ """ |
+ if sys.platform == 'darwin': |
+ os_type = 'Mac' |
+ if sys.platform == 'win32': |
+ os_type = 'Win' |
+ if sys.platform.startswith('linux'): |
+ os_type = 'Linux' |
+ if platform.architecture()[0] == '64bit': |
+ os_type += '_x64' |
+ else: |
+ raise RuntimeError('Unknown platform') |
+ return os_type |
+ |
+ def _GetLatestRevision(self, os_type): |
+ """Figure out the latest revision number of the prebuilt binary archive. |
+ |
+ Args: |
+ os_type: 'Mac', 'Win', 'Linux', or 'Linux_64' |
+ |
+ Returns: |
+ A string of latest revision number or None on fail. |
+ """ |
+ last_change_url = ('http://commondatastorage.googleapis.com/' |
+ 'chromium-browser-continuous/%s/LAST_CHANGE' % os_type) |
+ response = urllib2.urlopen(last_change_url) |
+ last_change = response.read() |
+ if not last_change: |
+ print 'Unable to get latest revision number from %s' % last_change_url |
+ return None |
+ return last_change |
+ |
+ @classmethod |
+ def _UnzipFilenameToDir(cls, filename, directory): |
+ """Unzip |filename| to directory |directory|. |
+ |
+ This works with as low as python2.4 (used on win). |
+ """ |
+ zf = zipfile.ZipFile(filename) |
+ pushd = os.getcwd() |
+ if not os.path.isdir(directory): |
+ os.mkdir(directory) |
+ os.chdir(directory) |
+ # Extract files. |
+ for info in zf.infolist(): |
+ name = info.filename |
+ print name |
+ if name.endswith('/'): # dir |
+ if not os.path.isdir(name): |
+ os.makedirs(name) |
+ else: # file |
+ directory = os.path.dirname(name) |
+ if directory and not os.path.isdir(directory): |
+ os.makedirs(directory) |
+ out = open(name, 'wb') |
+ out.write(zf.read(name)) |
+ out.close() |
+ # Set permissions. Permission info in external_attr is shifted 16 bits. |
+ os.chmod(name, info.external_attr >> 16L) |
+ os.chdir(pushd) |
+ |
+ |
+class CmdServer(object): |
+ """Start an http server which serves the Chrome Endure test results. |
+ |
+ Usage: |
+ ./endure server [options] |
+ Examples: |
+ ./endure.py server |
+ By default, it will serve the directory <DIR_OF_THIS_SCIRPT>/chrome_graph |
+ where DIR_OF_THIS_SCIRPT is the directory of this script (endure.py). |
+ |
+ ./endure.py server --graph-dir=/home/user/Document/graph_dir |
+ Use --graph-dir if you have your graph directory at a different place. |
+ """ |
+ NAME = 'server' |
+ DESCRIPTION = 'Start an http server for viewing the' \ |
+ 'tests results from a browser.' |
+ |
+ def _ParseArgs(self, argv): |
+ parser = optparse.OptionParser( |
+ usage='%%prog %s [options]' % self.NAME, |
+ formatter=PlainHelpFormatter(), |
+ description=self.__doc__) |
+ parser.add_option( |
+ '-g', '--graph-dir', type='string', |
+ default=os.path.join(os.path.dirname(__file__), 'chrome_graph'), |
+ help='The directory that contains graphing files' \ |
+ 'and data files of test results') |
+ return parser.parse_args(argv) |
+ |
+ def Run(self, argv): |
+ """Run this command.""" |
+ options, _ = self._ParseArgs(argv) |
+ self._graph_dir = os.path.abspath(options.graph_dir) |
+ cur_dir = os.getcwd() |
+ os.chdir(self._graph_dir) |
+ httpd = BaseHTTPServer.HTTPServer(('', 0), |
+ EndureHTTPRequestHandler) |
+ try: |
+ print 'Serving %s at port %d' % (self._graph_dir, httpd.server_port) |
+ print 'View test results at http://localhost:%d' % httpd.server_port |
+ print 'Press Ctrl-C to stop the server.' |
+ httpd.serve_forever() |
+ except KeyboardInterrupt: |
+ print 'Shutting down ...' |
+ httpd.shutdown() |
+ finally: |
+ os.chdir(cur_dir) |
+ return 0 |
+ |
+ |
+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
|
+ """A simple HTTP request handler for showing Chrome Endure test results.""" |
+ |
+ 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
|
+ """Override send_head so it won't redirect if path is not ended with "/". |
+ |
+ Common code for GET and HEAD commands. This sends the response |
+ code and MIME headers. |
+ |
+ Returns: |
+ either a file object (which has to be copied |
+ to the outputfile by the caller unless the command was HEAD, |
+ and must be closed by the caller under all circumstances), or |
+ None, in which case the caller has nothing further to do. |
+ """ |
+ path = self.translate_path(self.path) |
+ f = None |
+ if os.path.isdir(path): |
+ for index in 'index.html', 'index.htm': |
+ index = os.path.join(path, index) |
+ if os.path.exists(index): |
+ path = index |
+ break |
+ else: |
+ return self.list_directory(path) |
+ ctype = self.guess_type(path) |
+ try: |
+ # Always read in binary mode. Opening files in text mode may cause |
+ # newline translations, making the actual size of the content |
+ # transmitted *less* than the content-length! |
+ f = open(path, 'rb') |
+ except IOError: |
+ self.send_error(404, 'File not found') |
+ return None |
+ self.send_response(200) |
+ self.send_header('Content-type', ctype) |
+ fs = os.fstat(f.fileno()) |
+ self.send_header('Content-Length', str(fs[6])) |
+ self.send_header('Last-Modified', self.date_time_string(fs.st_mtime)) |
+ self.end_headers() |
+ return f |
+ |
+ |
+class CmdHelp(object): |
+ """Print a list of commands or help for a specific command.""" |
+ NAME = 'help' |
+ DESCRIPTION = __doc__ |
+ |
+ def __init__(self, cmds): |
+ """Initialize help command. |
+ |
+ Args: |
+ cmds: Commands for which help information will be printed. |
+ """ |
+ self.cmds = cmds |
+ |
+ def Run(self, args): |
+ """Run this command.""" |
+ if len(args) == 1: |
+ return Main(args + ['--help']) |
+ print 'Usage:\n ./endure.py [options] command' |
+ print 'Commands are:' |
+ for cmd in self.cmds: |
+ print '\t%s\t\t%s' % (cmd.NAME, cmd.DESCRIPTION) |
+ |
+ print 'Examples:' |
+ print ' ./endure.py %s' % CmdFetch.NAME |
+ return 0 |
+ |
+ |
+def Main(argv): |
+ if argv and argv[0] == CmdFetch.NAME: |
+ command = CmdFetch() |
+ elif argv and argv[0] == CmdServer.NAME: |
+ command = CmdServer() |
+ else: |
+ cmds = [CmdFetch, CmdServer, CmdHelp] |
+ command = CmdHelp(cmds) |
+ return command.Run(argv[1:]) |
+ |
+ |
+if '__main__' == __name__: |
+ sys.exit(Main(sys.argv[1:])) |