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 20 matching lines...) Expand all Loading... |
31 import logging | 31 import logging |
32 import re | 32 import re |
33 import shlex | 33 import shlex |
34 import sys | 34 import sys |
35 import time | 35 import time |
36 import os | 36 import os |
37 | 37 |
38 from webkitpy.common.system import path | 38 from webkitpy.common.system import path |
39 from webkitpy.common.system.profiler import ProfilerFactory | 39 from webkitpy.common.system.profiler import ProfilerFactory |
40 | 40 |
41 | |
42 _log = logging.getLogger(__name__) | 41 _log = logging.getLogger(__name__) |
43 | 42 |
44 | |
45 DRIVER_START_TIMEOUT_SECS = 30 | 43 DRIVER_START_TIMEOUT_SECS = 30 |
46 | 44 |
47 | 45 |
48 class DriverInput(object): | 46 class DriverInput(object): |
49 def __init__(self, test_name, timeout, image_hash, should_run_pixel_test, ar
gs): | 47 def __init__(self, test_name, timeout, image_hash, should_run_pixel_test, ar
gs): |
50 self.test_name = test_name | 48 self.test_name = test_name |
51 self.timeout = timeout # in ms | 49 self.timeout = timeout # in ms |
52 self.image_hash = image_hash | 50 self.image_hash = image_hash |
53 self.should_run_pixel_test = should_run_pixel_test | 51 self.should_run_pixel_test = should_run_pixel_test |
54 self.args = args | 52 self.args = args |
55 | 53 |
56 | 54 |
57 class DriverOutput(object): | 55 class DriverOutput(object): |
58 """Groups information about a output from driver for easy passing | 56 """Groups information about a output from driver for easy passing |
59 and post-processing of data.""" | 57 and post-processing of data.""" |
60 | 58 |
61 def __init__(self, text, image, image_hash, audio, crash=False, | 59 def __init__(self, |
62 test_time=0, measurements=None, timeout=False, error='', crashed_pro
cess_name='??', | 60 text, |
63 crashed_pid=None, crash_log=None, leak=False, leak_log=None, pid=Non
e): | 61 image, |
| 62 image_hash, |
| 63 audio, |
| 64 crash=False, |
| 65 test_time=0, |
| 66 measurements=None, |
| 67 timeout=False, |
| 68 error='', |
| 69 crashed_process_name='??', |
| 70 crashed_pid=None, |
| 71 crash_log=None, |
| 72 leak=False, |
| 73 leak_log=None, |
| 74 pid=None): |
64 # FIXME: Args could be renamed to better clarify what they do. | 75 # FIXME: Args could be renamed to better clarify what they do. |
65 self.text = text | 76 self.text = text |
66 self.image = image # May be empty-string if the test crashes. | 77 self.image = image # May be empty-string if the test crashes. |
67 self.image_hash = image_hash | 78 self.image_hash = image_hash |
68 self.image_diff = None # image_diff gets filled in after construction. | 79 self.image_diff = None # image_diff gets filled in after construction. |
69 self.audio = audio # Binary format is port-dependent. | 80 self.audio = audio # Binary format is port-dependent. |
70 self.crash = crash | 81 self.crash = crash |
71 self.crashed_process_name = crashed_process_name | 82 self.crashed_process_name = crashed_process_name |
72 self.crashed_pid = crashed_pid | 83 self.crashed_pid = crashed_pid |
73 self.crash_log = crash_log | 84 self.crash_log = crash_log |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
125 # FIXME: We should probably remove _read_first_block and _read_optional_
image_block and | 136 # FIXME: We should probably remove _read_first_block and _read_optional_
image_block and |
126 # instead scope these locally in run_test. | 137 # instead scope these locally in run_test. |
127 self.error_from_test = str() | 138 self.error_from_test = str() |
128 self.err_seen_eof = False | 139 self.err_seen_eof = False |
129 self._server_process = None | 140 self._server_process = None |
130 self._current_cmd_line = None | 141 self._current_cmd_line = None |
131 | 142 |
132 self._measurements = {} | 143 self._measurements = {} |
133 if self._port.get_option("profile"): | 144 if self._port.get_option("profile"): |
134 profiler_name = self._port.get_option("profiler") | 145 profiler_name = self._port.get_option("profiler") |
135 self._profiler = ProfilerFactory.create_profiler(self._port.host, | 146 self._profiler = ProfilerFactory.create_profiler(self._port.host, se
lf._port._path_to_driver(), |
136 self._port._path_to_driver(), self._port.results_directory(), pr
ofiler_name) | 147 self._port.results_
directory(), profiler_name) |
137 else: | 148 else: |
138 self._profiler = None | 149 self._profiler = None |
139 | 150 |
140 def __del__(self): | 151 def __del__(self): |
141 self.stop() | 152 self.stop() |
142 | 153 |
143 def run_test(self, driver_input, stop_when_done): | 154 def run_test(self, driver_input, stop_when_done): |
144 """Run a single test and return the results. | 155 """Run a single test and return the results. |
145 | 156 |
146 Note that it is okay if a test times out or crashes. content_shell | 157 Note that it is okay if a test times out or crashes. content_shell |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
196 pid_str = str(self._crashed_pid) if self._crashed_pid else "unkn
own pid" | 207 pid_str = str(self._crashed_pid) if self._crashed_pid else "unkn
own pid" |
197 crash_log = 'No crash log found for %s:%s.\n' % (self._crashed_p
rocess_name, pid_str) | 208 crash_log = 'No crash log found for %s:%s.\n' % (self._crashed_p
rocess_name, pid_str) |
198 # If we were unresponsive append a message informing there may n
ot have been a crash. | 209 # If we were unresponsive append a message informing there may n
ot have been a crash. |
199 if self._subprocess_was_unresponsive: | 210 if self._subprocess_was_unresponsive: |
200 crash_log += 'Process failed to become responsive before tim
ing out.\n' | 211 crash_log += 'Process failed to become responsive before tim
ing out.\n' |
201 | 212 |
202 # Print stdout and stderr to the placeholder crash log; we want
as much context as possible. | 213 # Print stdout and stderr to the placeholder crash log; we want
as much context as possible. |
203 if self.error_from_test: | 214 if self.error_from_test: |
204 crash_log += '\nstdout:\n%s\nstderr:\n%s\n' % (text, self.er
ror_from_test) | 215 crash_log += '\nstdout:\n%s\nstderr:\n%s\n' % (text, self.er
ror_from_test) |
205 | 216 |
206 return DriverOutput(text, image, actual_image_hash, audio, | 217 return DriverOutput(text, |
207 crash=crashed, test_time=time.time() - test_begin_time, measurements
=self._measurements, | 218 image, |
208 timeout=timed_out, error=self.error_from_test, | 219 actual_image_hash, |
209 crashed_process_name=self._crashed_process_name, | 220 audio, |
210 crashed_pid=self._crashed_pid, crash_log=crash_log, | 221 crash=crashed, |
211 leak=leaked, leak_log=self._leak_log, | 222 test_time=time.time() - test_begin_time, |
212 pid=pid) | 223 measurements=self._measurements, |
| 224 timeout=timed_out, |
| 225 error=self.error_from_test, |
| 226 crashed_process_name=self._crashed_process_name, |
| 227 crashed_pid=self._crashed_pid, |
| 228 crash_log=crash_log, |
| 229 leak=leaked, |
| 230 leak_log=self._leak_log, |
| 231 pid=pid) |
213 | 232 |
214 def _get_crash_log(self, stdout, stderr, newer_than): | 233 def _get_crash_log(self, stdout, stderr, newer_than): |
215 return self._port._get_crash_log(self._crashed_process_name, self._crash
ed_pid, stdout, stderr, newer_than) | 234 return self._port._get_crash_log(self._crashed_process_name, self._crash
ed_pid, stdout, stderr, newer_than) |
216 | 235 |
217 # FIXME: Seems this could just be inlined into callers. | 236 # FIXME: Seems this could just be inlined into callers. |
218 @classmethod | 237 @classmethod |
219 def _command_wrapper(cls, wrapper_option): | 238 def _command_wrapper(cls, wrapper_option): |
220 # Hook for injecting valgrind or other runtime instrumentation, | 239 # Hook for injecting valgrind or other runtime instrumentation, |
221 # used by e.g. tools/valgrind/valgrind_tests.py. | 240 # used by e.g. tools/valgrind/valgrind_tests.py. |
222 return shlex.split(wrapper_option) if wrapper_option else [] | 241 return shlex.split(wrapper_option) if wrapper_option else [] |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
312 self.stop() | 331 self.stop() |
313 self._driver_tempdir = self._port._filesystem.mkdtemp(prefix='%s-' % sel
f._port.driver_name()) | 332 self._driver_tempdir = self._port._filesystem.mkdtemp(prefix='%s-' % sel
f._port.driver_name()) |
314 server_name = self._port.driver_name() | 333 server_name = self._port.driver_name() |
315 environment = self._port.setup_environ_for_server(server_name) | 334 environment = self._port.setup_environ_for_server(server_name) |
316 environment = self._setup_environ_for_driver(environment) | 335 environment = self._setup_environ_for_driver(environment) |
317 self._crashed_process_name = None | 336 self._crashed_process_name = None |
318 self._crashed_pid = None | 337 self._crashed_pid = None |
319 self._leaked = False | 338 self._leaked = False |
320 self._leak_log = None | 339 self._leak_log = None |
321 cmd_line = self.cmd_line(pixel_tests, per_test_args) | 340 cmd_line = self.cmd_line(pixel_tests, per_test_args) |
322 self._server_process = self._port._server_process_constructor(self._port
, server_name, cmd_line, environment, logging=self._port.get_option("driver_logg
ing")) | 341 self._server_process = self._port._server_process_constructor(self._port
, |
| 342 server_nam
e, |
| 343 cmd_line, |
| 344 environmen
t, |
| 345 logging=se
lf._port.get_option("driver_logging")) |
323 self._server_process.start() | 346 self._server_process.start() |
324 self._current_cmd_line = cmd_line | 347 self._current_cmd_line = cmd_line |
325 | 348 |
326 if wait_for_ready: | 349 if wait_for_ready: |
327 deadline = time.time() + DRIVER_START_TIMEOUT_SECS | 350 deadline = time.time() + DRIVER_START_TIMEOUT_SECS |
328 if not self._wait_for_server_process_output(self._server_process, de
adline, '#READY'): | 351 if not self._wait_for_server_process_output(self._server_process, de
adline, '#READY'): |
329 _log.error("content_shell took too long to startup.") | 352 _log.error("content_shell took too long to startup.") |
330 | 353 |
331 def _wait_for_server_process_output(self, server_process, deadline, text): | 354 def _wait_for_server_process_output(self, server_process, deadline, text): |
332 output = '' | 355 output = '' |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
375 cmd.extend(per_test_args) | 398 cmd.extend(per_test_args) |
376 cmd.append('-') | 399 cmd.append('-') |
377 return cmd | 400 return cmd |
378 | 401 |
379 def _check_for_driver_crash(self, error_line): | 402 def _check_for_driver_crash(self, error_line): |
380 if error_line == "#CRASHED\n": | 403 if error_line == "#CRASHED\n": |
381 # This is used on Windows to report that the process has crashed | 404 # This is used on Windows to report that the process has crashed |
382 # See http://trac.webkit.org/changeset/65537. | 405 # See http://trac.webkit.org/changeset/65537. |
383 self._crashed_process_name = self._server_process.name() | 406 self._crashed_process_name = self._server_process.name() |
384 self._crashed_pid = self._server_process.pid() | 407 self._crashed_pid = self._server_process.pid() |
385 elif (error_line.startswith("#CRASHED - ") | 408 elif (error_line.startswith("#CRASHED - ") or error_line.startswith("#PR
OCESS UNRESPONSIVE - ")): |
386 or error_line.startswith("#PROCESS UNRESPONSIVE - ")): | |
387 # WebKitTestRunner uses this to report that the WebProcess subproces
s crashed. | 409 # WebKitTestRunner uses this to report that the WebProcess subproces
s crashed. |
388 match = re.match('#(?:CRASHED|PROCESS UNRESPONSIVE) - (\S+)', error_
line) | 410 match = re.match('#(?:CRASHED|PROCESS UNRESPONSIVE) - (\S+)', error_
line) |
389 self._crashed_process_name = match.group(1) if match else 'WebProces
s' | 411 self._crashed_process_name = match.group(1) if match else 'WebProces
s' |
390 match = re.search('pid (\d+)', error_line) | 412 match = re.search('pid (\d+)', error_line) |
391 pid = int(match.group(1)) if match else None | 413 pid = int(match.group(1)) if match else None |
392 self._crashed_pid = pid | 414 self._crashed_pid = pid |
393 # FIXME: delete this after we're sure this code is working :) | 415 # FIXME: delete this after we're sure this code is working :) |
394 _log.debug('%s crash, pid = %s, error_line = %s' % (self._crashed_pr
ocess_name, str(pid), error_line)) | 416 _log.debug('%s crash, pid = %s, error_line = %s' % (self._crashed_pr
ocess_name, str(pid), error_line)) |
395 if error_line.startswith("#PROCESS UNRESPONSIVE - "): | 417 if error_line.startswith("#PROCESS UNRESPONSIVE - "): |
396 self._subprocess_was_unresponsive = True | 418 self._subprocess_was_unresponsive = True |
397 self._port.sample_process(self._crashed_process_name, self._cras
hed_pid) | 419 self._port.sample_process(self._crashed_process_name, self._cras
hed_pid) |
398 # We want to show this since it's not a regular crash and probab
ly we don't have a crash log. | 420 # We want to show this since it's not a regular crash and probab
ly we don't have a crash log. |
399 self.error_from_test += error_line | 421 self.error_from_test += error_line |
400 return True | 422 return True |
401 return self.has_crashed() | 423 return self.has_crashed() |
402 | 424 |
403 def _check_for_leak(self, error_line): | 425 def _check_for_leak(self, error_line): |
404 if error_line.startswith("#LEAK - "): | 426 if error_line.startswith("#LEAK - "): |
405 self._leaked = True | 427 self._leaked = True |
406 match = re.match('#LEAK - (\S+) pid (\d+) (.+)\n', error_line) | 428 match = re.match('#LEAK - (\S+) pid (\d+) (.+)\n', error_line) |
407 self._leak_log = match.group(3) | 429 self._leak_log = match.group(3) |
408 return self._leaked | 430 return self._leaked |
409 | 431 |
410 def _command_from_driver_input(self, driver_input): | 432 def _command_from_driver_input(self, driver_input): |
411 # FIXME: performance tests pass in full URLs instead of test names. | 433 # FIXME: performance tests pass in full URLs instead of test names. |
412 if driver_input.test_name.startswith('http://') or driver_input.test_nam
e.startswith('https://') or driver_input.test_name == ('about:blank'): | 434 if driver_input.test_name.startswith('http://') or driver_input.test_nam
e.startswith( |
| 435 'https://') or driver_input.test_name == ('about:blank'): |
413 command = driver_input.test_name | 436 command = driver_input.test_name |
414 elif self.is_http_test(driver_input.test_name) or self._should_treat_as_
wpt_test(driver_input.test_name): | 437 elif self.is_http_test(driver_input.test_name) or self._should_treat_as_
wpt_test(driver_input.test_name): |
415 command = self.test_to_uri(driver_input.test_name) | 438 command = self.test_to_uri(driver_input.test_name) |
416 else: | 439 else: |
417 command = self._port.abspath_for_test(driver_input.test_name) | 440 command = self._port.abspath_for_test(driver_input.test_name) |
418 if sys.platform == 'cygwin': | 441 if sys.platform == 'cygwin': |
419 command = path.cygpath(command) | 442 command = path.cygpath(command) |
420 | 443 |
421 assert not driver_input.image_hash or driver_input.should_run_pixel_test | 444 assert not driver_input.image_hash or driver_input.should_run_pixel_test |
422 | 445 |
(...skipping 27 matching lines...) Expand all Loading... |
450 def _read_header(self, block, line, header_text, header_attr, header_filter=
None): | 473 def _read_header(self, block, line, header_text, header_attr, header_filter=
None): |
451 if line.startswith(header_text) and getattr(block, header_attr) is None: | 474 if line.startswith(header_text) and getattr(block, header_attr) is None: |
452 value = line.split()[1] | 475 value = line.split()[1] |
453 if header_filter: | 476 if header_filter: |
454 value = header_filter(value) | 477 value = header_filter(value) |
455 setattr(block, header_attr, value) | 478 setattr(block, header_attr, value) |
456 return True | 479 return True |
457 return False | 480 return False |
458 | 481 |
459 def _process_stdout_line(self, block, line): | 482 def _process_stdout_line(self, block, line): |
460 if (self._read_header(block, line, 'Content-Type: ', 'content_type') | 483 if (self._read_header(block, line, 'Content-Type: ', 'content_type') or |
461 or self._read_header(block, line, 'Content-Transfer-Encoding: ', 'en
coding') | 484 self._read_header(block, line, 'Content-Transfer-Encoding: ', 'e
ncoding') or |
462 or self._read_header(block, line, 'Content-Length: ', '_content_leng
th', int) | 485 self._read_header(block, line, 'Content-Length: ', '_content_len
gth', int) or |
463 or self._read_header(block, line, 'ActualHash: ', 'content_hash') | 486 self._read_header(block, line, 'ActualHash: ', 'content_hash') o
r |
464 or self._read_header(block, line, 'DumpMalloc: ', 'malloc') | 487 self._read_header(block, line, 'DumpMalloc: ', 'malloc') or |
465 or self._read_header(block, line, 'DumpJSHeap: ', 'js_heap') | 488 self._read_header(block, line, 'DumpJSHeap: ', 'js_heap') or |
466 or self._read_header(block, line, 'StdinPath', 'stdin_path')): | 489 self._read_header(block, line, 'StdinPath', 'stdin_path')): |
467 return | 490 return |
468 # Note, we're not reading ExpectedHash: here, but we could. | 491 # Note, we're not reading ExpectedHash: here, but we could. |
469 # If the line wasn't a header, we just append it to the content. | 492 # If the line wasn't a header, we just append it to the content. |
470 block.content += line | 493 block.content += line |
471 | 494 |
472 def _strip_eof(self, line): | 495 def _strip_eof(self, line): |
473 if line and line.endswith("#EOF\n"): | 496 if line and line.endswith("#EOF\n"): |
474 return line[:-5], True | 497 return line[:-5], True |
475 if line and line.endswith("#EOF\r\n"): | 498 if line and line.endswith("#EOF\r\n"): |
476 _log.error("Got a CRLF-terminated #EOF - this is a driver bug.") | 499 _log.error("Got a CRLF-terminated #EOF - this is a driver bug.") |
(...skipping 22 matching lines...) Expand all Loading... |
499 | 522 |
500 if out_line: | 523 if out_line: |
501 assert not out_seen_eof | 524 assert not out_seen_eof |
502 out_line, out_seen_eof = self._strip_eof(out_line) | 525 out_line, out_seen_eof = self._strip_eof(out_line) |
503 if err_line: | 526 if err_line: |
504 assert not self.err_seen_eof | 527 assert not self.err_seen_eof |
505 err_line, self.err_seen_eof = self._strip_eof(err_line) | 528 err_line, self.err_seen_eof = self._strip_eof(err_line) |
506 | 529 |
507 if out_line: | 530 if out_line: |
508 if out_line[-1] != "\n": | 531 if out_line[-1] != "\n": |
509 _log.error("Last character read from DRT stdout line was not
a newline! This indicates either a NRWT or DRT bug.") | 532 _log.error( |
| 533 "Last character read from DRT stdout line was not a newl
ine! This indicates either a NRWT or DRT bug.") |
510 content_length_before_header_check = block._content_length | 534 content_length_before_header_check = block._content_length |
511 self._process_stdout_line(block, out_line) | 535 self._process_stdout_line(block, out_line) |
512 # FIXME: Unlike HTTP, DRT dumps the content right after printing
a Content-Length header. | 536 # FIXME: Unlike HTTP, DRT dumps the content right after printing
a Content-Length header. |
513 # Don't wait until we're done with headers, just read the binary
blob right now. | 537 # Don't wait until we're done with headers, just read the binary
blob right now. |
514 if content_length_before_header_check != block._content_length: | 538 if content_length_before_header_check != block._content_length: |
515 if block._content_length > 0: | 539 if block._content_length > 0: |
516 block.content = self._server_process.read_stdout(deadlin
e, block._content_length) | 540 block.content = self._server_process.read_stdout(deadlin
e, block._content_length) |
517 else: | 541 else: |
518 _log.error("Received content of type %s with Content-Len
gth of 0! This indicates a bug in %s.", | 542 _log.error("Received content of type %s with Content-Len
gth of 0! This indicates a bug in %s.", |
519 block.content_type, self._server_process.name
()) | 543 block.content_type, self._server_process.name
()) |
(...skipping 20 matching lines...) Expand all Loading... |
540 self.decoded_content = None | 564 self.decoded_content = None |
541 self.malloc = None | 565 self.malloc = None |
542 self.js_heap = None | 566 self.js_heap = None |
543 self.stdin_path = None | 567 self.stdin_path = None |
544 | 568 |
545 def decode_content(self): | 569 def decode_content(self): |
546 if self.encoding == 'base64' and self.content is not None: | 570 if self.encoding == 'base64' and self.content is not None: |
547 self.decoded_content = base64.b64decode(self.content) | 571 self.decoded_content = base64.b64decode(self.content) |
548 else: | 572 else: |
549 self.decoded_content = self.content | 573 self.decoded_content = self.content |
OLD | NEW |