OLD | NEW |
| (Empty) |
1 #!/usr/bin/python2.4 | |
2 # | |
3 # Copyright 2009 The Chromium Authors. All rights reserved. | |
4 # Use of this source code is governed by a BSD-style license that can be | |
5 # found in the LICENSE file. | |
6 | |
7 | |
8 """A tool to run a chrome sync integration test, used by the buildbot slaves. | |
9 | |
10 When this is run, the current directory (cwd) should be the outer build | |
11 directory (e.g., chrome-release/build/). | |
12 | |
13 For a list of command-line options, call this script with '--help'. | |
14 """ | |
15 | |
16 __author__ = 'tejasshah@chromium.org' | |
17 | |
18 | |
19 import logging | |
20 import optparse | |
21 import os | |
22 import re | |
23 import subprocess | |
24 import sys | |
25 import tempfile | |
26 import time | |
27 import urllib2 | |
28 | |
29 | |
30 USAGE = '%s [options] [test args]' % os.path.basename(sys.argv[0]) | |
31 HTTP_SERVER_URL = None | |
32 HTTP_SERVER_PORT = None | |
33 | |
34 | |
35 class PathNotFound(Exception): pass | |
36 | |
37 | |
38 def ListSyncTests(test_exe_path): | |
39 """Returns list of the enabled live sync tests. | |
40 | |
41 Args: | |
42 test_exe_path: Absolute path to test exe file. | |
43 | |
44 Returns: | |
45 List of the tests that should be run. | |
46 """ | |
47 command = [test_exe_path] | |
48 command.append('--gtest_list_tests') | |
49 proc = subprocess.Popen( | |
50 command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1) | |
51 proc.wait() | |
52 sys.stdout.flush() | |
53 test_list = [] | |
54 for line in proc.stdout.readlines(): | |
55 if not line.strip(): | |
56 continue | |
57 elif line.count("."): # A new test collection | |
58 test_collection = line.split(".")[0].strip(); | |
59 else: # An individual test to run | |
60 test_name = line.strip() | |
61 test_list.append("%s.%s" % (test_collection, test_name)) | |
62 logging.info('List of tests to run: %s' % (test_list)) | |
63 return test_list | |
64 | |
65 | |
66 def GetUrl(command, port): | |
67 """Prepares the URL with appropriate command to send it to http server. | |
68 | |
69 Args: | |
70 command: Command for HTTP server | |
71 port: Port number as a parameter to the command | |
72 | |
73 Returns: | |
74 Formulated URL with the command and parameter. | |
75 """ | |
76 command_url = ( | |
77 '%s:%s/%s?port=%s' | |
78 % (HTTP_SERVER_URL, HTTP_SERVER_PORT, command, port)) | |
79 return command_url | |
80 | |
81 | |
82 def StartSyncServer(port=None): | |
83 """Starts a chrome sync server. | |
84 | |
85 Args: | |
86 port: Port number on which sync server to start | |
87 (Default value none, in this case it uses random port). | |
88 | |
89 Returns: | |
90 If success then port number on which sync server started, else None. | |
91 """ | |
92 sync_port = None | |
93 if port: | |
94 start_sync_server_url = GetUrl('startsyncserver', port) | |
95 else: | |
96 start_sync_server_url = ( | |
97 '%s:%s/%s' % (HTTP_SERVER_URL, HTTP_SERVER_PORT, 'startsyncserver')) | |
98 req = urllib2.Request(start_sync_server_url) | |
99 try: | |
100 response = urllib2.urlopen(req) | |
101 except urllib2.HTTPError, e: | |
102 logging.error( | |
103 'Could not start sync server, something went wrong.' | |
104 'Request URL: %s , Error Code: %s'% (start_sync_server_url, e.code)) | |
105 return sync_port | |
106 except urllib2.URLError, e: | |
107 logging.error( | |
108 'Failed to reach HTTP server.Request URL: %s , Error: %s' | |
109 % (start_sync_server_url, e.reason)) | |
110 return sync_port | |
111 else: | |
112 # Let's read response and parse the sync server port number. | |
113 output = response.readlines() | |
114 # Regex to ensure that sync server started and extract the port number. | |
115 regex = re.compile( | |
116 ".*not.*running.*on.*port\s*:\s*(\d+).*started.*new.*sync.*server", | |
117 re.IGNORECASE|re.MULTILINE|re.DOTALL) | |
118 r = regex.search(output[0]) | |
119 if r: | |
120 sync_port = r.groups()[0] | |
121 if sync_port: | |
122 logging.info( | |
123 'Started Sync Server Successfully on Port:%s. Request URL: %s , ' | |
124 'Response: %s' % (sync_port, start_sync_server_url, output)) | |
125 response.fp._sock.recv = None | |
126 response.close() | |
127 return sync_port | |
128 | |
129 | |
130 def CheckIfSyncServerRunning(port): | |
131 """Check the healthz status of a chrome sync server. | |
132 | |
133 Args: | |
134 port: Port number on which sync server is running | |
135 | |
136 Returns: | |
137 True: If sync server running. | |
138 False: Otherwise. | |
139 """ | |
140 sync_server_healthz_url = ('%s:%s/healthz' % (HTTP_SERVER_URL, port)) | |
141 req = urllib2.Request(sync_server_healthz_url) | |
142 try: | |
143 response = urllib2.urlopen(req) | |
144 except urllib2.HTTPError, e: | |
145 logging.error( | |
146 'It seems like Sync Server is not running, healthz check failed.' | |
147 'Request URL: %s , Error Code: %s'% (sync_server_healthz_url, e.code)) | |
148 return False | |
149 except urllib2.URLError, e: | |
150 logging.error( | |
151 'Failed to reach Sync server, healthz check failed.' | |
152 'Request URL: %s , Error: %s'% (sync_server_healthz_url, e.reason)) | |
153 return False | |
154 else: | |
155 logging.info( | |
156 'Sync Server healthz check Passed.Request URL: %s , Response: %s' | |
157 % (sync_server_healthz_url, response.readlines())) | |
158 response.fp._sock.recv = None | |
159 response.close() | |
160 return True | |
161 | |
162 | |
163 def StopSyncServer(port): | |
164 """Stops a chrome sync server. | |
165 | |
166 Args: | |
167 port: Port number on which sync server to Stop | |
168 | |
169 Returns: | |
170 Success/Failure as a bool value. | |
171 """ | |
172 stop_sync_server_url = GetUrl('stopsyncserver', port) | |
173 req = urllib2.Request(stop_sync_server_url) | |
174 logging.info("Stopping: %s" % stop_sync_server_url) | |
175 try: | |
176 response = urllib2.urlopen(req) | |
177 except urllib2.HTTPError, e: | |
178 logging.error( | |
179 'Could not stop sync server, something went wrong.' | |
180 'Request URL: %s , Error Code: %s'% (stop_sync_server_url, e.code)) | |
181 return False | |
182 except urllib2.URLError, e: | |
183 logging.error( | |
184 'Failed to reach HTTP server.Request URL: %s , Error: %s' | |
185 % (stop_sync_server_url, e.reason)) | |
186 return False | |
187 else: | |
188 logging.info( | |
189 'Stopped Sync Server Successfully.Request URL: %s , Response: %s' | |
190 % (stop_sync_server_url, response.readlines())) | |
191 response.fp._sock.recv = None | |
192 response.close() | |
193 return True | |
194 | |
195 | |
196 def ShowGtestLikeFailure(test_exe_path, test_name): | |
197 """Show a gtest-like error when the test can't be run for some reason. | |
198 | |
199 The scripts responsible for detecting test failures watch for this pattern. | |
200 | |
201 Args: | |
202 test_exe_path: Absolute path to the test exe file. | |
203 test_name: The name of the test that wasn't run. | |
204 """ | |
205 print '[ RUN ] %s.%s' % (os.path.basename(test_exe_path), test_name) | |
206 print '[ FAILED ] %s.%s' % (os.path.basename(test_exe_path), test_name) | |
207 | |
208 | |
209 def RunCommand(command): | |
210 """Runs the command list, printing its output and returning its exit status. | |
211 | |
212 Prints the given command (which should be a list of one or more strings), | |
213 then runs it and prints its stdout and stderr together to stdout, | |
214 line-buffered, converting line endings to CRLF (see note below). Waits for | |
215 the command to terminate and returns its status. | |
216 | |
217 Args: | |
218 command: Command to run. | |
219 | |
220 Returns: | |
221 Process exit code. | |
222 """ | |
223 print '\n' + subprocess.list2cmdline(command) + '\n', | |
224 proc = subprocess.Popen(command, stdout=subprocess.PIPE, | |
225 stderr=subprocess.STDOUT, bufsize=1) | |
226 last_flush_time = time.time() | |
227 while proc.poll() == None: | |
228 # Note that Windows Python converts \n to \r\n automatically whenever it | |
229 # encounters it written to a text file (including stdout). The only way | |
230 # around it is to write to a binary file, which isn't feasible for stdout. | |
231 # So we're stuck with \r\n here even though we explicitly write \n. (We | |
232 # could write \r instead, which doesn't get converted to \r\n, but that's | |
233 # probably more troublesome for people trying to read the files.) | |
234 line = proc.stdout.readline() | |
235 if line: | |
236 # The comma at the end tells python to not add \n, which is \r\n on | |
237 # Windows. | |
238 print line.rstrip() + '\n', | |
239 # Python on windows writes the buffer only when it reaches 4k. This is | |
240 # not fast enough. Flush each 10 seconds instead. | |
241 if time.time() - last_flush_time >= 10: | |
242 sys.stdout.flush() | |
243 last_flush_time = time.time() | |
244 sys.stdout.flush() | |
245 # Write the remaining buffer. | |
246 for line in proc.stdout.readlines(): | |
247 print line.rstrip() + '\n', | |
248 sys.stdout.flush() | |
249 return proc.returncode | |
250 | |
251 | |
252 def RunOneTest(test_exe_path, test_name, username, password): | |
253 """Run one live sync test after setting up a fresh server environment. | |
254 | |
255 Args: | |
256 test_exe_path: Absolute path to test exe file. | |
257 test_name: the name of the one test to run. | |
258 username: test account username. | |
259 password: test account password. | |
260 | |
261 Returns: | |
262 Zero for suceess. | |
263 Non-zero for failure. | |
264 """ | |
265 def LogTestNotRun(message): | |
266 ShowGtestLikeFailure(test_exe_path, test_name) | |
267 logging.info('\n\n%s did not run because %s' % (test_name, message)) | |
268 | |
269 try: | |
270 logging.info('\n\n*************************************') | |
271 logging.info('%s Start' % (test_name)) | |
272 sync_port = StartSyncServer() | |
273 if not sync_port: | |
274 LogTestNotRun('starting the sync server failed.') | |
275 return 1 | |
276 if not CheckIfSyncServerRunning(sync_port): | |
277 LogTestNotRun('sync server running check failed.') | |
278 return 1 | |
279 logging.info('Verified that sync server is running on port: %s', sync_port) | |
280 user_dir = GenericSetup(test_name) | |
281 logging.info('Created user data dir %s' % (user_dir)) | |
282 command = [ | |
283 test_exe_path, | |
284 '--gtest_filter='+ test_name, | |
285 '--user-data-dir=' + user_dir, | |
286 '--sync-user-for-test=' + username, | |
287 '--sync-password-for-test=' + password, | |
288 '--sync-url=' + HTTP_SERVER_URL + ':' + sync_port] | |
289 logging.info( | |
290 '%s will run with command: %s' | |
291 % (test_name, subprocess.list2cmdline(command))) | |
292 result = RunCommand(command) | |
293 StopSyncServer(sync_port) | |
294 return result | |
295 finally: | |
296 logging.info('%s End' % (test_name)) | |
297 | |
298 | |
299 def GenericSetup(test_name): | |
300 """Generic setup for running one test. | |
301 | |
302 Args: | |
303 test_name: The name of a test that is about to be run. | |
304 | |
305 Returns: | |
306 user_dir: Absolute path to user data dir created for the test. | |
307 """ | |
308 user_dir = tempfile.mkdtemp(prefix=test_name + '.') | |
309 return user_dir | |
310 | |
311 | |
312 def main_win(options, args): | |
313 """Main Function for win32 platform which drives the test here. | |
314 | |
315 Using the target build configuration, run the executable given in the | |
316 first non-option argument, passing any following arguments to that | |
317 executable. | |
318 | |
319 Args: | |
320 options: Option parameters. | |
321 args: Test arguments. | |
322 | |
323 Returns: | |
324 Result: Zero for success/ Non-zero for failure. | |
325 """ | |
326 final_result = 0 | |
327 test_exe = 'sync_integration_tests.exe' | |
328 build_dir = os.path.abspath(options.build_dir) | |
329 test_exe_path = os.path.join(build_dir, options.target, test_exe) | |
330 if not os.path.exists(test_exe_path): | |
331 raise PathNotFound('Unable to find %s' % test_exe_path) | |
332 test_list = ListSyncTests(test_exe_path) | |
333 for test_name in test_list: | |
334 result = RunOneTest( | |
335 test_exe_path, test_name, options.sync_test_username, | |
336 options.sync_test_password) | |
337 # If any single test fails then final result should be failure | |
338 if result != 0: | |
339 final_result = result | |
340 return final_result | |
341 | |
342 if '__main__' == __name__: | |
343 # Initialize logging. | |
344 log_level = logging.INFO | |
345 logging.basicConfig( | |
346 level=log_level, format='%(asctime)s %(filename)s:%(lineno)-3d' | |
347 ' %(levelname)s %(message)s', datefmt='%y%m%d %H:%M:%S') | |
348 | |
349 option_parser = optparse.OptionParser(usage=USAGE) | |
350 | |
351 # Since the trailing program to run may have has command-line args of its | |
352 # own, we need to stop parsing when we reach the first positional argument. | |
353 option_parser.disable_interspersed_args() | |
354 | |
355 option_parser.add_option( | |
356 '', '--target', default='Release', help='Build target (Debug or Release)' | |
357 ' Default value Release.') | |
358 option_parser.add_option( | |
359 '', '--build-dir', help='Path to main build directory' | |
360 '(the parent of the Release or Debug directory).') | |
361 option_parser.add_option( | |
362 '', '--http-server-url', help='Path to http server that can be used to' | |
363 ' start/stop sync server e.g. http://http_server_url_without_port') | |
364 option_parser.add_option( | |
365 '', '--http-server-port', help='Port on which http server is running' | |
366 ' e.g. 1010') | |
367 option_parser.add_option( | |
368 '', '--sync-test-username', help='Test username e.g. foo@gmail.com') | |
369 option_parser.add_option( | |
370 '', '--sync-test-password', help='Password for the test account') | |
371 options, args = option_parser.parse_args() | |
372 if not options.sync_test_password: | |
373 # Check along side this script under src/ first, and then check in | |
374 # the user profile dir. | |
375 password_file_path = os.path.join( | |
376 os.path.dirname(__file__),'sync_password') | |
377 if (not os.path.exists(password_file_path)): | |
378 password_file_path = os.path.join( | |
379 os.environ['USERPROFILE'], 'sync_password') | |
380 | |
381 if os.path.exists(password_file_path): | |
382 fs = open(password_file_path, 'r') | |
383 lines = fs.readlines() | |
384 if len(lines)==1: | |
385 options.sync_test_password = lines[0].strip() | |
386 else: | |
387 sys.stderr.write('sync_password file is not in required format.\n') | |
388 sys.exit(1) | |
389 else: | |
390 sys.stderr.write( | |
391 'Missing required parameter- sync_test_password, please fix it.\n') | |
392 sys.exit(1) | |
393 if (not options.build_dir or not options.http_server_url or | |
394 not options.http_server_port or not options.sync_test_username): | |
395 sys.stderr.write('Missing required parameter, please fix it.\n') | |
396 option_parser.print_help() | |
397 sys.exit(1) | |
398 if sys.platform == 'win32': | |
399 HTTP_SERVER_URL = options.http_server_url | |
400 HTTP_SERVER_PORT = options.http_server_port | |
401 sys.exit(main_win(options, args)) | |
402 else: | |
403 sys.stderr.write('Unknown sys.platform value %s\n' % repr(sys.platform)) | |
404 sys.exit(1) | |
OLD | NEW |