OLD | NEW |
1 # Copyright (C) 2011 Google Inc. All rights reserved. | 1 # Copyright (C) 2011 Google Inc. All rights reserved. |
2 # | 2 # |
3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
5 # met: | 5 # met: |
6 # | 6 # |
7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
10 # copyright notice, this list of conditions and the following disclaimer | 10 # copyright notice, this list of conditions and the following disclaimer |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
53 self.should_run_pixel_test = should_run_pixel_test | 53 self.should_run_pixel_test = should_run_pixel_test |
54 self.args = args | 54 self.args = args |
55 | 55 |
56 | 56 |
57 class DriverOutput(object): | 57 class DriverOutput(object): |
58 """Groups information about a output from driver for easy passing | 58 """Groups information about a output from driver for easy passing |
59 and post-processing of data.""" | 59 and post-processing of data.""" |
60 | 60 |
61 def __init__(self, text, image, image_hash, audio, crash=False, | 61 def __init__(self, text, image, image_hash, audio, crash=False, |
62 test_time=0, measurements=None, timeout=False, error='', crashed_pro
cess_name='??', | 62 test_time=0, measurements=None, timeout=False, error='', crashed_pro
cess_name='??', |
63 crashed_pid=None, crash_log=None, pid=None): | 63 crashed_pid=None, crash_log=None, leak=False, leak_log=None, pid=Non
e): |
64 # FIXME: Args could be renamed to better clarify what they do. | 64 # FIXME: Args could be renamed to better clarify what they do. |
65 self.text = text | 65 self.text = text |
66 self.image = image # May be empty-string if the test crashes. | 66 self.image = image # May be empty-string if the test crashes. |
67 self.image_hash = image_hash | 67 self.image_hash = image_hash |
68 self.image_diff = None # image_diff gets filled in after construction. | 68 self.image_diff = None # image_diff gets filled in after construction. |
69 self.audio = audio # Binary format is port-dependent. | 69 self.audio = audio # Binary format is port-dependent. |
70 self.crash = crash | 70 self.crash = crash |
71 self.crashed_process_name = crashed_process_name | 71 self.crashed_process_name = crashed_process_name |
72 self.crashed_pid = crashed_pid | 72 self.crashed_pid = crashed_pid |
73 self.crash_log = crash_log | 73 self.crash_log = crash_log |
| 74 self.leak = leak |
| 75 self.leak_log = leak_log |
74 self.test_time = test_time | 76 self.test_time = test_time |
75 self.measurements = measurements | 77 self.measurements = measurements |
76 self.timeout = timeout | 78 self.timeout = timeout |
77 self.error = error # stderr output | 79 self.error = error # stderr output |
78 self.pid = pid | 80 self.pid = pid |
79 | 81 |
80 def has_stderr(self): | 82 def has_stderr(self): |
81 return bool(self.error) | 83 return bool(self.error) |
82 | 84 |
83 | 85 |
(...skipping 11 matching lines...) Expand all Loading... |
95 ready for subsequent input. | 97 ready for subsequent input. |
96 | 98 |
97 port - reference back to the port object. | 99 port - reference back to the port object. |
98 worker_number - identifier for a particular worker/driver instance | 100 worker_number - identifier for a particular worker/driver instance |
99 """ | 101 """ |
100 self._port = port | 102 self._port = port |
101 self._worker_number = worker_number | 103 self._worker_number = worker_number |
102 self._no_timeout = no_timeout | 104 self._no_timeout = no_timeout |
103 | 105 |
104 self._driver_tempdir = None | 106 self._driver_tempdir = None |
105 # WebKitTestRunner can report back subprocess crashes by printing | 107 # content_shell can report back subprocess crashes by printing |
106 # "#CRASHED - PROCESSNAME". Since those can happen at any time | 108 # "#CRASHED - PROCESSNAME". Since those can happen at any time |
107 # and ServerProcess won't be aware of them (since the actual tool | 109 # and ServerProcess won't be aware of them (since the actual tool |
108 # didn't crash, just a subprocess) we record the crashed subprocess name
here. | 110 # didn't crash, just a subprocess) we record the crashed subprocess name
here. |
109 self._crashed_process_name = None | 111 self._crashed_process_name = None |
110 self._crashed_pid = None | 112 self._crashed_pid = None |
111 | 113 |
112 # WebKitTestRunner can report back subprocesses that became unresponsive | 114 # content_shell can report back subprocesses that became unresponsive |
113 # This could mean they crashed. | 115 # This could mean they crashed. |
114 self._subprocess_was_unresponsive = False | 116 self._subprocess_was_unresponsive = False |
115 | 117 |
| 118 # content_shell can report back subprocess DOM-object leaks by printing |
| 119 # "#LEAK". This leak detection is enabled only when the flag |
| 120 # --enable-leak-detection is passed to content_shell. |
| 121 self._leaked = False |
| 122 |
116 # stderr reading is scoped on a per-test (not per-block) basis, so we st
ore the accumulated | 123 # stderr reading is scoped on a per-test (not per-block) basis, so we st
ore the accumulated |
117 # stderr output, as well as if we've seen #EOF on this driver instance. | 124 # stderr output, as well as if we've seen #EOF on this driver instance. |
118 # FIXME: We should probably remove _read_first_block and _read_optional_
image_block and | 125 # FIXME: We should probably remove _read_first_block and _read_optional_
image_block and |
119 # instead scope these locally in run_test. | 126 # instead scope these locally in run_test. |
120 self.error_from_test = str() | 127 self.error_from_test = str() |
121 self.err_seen_eof = False | 128 self.err_seen_eof = False |
122 self._server_process = None | 129 self._server_process = None |
123 self._current_cmd_line = None | 130 self._current_cmd_line = None |
124 | 131 |
125 self._measurements = {} | 132 self._measurements = {} |
(...skipping 25 matching lines...) Expand all Loading... |
151 command = self._command_from_driver_input(driver_input) | 158 command = self._command_from_driver_input(driver_input) |
152 deadline = test_begin_time + int(driver_input.timeout) / 1000.0 | 159 deadline = test_begin_time + int(driver_input.timeout) / 1000.0 |
153 | 160 |
154 self._server_process.write(command) | 161 self._server_process.write(command) |
155 text, audio = self._read_first_block(deadline) # First block is either
text or audio | 162 text, audio = self._read_first_block(deadline) # First block is either
text or audio |
156 image, actual_image_hash = self._read_optional_image_block(deadline) #
The second (optional) block is image data. | 163 image, actual_image_hash = self._read_optional_image_block(deadline) #
The second (optional) block is image data. |
157 | 164 |
158 crashed = self.has_crashed() | 165 crashed = self.has_crashed() |
159 timed_out = self._server_process.timed_out | 166 timed_out = self._server_process.timed_out |
160 pid = self._server_process.pid() | 167 pid = self._server_process.pid() |
| 168 leaked = self._leaked |
161 | 169 |
162 if stop_when_done or crashed or timed_out: | 170 if stop_when_done or crashed or timed_out or leaked: |
163 # We call stop() even if we crashed or timed out in order to get any
remaining stdout/stderr output. | 171 # We call stop() even if we crashed or timed out in order to get any
remaining stdout/stderr output. |
164 # In the timeout case, we kill the hung process as well. | 172 # In the timeout case, we kill the hung process as well. |
165 out, err = self._server_process.stop(self._port.driver_stop_timeout(
) if stop_when_done else 0.0) | 173 out, err = self._server_process.stop(self._port.driver_stop_timeout(
) if stop_when_done else 0.0) |
166 if out: | 174 if out: |
167 text += out | 175 text += out |
168 if err: | 176 if err: |
169 self.error_from_test += err | 177 self.error_from_test += err |
170 self._server_process = None | 178 self._server_process = None |
171 | 179 |
172 crash_log = None | 180 crash_log = None |
| 181 leak_log = None |
173 if crashed: | 182 if crashed: |
174 self.error_from_test, crash_log = self._get_crash_log(text, self.err
or_from_test, newer_than=start_time) | 183 self.error_from_test, crash_log = self._get_crash_log(text, self.err
or_from_test, newer_than=start_time) |
175 | 184 |
176 # If we don't find a crash log use a placeholder error message inste
ad. | 185 # If we don't find a crash log use a placeholder error message inste
ad. |
177 if not crash_log: | 186 if not crash_log: |
178 pid_str = str(self._crashed_pid) if self._crashed_pid else "unkn
own pid" | 187 pid_str = str(self._crashed_pid) if self._crashed_pid else "unkn
own pid" |
179 crash_log = 'No crash log found for %s:%s.\n' % (self._crashed_p
rocess_name, pid_str) | 188 crash_log = 'No crash log found for %s:%s.\n' % (self._crashed_p
rocess_name, pid_str) |
180 # If we were unresponsive append a message informing there may n
ot have been a crash. | 189 # If we were unresponsive append a message informing there may n
ot have been a crash. |
181 if self._subprocess_was_unresponsive: | 190 if self._subprocess_was_unresponsive: |
182 crash_log += 'Process failed to become responsive before tim
ing out.\n' | 191 crash_log += 'Process failed to become responsive before tim
ing out.\n' |
183 | 192 |
184 # Print stdout and stderr to the placeholder crash log; we want
as much context as possible. | 193 # Print stdout and stderr to the placeholder crash log; we want
as much context as possible. |
185 if self.error_from_test: | 194 if self.error_from_test: |
186 crash_log += '\nstdout:\n%s\nstderr:\n%s\n' % (text, self.er
ror_from_test) | 195 crash_log += '\nstdout:\n%s\nstderr:\n%s\n' % (text, self.er
ror_from_test) |
| 196 elif leaked: |
| 197 self.error_from_test, leak_log = self._get_leak_log(text, self.error
_from_test, newer_than=start_time) |
187 | 198 |
188 return DriverOutput(text, image, actual_image_hash, audio, | 199 return DriverOutput(text, image, actual_image_hash, audio, |
189 crash=crashed, test_time=time.time() - test_begin_time, measurements
=self._measurements, | 200 crash=crashed, test_time=time.time() - test_begin_time, measurements
=self._measurements, |
190 timeout=timed_out, error=self.error_from_test, | 201 timeout=timed_out, error=self.error_from_test, |
191 crashed_process_name=self._crashed_process_name, | 202 crashed_process_name=self._crashed_process_name, |
192 crashed_pid=self._crashed_pid, crash_log=crash_log, pid=pid) | 203 crashed_pid=self._crashed_pid, crash_log=crash_log, |
| 204 leak=leaked, leak_log=leak_log, |
| 205 pid=pid) |
193 | 206 |
194 def _get_crash_log(self, stdout, stderr, newer_than): | 207 def _get_crash_log(self, stdout, stderr, newer_than): |
195 return self._port._get_crash_log(self._crashed_process_name, self._crash
ed_pid, stdout, stderr, newer_than) | 208 return self._port._get_crash_log(self._crashed_process_name, self._crash
ed_pid, stdout, stderr, newer_than) |
196 | 209 |
| 210 def _get_leak_log(self, stdout, stderr, newer_than): |
| 211 return self._port._get_leak_log(self._crashed_process_name, self._crashe
d_pid, stdout, stderr, newer_than) |
| 212 |
197 # FIXME: Seems this could just be inlined into callers. | 213 # FIXME: Seems this could just be inlined into callers. |
198 @classmethod | 214 @classmethod |
199 def _command_wrapper(cls, wrapper_option): | 215 def _command_wrapper(cls, wrapper_option): |
200 # Hook for injecting valgrind or other runtime instrumentation, | 216 # Hook for injecting valgrind or other runtime instrumentation, |
201 # used by e.g. tools/valgrind/valgrind_tests.py. | 217 # used by e.g. tools/valgrind/valgrind_tests.py. |
202 return shlex.split(wrapper_option) if wrapper_option else [] | 218 return shlex.split(wrapper_option) if wrapper_option else [] |
203 | 219 |
204 HTTP_DIR = "http/tests/" | 220 HTTP_DIR = "http/tests/" |
205 HTTP_LOCAL_DIR = "http/tests/local/" | 221 HTTP_LOCAL_DIR = "http/tests/local/" |
206 | 222 |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
261 return environment | 277 return environment |
262 | 278 |
263 def _start(self, pixel_tests, per_test_args, wait_for_ready=True): | 279 def _start(self, pixel_tests, per_test_args, wait_for_ready=True): |
264 self.stop() | 280 self.stop() |
265 self._driver_tempdir = self._port._filesystem.mkdtemp(prefix='%s-' % sel
f._port.driver_name()) | 281 self._driver_tempdir = self._port._filesystem.mkdtemp(prefix='%s-' % sel
f._port.driver_name()) |
266 server_name = self._port.driver_name() | 282 server_name = self._port.driver_name() |
267 environment = self._port.setup_environ_for_server(server_name) | 283 environment = self._port.setup_environ_for_server(server_name) |
268 environment = self._setup_environ_for_driver(environment) | 284 environment = self._setup_environ_for_driver(environment) |
269 self._crashed_process_name = None | 285 self._crashed_process_name = None |
270 self._crashed_pid = None | 286 self._crashed_pid = None |
| 287 self._leaked = False |
271 cmd_line = self.cmd_line(pixel_tests, per_test_args) | 288 cmd_line = self.cmd_line(pixel_tests, per_test_args) |
272 self._server_process = self._port._server_process_constructor(self._port
, server_name, cmd_line, environment, logging=self._port.get_option("driver_logg
ing")) | 289 self._server_process = self._port._server_process_constructor(self._port
, server_name, cmd_line, environment, logging=self._port.get_option("driver_logg
ing")) |
273 self._server_process.start() | 290 self._server_process.start() |
274 self._current_cmd_line = cmd_line | 291 self._current_cmd_line = cmd_line |
275 | 292 |
276 if wait_for_ready: | 293 if wait_for_ready: |
277 deadline = time.time() + DRIVER_START_TIMEOUT_SECS | 294 deadline = time.time() + DRIVER_START_TIMEOUT_SECS |
278 if not self._wait_for_server_process_output(self._server_process, de
adline, '#READY'): | 295 if not self._wait_for_server_process_output(self._server_process, de
adline, '#READY'): |
279 _log.error("content_shell took too long to startup.") | 296 _log.error("content_shell took too long to startup.") |
280 | 297 |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
313 | 330 |
314 self._current_cmd_line = None | 331 self._current_cmd_line = None |
315 | 332 |
316 def cmd_line(self, pixel_tests, per_test_args): | 333 def cmd_line(self, pixel_tests, per_test_args): |
317 cmd = self._command_wrapper(self._port.get_option('wrapper')) | 334 cmd = self._command_wrapper(self._port.get_option('wrapper')) |
318 cmd.append(self._port._path_to_driver()) | 335 cmd.append(self._port._path_to_driver()) |
319 if self._no_timeout: | 336 if self._no_timeout: |
320 cmd.append('--no-timeout') | 337 cmd.append('--no-timeout') |
321 cmd.extend(self._port.get_option('additional_drt_flag', [])) | 338 cmd.extend(self._port.get_option('additional_drt_flag', [])) |
322 cmd.extend(self._port.additional_drt_flag()) | 339 cmd.extend(self._port.additional_drt_flag()) |
| 340 if self._port.get_option('enable_leak_detection'): |
| 341 cmd.append('--enable-leak-detection') |
323 cmd.extend(per_test_args) | 342 cmd.extend(per_test_args) |
324 cmd.append('-') | 343 cmd.append('-') |
325 return cmd | 344 return cmd |
326 | 345 |
327 def _check_for_driver_crash(self, error_line): | 346 def _check_for_driver_crash(self, error_line): |
328 if error_line == "#CRASHED\n": | 347 if error_line == "#CRASHED\n": |
329 # This is used on Windows to report that the process has crashed | 348 # This is used on Windows to report that the process has crashed |
330 # See http://trac.webkit.org/changeset/65537. | 349 # See http://trac.webkit.org/changeset/65537. |
331 self._crashed_process_name = self._server_process.name() | 350 self._crashed_process_name = self._server_process.name() |
332 self._crashed_pid = self._server_process.pid() | 351 self._crashed_pid = self._server_process.pid() |
333 elif (error_line.startswith("#CRASHED - ") | 352 elif (error_line.startswith("#CRASHED - ") |
334 or error_line.startswith("#PROCESS UNRESPONSIVE - ")): | 353 or error_line.startswith("#PROCESS UNRESPONSIVE - ")): |
335 # WebKitTestRunner uses this to report that the WebProcess subproces
s crashed. | 354 # WebKitTestRunner uses this to report that the WebProcess subproces
s crashed. |
336 match = re.match('#(?:CRASHED|PROCESS UNRESPONSIVE) - (\S+)', error_
line) | 355 match = re.match('#(?:CRASHED|PROCESS UNRESPONSIVE) - (\S+)', error_
line) |
337 self._crashed_process_name = match.group(1) if match else 'WebProces
s' | 356 self._crashed_process_name = match.group(1) if match else 'WebProces
s' |
338 match = re.search('pid (\d+)', error_line) | 357 match = re.search('pid (\d+)', error_line) |
339 pid = int(match.group(1)) if match else None | 358 pid = int(match.group(1)) if match else None |
340 self._crashed_pid = pid | 359 self._crashed_pid = pid |
341 # FIXME: delete this after we're sure this code is working :) | 360 # FIXME: delete this after we're sure this code is working :) |
342 _log.debug('%s crash, pid = %s, error_line = %s' % (self._crashed_pr
ocess_name, str(pid), error_line)) | 361 _log.debug('%s crash, pid = %s, error_line = %s' % (self._crashed_pr
ocess_name, str(pid), error_line)) |
343 if error_line.startswith("#PROCESS UNRESPONSIVE - "): | 362 if error_line.startswith("#PROCESS UNRESPONSIVE - "): |
344 self._subprocess_was_unresponsive = True | 363 self._subprocess_was_unresponsive = True |
345 self._port.sample_process(self._crashed_process_name, self._cras
hed_pid) | 364 self._port.sample_process(self._crashed_process_name, self._cras
hed_pid) |
346 # We want to show this since it's not a regular crash and probab
ly we don't have a crash log. | 365 # We want to show this since it's not a regular crash and probab
ly we don't have a crash log. |
347 self.error_from_test += error_line | 366 self.error_from_test += error_line |
348 return True | 367 return True |
349 return self.has_crashed() | 368 return self.has_crashed() |
350 | 369 |
| 370 def _check_for_leak(self, error_line): |
| 371 if error_line.startswith("#LEAK - "): |
| 372 self._leaked = True |
| 373 return self._leaked |
| 374 |
351 def _command_from_driver_input(self, driver_input): | 375 def _command_from_driver_input(self, driver_input): |
352 # FIXME: performance tests pass in full URLs instead of test names. | 376 # FIXME: performance tests pass in full URLs instead of test names. |
353 if driver_input.test_name.startswith('http://') or driver_input.test_nam
e.startswith('https://') or driver_input.test_name == ('about:blank'): | 377 if driver_input.test_name.startswith('http://') or driver_input.test_nam
e.startswith('https://') or driver_input.test_name == ('about:blank'): |
354 command = driver_input.test_name | 378 command = driver_input.test_name |
355 elif self.is_http_test(driver_input.test_name): | 379 elif self.is_http_test(driver_input.test_name): |
356 command = self.test_to_uri(driver_input.test_name) | 380 command = self.test_to_uri(driver_input.test_name) |
357 else: | 381 else: |
358 command = self._port.abspath_for_test(driver_input.test_name) | 382 command = self._port.abspath_for_test(driver_input.test_name) |
359 if sys.platform == 'cygwin': | 383 if sys.platform == 'cygwin': |
360 command = path.cygpath(command) | 384 command = path.cygpath(command) |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
454 if content_length_before_header_check != block._content_length: | 478 if content_length_before_header_check != block._content_length: |
455 if block._content_length > 0: | 479 if block._content_length > 0: |
456 block.content = self._server_process.read_stdout(deadlin
e, block._content_length) | 480 block.content = self._server_process.read_stdout(deadlin
e, block._content_length) |
457 else: | 481 else: |
458 _log.error("Received content of type %s with Content-Len
gth of 0! This indicates a bug in %s.", | 482 _log.error("Received content of type %s with Content-Len
gth of 0! This indicates a bug in %s.", |
459 block.content_type, self._server_process.name
()) | 483 block.content_type, self._server_process.name
()) |
460 | 484 |
461 if err_line: | 485 if err_line: |
462 if self._check_for_driver_crash(err_line): | 486 if self._check_for_driver_crash(err_line): |
463 break | 487 break |
| 488 self._check_for_leak(err_line) |
464 self.error_from_test += err_line | 489 self.error_from_test += err_line |
465 | 490 |
466 block.decode_content() | 491 block.decode_content() |
467 return block | 492 return block |
468 | 493 |
469 | 494 |
470 class ContentBlock(object): | 495 class ContentBlock(object): |
471 def __init__(self): | 496 def __init__(self): |
472 self.content_type = None | 497 self.content_type = None |
473 self.encoding = None | 498 self.encoding = None |
474 self.content_hash = None | 499 self.content_hash = None |
475 self._content_length = None | 500 self._content_length = None |
476 # Content is treated as binary data even though the text output is usual
ly UTF-8. | 501 # Content is treated as binary data even though the text output is usual
ly UTF-8. |
477 self.content = str() # FIXME: Should be bytearray() once we require Pyt
hon 2.6. | 502 self.content = str() # FIXME: Should be bytearray() once we require Pyt
hon 2.6. |
478 self.decoded_content = None | 503 self.decoded_content = None |
479 self.malloc = None | 504 self.malloc = None |
480 self.js_heap = None | 505 self.js_heap = None |
481 | 506 |
482 def decode_content(self): | 507 def decode_content(self): |
483 if self.encoding == 'base64' and self.content is not None: | 508 if self.encoding == 'base64' and self.content is not None: |
484 self.decoded_content = base64.b64decode(self.content) | 509 self.decoded_content = base64.b64decode(self.content) |
485 else: | 510 else: |
486 self.decoded_content = self.content | 511 self.decoded_content = self.content |
OLD | NEW |