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