OLD | NEW |
---|---|
(Empty) | |
1 """Automate the setup process of Chrome Endure environment. | |
Nirnimesh
2012/08/09 17:28:05
license header?
fdeng1
2012/08/14 19:11:40
Done.
| |
2 | |
3 Usage: | |
4 python endure_setup.py [fetch|server] [option] | |
5 | |
6 We use <ENDURE_DIR> to refer to the root directory in which Chrome Endure | |
7 is set up. By default, <ENDURE_DIR> is the current working directory. | |
8 | |
9 First, run "python endure_setup.py fetch". This command will automatically setup | |
10 Chrome Endure in <ENDURE_DIR>. | |
11 | |
12 Next, try to run your first endure test by: | |
13 TEST_LENGTH=60 LOCAL_GRAPH='<ENDURE_DIR>/chrome_graph' \\ | |
14 python <ENDURE_DIR>/src/chrome/test/functional/perf_endure_setup.py \\ | |
15 perf_endure.ChromeEndureGmailTest.testGmailComposeDiscard \\ | |
16 The above commands runs a Chrome Endure test for 60 seconds and saves | |
17 the results to <ENDURE_DIR>/chrome_graph. | |
18 | |
19 Last, run "python endure_setup.py server". This command will start a local | |
20 HTTP server to serve <ENDURE_DIR>/chrome_graph. A port will be automatically | |
21 picked. You can now view the result graphs via http://localhost:<GIVEN_PORT>. | |
22 | |
23 Use python endure_setup.py [fetch|server] --help for more options. | |
24 """ | |
dennis_jeffrey
2012/08/08 23:41:07
in this docstring, also list the dependencies that
fdeng1
2012/08/14 19:11:40
Add some text about dependencies on depot_tools an
| |
25 | |
26 import BaseHTTPServer | |
27 import optparse | |
28 import os | |
29 import shutil | |
30 import SimpleHTTPServer | |
31 import subprocess | |
32 import sys | |
33 import urllib | |
34 import zipfile | |
35 | |
36 | |
37 class SetupError(Exception): | |
38 """Catch errors in setting up Chrome Endure.""" | |
39 pass | |
40 | |
41 | |
42 class PlainHelpFormatter(optparse.IndentedHelpFormatter): | |
43 """Format the help message of this script.""" | |
44 | |
45 def format_description(self, description): | |
46 if description: | |
47 return description + '\n' | |
48 else: | |
49 return '' | |
50 | |
51 | |
52 class CmdFetch(object): | |
53 """Fetch Chrome Endure. | |
54 | |
55 Usage: | |
56 python endure_setup.py fetch [options] | |
Nirnimesh
2012/08/09 17:28:05
leave a blank line
fdeng1
2012/08/14 19:11:40
Done.
| |
57 Examples: | |
58 python endure_setup.py fetch | |
59 Fetch the latest version of Chrome Endure to the current | |
60 working directory. | |
61 | |
62 python endure_setup.py fetch --endure-dir=/home/user/endure_dir | |
63 Fetch the latest version of Chrome Endure to /home/user/endure_dir. | |
64 """ | |
65 _URLS = {'depot_tools': ('http://src.chromium.org' | |
66 '/chrome/trunk/tools/depot_tools'), | |
67 'pyauto': ('https://src.chromium.org/' | |
68 'chrome/trunk/src/chrome/test/functional.DEPS'), | |
69 # TODO(fdeng): change to an external address after graph code | |
70 # is checked in | |
71 'graph': ('http://www.corp.google.com/' | |
72 '~dennisjeffrey/chrome_perf/local_graphs.zip'), | |
73 } | |
74 NAME = 'fetch' | |
75 DESCRIPTION = 'Fetch Chrome Endure.' | |
76 | |
77 def _ParseArgs(self, argv): | |
78 parser = optparse.OptionParser( | |
79 usage='%%prog %s [options]' % self.NAME, | |
80 formatter=PlainHelpFormatter(), | |
81 description=self.__doc__) | |
82 parser.add_option( | |
83 '-d', '--endure-dir', type='string', default=os.getcwd(), | |
84 help='Directory in which to setup or update. ' \ | |
85 'Default value is the current working directory') | |
86 return parser.parse_args(argv) | |
87 | |
88 def Run(self, argv): | |
89 """Run this command.""" | |
90 options, _ = self._ParseArgs(argv) | |
91 self._endure_dir = os.path.abspath(options.endure_dir) | |
92 self._depot_dir = os.path.join(self._endure_dir, 'depot_tools') | |
93 self._gclient = os.path.join(self._depot_dir, 'gclient') | |
94 self._fetch_py = os.path.join(self._endure_dir, 'src', 'chrome', | |
95 'test', 'pyautolib', | |
96 'fetch_prebuilt_pyauto.py') | |
97 self._binary_dir = os.path.join(self._endure_dir, 'src', 'out', 'Release') | |
98 self._graph_dir = os.path.join(self._endure_dir, 'chrome_graph') | |
99 | |
100 if not os.path.isdir(self._endure_dir): | |
101 os.makedirs(self._endure_dir) | |
102 | |
103 print 'Checking depot tools...' | |
dennis_jeffrey
2012/08/08 23:41:07
i recommend using the "logging" module instead of
fdeng1
2012/08/14 19:11:40
Switch from "print" to "logging".
On 2012/08/08 23
| |
104 self._FetchDepot() | |
105 print 'Fetching PyAuto (python code)...' | |
106 self._FetchPyAuto() | |
107 print 'Fetching binaries(chrome, pyautolib, chrome driver)...' | |
108 self._FetchBinaries() | |
109 # TODO(fdeng): remove this after it is check into the chrome tree. | |
110 print 'Fetching chrome graphing files...' | |
111 self._FetchGraph() | |
112 return 0 | |
113 | |
114 def _FetchDepot(self): | |
115 """Fetch depot_tool if not installed in the system.""" | |
116 try: | |
117 subprocess.call(['gclient', '--version']) | |
118 self._gclient = 'gclient' | |
119 except OSError: | |
120 # TODO(fdeng): how can I know gclient is not installed | |
121 # without checking OSError, a better way? | |
dennis_jeffrey
2012/08/08 23:41:07
it's possible depot_tools may already be installed
fdeng1
2012/08/14 19:11:40
Done.
| |
122 url = self._URLS['depot_tools'] | |
123 print 'Fetching depot tools: %s' % url | |
124 subprocess.call(['svn', 'co', self._URLS['depot_tools'], | |
125 self._depot_dir]) | |
126 subprocess.call([self._gclient, '--version']) | |
dennis_jeffrey
2012/08/08 23:41:07
is there any way to verify that we were successful
fdeng1
2012/08/14 19:11:40
Added code to check that gclient/gclient.bat(win)
| |
127 | |
128 def _FetchPyAuto(self): | |
129 """Use gclient to fetch python code.""" | |
130 cur_dir = os.getcwd() | |
131 os.chdir(self._endure_dir) | |
132 # gclient config | |
133 config_cmd = [self._gclient, 'config', self._URLS['pyauto']] | |
dennis_jeffrey
2012/08/08 23:41:07
Did Nirnimesh say it's not sufficient to use pyaut
fdeng1
2012/08/14 19:11:40
I think we were talking about webpagereplay was no
dennis_jeffrey
2012/08/15 17:53:08
Ok. I thought there was some problem where the fu
| |
134 code = subprocess.call(config_cmd) | |
135 if code != 0: | |
136 raise SetupError('Running "%s" failed.' % ' '.join(config_cmd)) | |
137 # gclient sync | |
138 sync_cmd = [self._gclient, 'sync'] | |
139 code = subprocess.call(sync_cmd) | |
140 if code != 0: | |
141 raise SetupError('Running "%s" failed.' % ' '.join(sync_cmd)) | |
142 os.chdir(cur_dir) | |
dennis_jeffrey
2012/08/08 23:41:07
should we also check to see whether an expected di
fdeng1
2012/08/14 19:11:40
Add _CheckPyAuto()
Check src/chrome/test/pyatuolib
dennis_jeffrey
2012/08/15 17:53:08
I think this is good. I just thought a simple san
| |
143 | |
144 def _FetchBinaries(self): | |
145 """Get the prebuilt binaries from continuous build archive.""" | |
146 if not os.path.exists(self._fetch_py): | |
147 raise SetupError( | |
148 'Unable to find %s, did fetching python code succeed?' | |
149 % self._fetch_py) | |
150 print 'Cleaning %s' % self._binary_dir | |
151 if os.path.exists(self._binary_dir): | |
152 shutil.rmtree(self._binary_dir) | |
153 print 'Downloading...' | |
154 cmd = [self._fetch_py, '-d', self._binary_dir, '--latest'] | |
155 code = subprocess.call(cmd) | |
dennis_jeffrey
2012/08/08 23:41:07
if this is successful, maybe we could log a messag
fdeng1
2012/08/14 19:11:40
Done.
| |
156 if code != 0: | |
157 raise SetupError('Running "%s" failed.' % ' '.join(cmd)) | |
158 | |
159 def _FetchGraph(self): | |
160 """Fetch graph code.""" | |
161 graph_zip = urllib.urlretrieve(self._URLS['graph'])[0] | |
162 if graph_zip == None or not os.path.exists(graph_zip): | |
163 raise SetupError('Unable to retrieve %s' % self._URLS['graph']) | |
164 if os.path.exists(self._graph_dir): | |
165 print 'Cleaning %s, ' % self._graph_dir, | |
dennis_jeffrey
2012/08/08 23:41:07
Do we need to do all this work to clean the old di
fdeng1
2012/08/14 19:11:40
Done.
| |
166 print 'data files(.dat) will be preserved.' | |
167 files = os.listdir(self._graph_dir) | |
168 for f in files: | |
169 path = os.path.join(self._graph_dir, f) | |
170 if os.path.isdir(path): | |
171 shutil.rmtree(path) | |
172 elif os.path.splitext(f) != 'dat': | |
173 os.remove(path) | |
174 else: | |
175 os.mkdir(self._graph_dir) | |
176 self._UnzipFilenameToDir(graph_zip, self._graph_dir) | |
177 | |
178 @classmethod | |
179 def _UnzipFilenameToDir(cls, filename, directory): | |
180 """Unzip |filename| to directory |directory|. | |
181 | |
182 This works with as low as python2.4 (used on win). | |
183 """ | |
184 zf = zipfile.ZipFile(filename) | |
185 pushd = os.getcwd() | |
186 if not os.path.isdir(directory): | |
187 os.mkdir(directory) | |
188 os.chdir(directory) | |
189 # Extract files. | |
190 for info in zf.infolist(): | |
191 name = info.filename | |
192 print name | |
193 if name.endswith('/'): # dir | |
194 if not os.path.isdir(name): | |
195 os.makedirs(name) | |
196 else: # file | |
197 directory = os.path.dirname(name) | |
198 if directory and not os.path.isdir(directory): | |
199 os.makedirs(directory) | |
200 out = open(name, 'wb') | |
201 out.write(zf.read(name)) | |
202 out.close() | |
203 # Set permissions. Permission info in external_attr is shifted 16 bits. | |
204 os.chmod(name, info.external_attr >> 16L) | |
dennis_jeffrey
2012/08/08 23:41:07
did you get this code from somewhere? should we c
fdeng1
2012/08/14 19:11:40
Mentioned it in docstring that it is adapted from
| |
205 os.chdir(pushd) | |
206 | |
207 | |
208 class CmdServer(object): | |
209 """Start an http server which serves the Chrome Endure test results. | |
dennis_jeffrey
2012/08/08 23:41:07
'test results' --> 'graphs'.
fdeng1
2012/08/14 19:11:40
Done.
| |
210 | |
211 Usage: | |
212 python endure_setup.py server [options] | |
213 Examples: | |
214 python endure_setup.py server | |
215 Start a server which serves the default location | |
216 <CURRENT_WORKING_DIR>/chrome_graph. | |
217 | |
218 python endure_setup.py server --graph-dir=/home/user/Document/graph_dir | |
219 Start a server which serves /home/user/Document/graph_dir which | |
220 is where your graph code and test results are. | |
221 """ | |
222 NAME = 'server' | |
223 DESCRIPTION = 'Start an http server for viewing the' \ | |
224 'tests results from a browser.' | |
225 | |
226 def _ParseArgs(self, argv): | |
227 parser = optparse.OptionParser( | |
228 usage='%%prog %s [options]' % self.NAME, | |
229 formatter=PlainHelpFormatter(), | |
230 description=self.__doc__) | |
231 parser.add_option( | |
232 '-g', '--graph-dir', type='string', | |
233 default=os.path.join(os.getcwd(), 'chrome_graph'), | |
234 help='The directory that contains graph code ' \ | |
235 'and data files of test results. Default value is ' \ | |
236 '<CURRENT_WORKING_DIR>/chrome_graph') | |
237 return parser.parse_args(argv) | |
238 | |
239 def Run(self, argv): | |
240 """Run this command.""" | |
241 options, _ = self._ParseArgs(argv) | |
242 self._graph_dir = os.path.abspath(options.graph_dir) | |
243 cur_dir = os.getcwd() | |
244 os.chdir(self._graph_dir) | |
245 httpd = BaseHTTPServer.HTTPServer(('', 0), | |
246 EndureHTTPRequestHandler) | |
247 try: | |
248 print 'Serving %s at port %d' % (self._graph_dir, httpd.server_port) | |
249 print 'View test results at http://localhost:%d' % httpd.server_port | |
250 print 'Press Ctrl-C to stop the server.' | |
251 httpd.serve_forever() | |
252 except KeyboardInterrupt: | |
253 print 'Shutting down ...' | |
254 httpd.shutdown() | |
255 finally: | |
256 os.chdir(cur_dir) | |
257 return 0 | |
258 | |
259 | |
260 class EndureHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): | |
261 """A simple HTTP request handler for showing Chrome Endure test results.""" | |
262 | |
263 def send_head(self): | |
264 """Override send_head so it won't redirect if path is not ended with "/". | |
265 | |
266 Common code for GET and HEAD commands. This sends the response | |
267 code and MIME headers. | |
268 | |
269 Returns: | |
270 either a file object (which has to be copied | |
271 to the output file by the caller unless the command was HEAD, | |
272 and must be closed by the caller under all circumstances), or | |
273 None, in which case the caller has nothing further to do. | |
274 """ | |
275 path = self.translate_path(self.path) | |
276 f = None | |
277 if os.path.isdir(path): | |
278 for index in 'index.html', 'index.htm': | |
279 index = os.path.join(path, index) | |
280 if os.path.exists(index): | |
281 path = index | |
282 break | |
283 else: | |
284 return self.list_directory(path) | |
285 ctype = self.guess_type(path) | |
286 try: | |
287 # Always read in binary mode. Opening files in text mode may cause | |
288 # newline translations, making the actual size of the content | |
289 # transmitted *less* than the content-length! | |
290 f = open(path, 'rb') | |
291 except IOError: | |
292 self.send_error(404, 'File not found') | |
293 return None | |
294 self.send_response(200) | |
295 self.send_header('Content-type', ctype) | |
296 fs = os.fstat(f.fileno()) | |
297 self.send_header('Content-Length', str(fs[6])) | |
298 self.send_header('Last-Modified', self.date_time_string(fs.st_mtime)) | |
299 self.end_headers() | |
300 return f | |
301 | |
302 | |
303 class CmdHelp(object): | |
304 """Print a list of commands or help for a specific command.""" | |
305 NAME = 'help' | |
306 DESCRIPTION = __doc__ | |
307 | |
308 def __init__(self, cmds): | |
309 """Initialize help command. | |
310 | |
311 Args: | |
312 cmds: Commands for which help information will be printed. | |
313 """ | |
314 self.cmds = cmds | |
315 | |
316 def Run(self, args): | |
317 """Run this command.""" | |
318 if len(args) == 1: | |
319 return Main(args + ['--help']) | |
320 #print 'Usage:\n ./endure_setup.py [options] command' | |
321 print __doc__ | |
322 print 'Commands are:' | |
323 for cmd in self.cmds: | |
324 print '\t%s\t\t%s' % (cmd.NAME, cmd.DESCRIPTION) | |
325 | |
326 return 0 | |
327 | |
328 | |
329 def Main(argv): | |
dennis_jeffrey
2012/08/08 23:41:07
maybe we should split up this script into 2 separa
fdeng1
2012/08/09 03:26:16
Since we are able to solve the problem brought by
dennis_jeffrey
2012/08/09 17:16:27
I like the feature of having a free port automatic
fdeng1
2012/08/14 19:11:40
That sounds reasonable. I've moved it to a separat
| |
330 if argv and argv[0] == CmdFetch.NAME: | |
331 command = CmdFetch() | |
332 elif argv and argv[0] == CmdServer.NAME: | |
333 command = CmdServer() | |
334 else: | |
335 cmds = [CmdFetch, CmdServer, CmdHelp] | |
dennis_jeffrey
2012/08/08 23:41:07
I think it's easier to use optparse for parsing ar
fdeng1
2012/08/14 19:11:40
Done.
| |
336 command = CmdHelp(cmds) | |
337 return command.Run(argv[1:]) | |
338 | |
339 | |
340 if '__main__' == __name__: | |
341 sys.exit(Main(sys.argv[1:])) | |
OLD | NEW |