1 #!/usr/bin/python 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 import glob 7 import optparse 8 import os.path 9 import socket 10 import sys 11 import thread 12 import time 13 import urllib 14 15 # Allow the import of third party modules 16 script_dir = os.path.dirname(os.path.abspath(__file__)) 17 sys.path.append(os.path.join(script_dir, '../../../../third_party/')) 18 sys.path.append(os.path.join(script_dir, '../../../../tools/valgrind/')) 19 sys.path.append(os.path.join(script_dir, '../../../../testing/')) 20 21 import browsertester.browserlauncher 22 import browsertester.rpclistener 23 import browsertester.server 24 25 import memcheck_analyze 26 import tsan_analyze 27 28 import test_env 29 30 def BuildArgParser(): 31 usage = 'usage: %prog [options]' 32 parser = optparse.OptionParser(usage) 33 34 parser.add_option('-p', '--port', dest='port', action='store', type='int', 35 default='0', help='The TCP port the server will bind to. ' 36 'The default is to pick an unused port number.') 37 parser.add_option('--browser_path', dest='browser_path', action='store', 38 type='string', default=None, 39 help='Use the browser located here.') 40 parser.add_option('--map_file', dest='map_files', action='append', 41 type='string', nargs=2, default=[], 42 metavar='DEST SRC', 43 help='Add file SRC to be served from the HTTP server, ' 44 'to be made visible under the path DEST.') 45 parser.add_option('--serving_dir', dest='serving_dirs', action='append', 46 type='string', default=[], 47 metavar='DIRNAME', 48 help='Add directory DIRNAME to be served from the HTTP ' 49 'server to be made visible under the root.') 50 parser.add_option('--test_arg', dest='test_args', action='append', 51 type='string', nargs=2, default=[], 52 metavar='KEY VALUE', 53 help='Parameterize the test with a key/value pair.') 54 parser.add_option('--redirect_url', dest='map_redirects', action='append', 55 type='string', nargs=2, default=[], 56 metavar='DEST SRC', 57 help='Add a redirect to the HTTP server, ' 58 'requests for SRC will result in a redirect (302) to DEST.') 59 parser.add_option('-f', '--file', dest='files', action='append', 60 type='string', default=[], 61 metavar='FILENAME', 62 help='Add a file to serve from the HTTP server, to be ' 63 'made visible in the root directory. ' 64 '"--file path/to/foo.html" is equivalent to ' 65 '"--map_file foo.html path/to/foo.html"') 66 parser.add_option('--mime_type', dest='mime_types', action='append', 67 type='string', nargs=2, default=[], metavar='DEST SRC', 68 help='Map file extension SRC to MIME type DEST when ' 69 'serving it from the HTTP server.') 70 parser.add_option('-u', '--url', dest='url', action='store', 71 type='string', default=None, 72 help='The webpage to load.') 73 parser.add_option('--ppapi_plugin', dest='ppapi_plugin', action='store', 74 type='string', default=None, 75 help='Use the browser plugin located here.') 76 parser.add_option('--sel_ldr', dest='sel_ldr', action='store', 77 type='string', default=None, 78 help='Use the sel_ldr located here.') 79 parser.add_option('--sel_ldr_bootstrap', dest='sel_ldr_bootstrap', 80 action='store', type='string', default=None, 81 help='Use the bootstrap loader located here.') 82 parser.add_option('--irt_library', dest='irt_library', action='store', 83 type='string', default=None, 84 help='Use the integrated runtime (IRT) library ' 85 'located here.') 86 parser.add_option('--interactive', dest='interactive', action='store_true', 87 default=False, help='Do not quit after testing is done. ' 88 'Handy for iterative development. Disables timeout.') 89 parser.add_option('--debug', dest='debug', action='store_true', default=False, 90 help='Request debugging output from browser.') 91 parser.add_option('--timeout', dest='timeout', action='store', type='float', 92 default=5.0, 93 help='The maximum amount of time to wait, in seconds, for ' 94 'the browser to make a request. The timer resets with each ' 95 'request.') 96 parser.add_option('--hard_timeout', dest='hard_timeout', action='store', 97 type='float', default=None, 98 help='The maximum amount of time to wait, in seconds, for ' 99 'the entire test. This will kill runaway tests. ') 100 parser.add_option('--allow_404', dest='allow_404', action='store_true', 101 default=False, 102 help='Allow 404s to occur without failing the test.') 103 parser.add_option('-b', '--bandwidth', dest='bandwidth', action='store', 104 type='float', default='0.0', 105 help='The amount of bandwidth (megabits / second) to ' 106 'simulate between the client and the server. This used for ' 107 'replies with file payloads. All other responses are ' 108 'assumed to be short. Bandwidth values <= 0.0 are assumed ' 109 'to mean infinite bandwidth.') 110 parser.add_option('--extension', dest='browser_extensions', action='append', 111 type='string', default=[], 112 help='Load the browser extensions located at the list of ' 113 'paths. Note: this currently only works with the Chrome ' 114 'browser.') 115 parser.add_option('--tool', dest='tool', action='store', 116 type='string', default=None, 117 help='Run tests under a tool.') 118 parser.add_option('--browser_flag', dest='browser_flags', action='append', 119 type='string', default=[], 120 help='Additional flags for the chrome command.') 121 parser.add_option('--enable_ppapi_dev', dest='enable_ppapi_dev', 122 action='store', type='int', default=1, 123 help='Enable/disable PPAPI Dev interfaces while testing.') 124 parser.add_option('--nacl_exe_stdin', dest='nacl_exe_stdin', 125 type='string', default=None, 126 help='Redirect standard input of NaCl executable.') 127 parser.add_option('--nacl_exe_stdout', dest='nacl_exe_stdout', 128 type='string', default=None, 129 help='Redirect standard output of NaCl executable.') 130 parser.add_option('--nacl_exe_stderr', dest='nacl_exe_stderr', 131 type='string', default=None, 132 help='Redirect standard error of NaCl executable.') 133 parser.add_option('--expect_browser_process_crash', 134 dest='expect_browser_process_crash', 135 action='store_true', 136 help='Do not signal a failure if the browser process ' 137 'crashes') 138 parser.add_option('--enable_crash_reporter', dest='enable_crash_reporter', 139 action='store_true', default=False, 140 help='Force crash reporting on.') 141 142 return parser 143 144 145 def ProcessToolLogs(options, logs_dir): 146 if options.tool == 'memcheck': 147 analyzer = memcheck_analyze.MemcheckAnalyzer('', use_gdb=True) 148 logs_wildcard = 'xml.*' 149 elif options.tool == 'tsan': 150 analyzer = tsan_analyze.TsanAnalyzer('', use_gdb=True) 151 logs_wildcard = 'log.*' 152 files = glob.glob(os.path.join(logs_dir, logs_wildcard)) 153 retcode = analyzer.Report(files, options.url) 154 return retcode 155 156 157 # An exception that indicates possible flake. 158 class RetryTest(Exception): 159 pass 160 161 162 def DumpNetLog(netlog): 163 sys.stdout.write('\n') 164 if not os.path.isfile(netlog): 165 sys.stdout.write('Cannot find netlog, did Chrome actually launch?\n') 166 else: 167 sys.stdout.write('Netlog exists (%d bytes).\n' % os.path.getsize(netlog)) 168 sys.stdout.write('Dumping it to stdout.\n\n\n') 169 sys.stdout.write(open(netlog).read()) 170 sys.stdout.write('\n\n\n') 171 172 173 # Try to discover the real IP address of this machine. If we can't figure it 174 # out, fall back to localhost. 175 # A windows bug makes using the loopback interface flaky in rare cases. 176 # http://code.google.com/p/chromium/issues/detail?id=114369 177 def GetHostName(): 178 host = 'localhost' 179 try: 180 host = socket.gethostbyname(socket.gethostname()) 181 except Exception: 182 pass 183 if host == '0.0.0.0': 184 host = 'localhost' 185 return host 186 187 188 def RunTestsOnce(url, options): 189 # Set the default here so we're assured hard_timeout will be defined. 190 # Tests, such as run_inbrowser_trusted_crash_in_startup_test, may not use the 191 # RunFromCommand line entry point - and otherwise get stuck in an infinite 192 # loop when something goes wrong and the hard timeout is not set. 193 # http://code.google.com/p/chromium/issues/detail?id=105406 194 if options.hard_timeout is None: 195 options.hard_timeout = options.timeout * 4 196 197 options.files.append(os.path.join(script_dir, 'browserdata', 'nacltest.js')) 198 199 # Setup the environment with the setuid sandbox path. 200 test_env.enable_sandbox_if_required(os.environ) 201 202 # Create server 203 host = GetHostName() 204 try: 205 server = browsertester.server.Create(host, options.port) 206 except Exception: 207 sys.stdout.write('Could not bind %r, falling back to localhost.\n' % host) 208 server = browsertester.server.Create('localhost', options.port) 209 210 # If port 0 has been requested, an arbitrary port will be bound so we need to 211 # query it. Older version of Python do not set server_address correctly when 212 # The requested port is 0 so we need to break encapsulation and query the 213 # socket directly. 214 host, port = server.socket.getsockname() 215 216 file_mapping = dict(options.map_files) 217 for filename in options.files: 218 file_mapping[os.path.basename(filename)] = filename 219 for server_path, real_path in file_mapping.iteritems(): 220 if not os.path.exists(real_path): 221 raise AssertionError('\'%s\' does not exist.' % real_path) 222 mime_types = {} 223 for ext, mime_type in options.mime_types: 224 mime_types['.' + ext] = mime_type 225 226 def ShutdownCallback(): 227 server.TestingEnded() 228 close_browser = options.tool is not None and not options.interactive 229 return close_browser 230 231 listener = browsertester.rpclistener.RPCListener(ShutdownCallback) 232 server.Configure(file_mapping, 233 dict(options.map_redirects), 234 mime_types, 235 options.allow_404, 236 options.bandwidth, 237 listener, 238 options.serving_dirs) 239 240 browser = browsertester.browserlauncher.ChromeLauncher(options) 241 242 full_url = 'http://%s:%d/%s' % (host, port, url) 243 if len(options.test_args) > 0: 244 full_url += '?' + urllib.urlencode(options.test_args) 245 browser.Run(full_url, port) 246 server.TestingBegun(0.125) 247 248 # In Python 2.5, server.handle_request may block indefinitely. Serving pages 249 # is done in its own thread so the main thread can time out as needed. 250 def Serve(): 251 while server.test_in_progress or options.interactive: 252 server.handle_request() 253 thread.start_new_thread(Serve, ()) 254 255 tool_failed = False 256 time_started = time.time() 257 258 def HardTimeout(total_time): 259 return total_time >= 0.0 and time.time() - time_started >= total_time 260 261 try: 262 while server.test_in_progress or options.interactive: 263 if not browser.IsRunning(): 264 if options.expect_browser_process_crash: 265 break 266 listener.ServerError('Browser process ended during test ' 267 '(return code %r)' % browser.GetReturnCode()) 268 # If Chrome exits prematurely without making a single request to the 269 # web server, this is probally a Chrome crash-on-launch bug not related 270 # to the test at hand. Retry, unless we're in interactive mode. In 271 # interactive mode the user may manually close the browser, so don't 272 # retry (it would just be annoying.) 273 if not server.received_request and not options.interactive: 274 raise RetryTest('Chrome failed to launch.') 275 else: 276 break 277 elif not options.interactive and server.TimedOut(options.timeout): 278 js_time = server.TimeSinceJSHeartbeat() 279 err = 'Did not hear from the test for %.1f seconds.' % options.timeout 280 err += '\nHeard from Javascript %.1f seconds ago.' % js_time 281 if js_time > 2.0: 282 err += '\nThe renderer probably hung or crashed.' 283 else: 284 err += '\nThe test probably did not get a callback that it expected.' 285 listener.ServerError(err) 286 break 287 elif not options.interactive and HardTimeout(options.hard_timeout): 288 listener.ServerError('The test took over %.1f seconds. This is ' 289 'probably a runaway test.' % options.hard_timeout) 290 break 291 else: 292 # If Python 2.5 support is dropped, stick server.handle_request() here. 293 time.sleep(0.125) 294 295 if options.tool: 296 sys.stdout.write('##################### Waiting for the tool to exit\n') 297 browser.WaitForProcessDeath() 298 sys.stdout.write('##################### Processing tool logs\n') 299 tool_failed = ProcessToolLogs(options, browser.tool_log_dir) 300 301 finally: 302 try: 303 if listener.ever_failed and not options.interactive: 304 if not server.received_request: 305 sys.stdout.write('\nNo URLs were served by the test runner. It is ' 306 'unlikely this test failure has anything to do with ' 307 'this particular test.\n') 308 DumpNetLog(browser.NetLogName()) 309 except Exception: 310 listener.ever_failed = 1 311 browser.Cleanup() 312 # We avoid calling server.server_close() here because it causes 313 # the HTTP server thread to exit uncleanly with an EBADF error, 314 # which adds noise to the logs (though it does not cause the test 315 # to fail). server_close() does not attempt to tell the server 316 # loop to shut down before closing the socket FD it is 317 # select()ing. Since we are about to exit, we don't really need 318 # to close the socket FD. 319 320 if tool_failed: 321 return 2 322 elif listener.ever_failed: 323 return 1 324 else: 325 return 0 326 327 328 # This is an entrypoint for tests that treat the browser tester as a Python 329 # library rather than an opaque script. 330 # (e.g. run_inbrowser_trusted_crash_in_startup_test) 331 def Run(url, options): 332 result = 1 333 attempt = 1 334 while True: 335 try: 336 result = RunTestsOnce(url, options) 337 break 338 except RetryTest: 339 # Only retry once. 340 if attempt < 2: 341 sys.stdout.write('\n@@@STEP_WARNINGS@@@\n') 342 sys.stdout.write('WARNING: suspected flake, retrying test!\n\n') 343 attempt += 1 344 continue 345 else: 346 sys.stdout.write('\nWARNING: failed too many times, not retrying.\n\n') 347 result = 1 348 break 349 return result 350 351 352 def RunFromCommandLine(): 353 parser = BuildArgParser() 354 options, args = parser.parse_args() 355 356 if len(args) != 0: 357 print args 358 parser.error('Invalid arguments') 359 360 # Validate the URL 361 url = options.url 362 if url is None: 363 parser.error('Must specify a URL') 364 365 return Run(url, options) 366 367 368 if __name__ == '__main__': 369 sys.exit(RunFromCommandLine()) 370