| 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 |