Better fix for Layout Test fast/parser/external-entities-in-xslt.xml is flaky on...
[WebKit-https.git] / Tools / Scripts / webkitpy / port / driver.py
1 # Copyright (C) 2011 Google Inc. All rights reserved.
2 # Copyright (c) 2015, 2016 Apple Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the Google name nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 import base64
31 import copy
32 import logging
33 import re
34 import shlex
35 import sys
36 import time
37 import os
38
39 from webkitpy.common.system import path
40 from webkitpy.common.system.profiler import ProfilerFactory
41 from webkitpy.layout_tests.servers.web_platform_test_server import WebPlatformTestServer
42
43
44 _log = logging.getLogger(__name__)
45
46
47 class DriverInput(object):
48     def __init__(self, test_name, timeout, image_hash, should_run_pixel_test, args=None):
49         self.test_name = test_name
50         self.timeout = timeout  # in ms
51         self.image_hash = image_hash
52         self.should_run_pixel_test = should_run_pixel_test
53         self.args = args or []
54
55
56 class DriverOutput(object):
57     """Groups information about a output from driver for easy passing
58     and post-processing of data."""
59
60     metrics_patterns = []
61     metrics_patterns.append((re.compile('at \(-?[0-9]+,-?[0-9]+\) *'), ''))
62     metrics_patterns.append((re.compile('size -?[0-9]+x-?[0-9]+ *'), ''))
63     metrics_patterns.append((re.compile('text run width -?[0-9]+: '), ''))
64     metrics_patterns.append((re.compile('text run width -?[0-9]+ [a-zA-Z ]+: '), ''))
65     metrics_patterns.append((re.compile('RenderButton {BUTTON} .*'), 'RenderButton {BUTTON}'))
66     metrics_patterns.append((re.compile('RenderImage {INPUT} .*'), 'RenderImage {INPUT}'))
67     metrics_patterns.append((re.compile('RenderBlock {INPUT} .*'), 'RenderBlock {INPUT}'))
68     metrics_patterns.append((re.compile('RenderTextControl {INPUT} .*'), 'RenderTextControl {INPUT}'))
69     metrics_patterns.append((re.compile('\([0-9]+px'), 'px'))
70     metrics_patterns.append((re.compile(' *" *\n +" *'), ' '))
71     metrics_patterns.append((re.compile('" +$'), '"'))
72     metrics_patterns.append((re.compile('- '), '-'))
73     metrics_patterns.append((re.compile('\n( *)"\s+'), '\n\g<1>"'))
74     metrics_patterns.append((re.compile('\s+"\n'), '"\n'))
75     metrics_patterns.append((re.compile('scrollWidth [0-9]+'), 'scrollWidth'))
76     metrics_patterns.append((re.compile('scrollHeight [0-9]+'), 'scrollHeight'))
77     metrics_patterns.append((re.compile('scrollX [0-9]+'), 'scrollX'))
78     metrics_patterns.append((re.compile('scrollY [0-9]+'), 'scrollY'))
79     metrics_patterns.append((re.compile('scrolled to [0-9]+,[0-9]+'), 'scrolled'))
80
81     def __init__(self, text, image, image_hash, audio, crash=False,
82             test_time=0, measurements=None, timeout=False, error='', crashed_process_name='??',
83             crashed_pid=None, crash_log=None, pid=None):
84         # FIXME: Args could be renamed to better clarify what they do.
85         self.text = text
86         self.image = image  # May be empty-string if the test crashes.
87         self.image_hash = image_hash
88         self.image_diff = None  # image_diff gets filled in after construction.
89         self.audio = audio  # Binary format is port-dependent.
90         self.crash = crash
91         self.crashed_process_name = crashed_process_name
92         self.crashed_pid = crashed_pid
93         self.crash_log = crash_log
94         self.test_time = test_time
95         self.measurements = measurements
96         self.timeout = timeout
97         self.error = error  # stderr output
98         self.pid = pid
99
100     def has_stderr(self):
101         return bool(self.error)
102
103     def strip_metrics(self):
104         self.strip_patterns(self.metrics_patterns)
105
106     def strip_patterns(self, patterns):
107         if not self.text:
108             return
109         for pattern in patterns:
110             self.text = re.sub(pattern[0], pattern[1], self.text)
111
112     def strip_stderror_patterns(self, patterns):
113         if not self.error:
114             return
115         for pattern in patterns:
116             self.error = re.sub(pattern[0], pattern[1], self.error)
117
118 class Driver(object):
119     """object for running test(s) using DumpRenderTree/WebKitTestRunner."""
120
121     def __init__(self, port, worker_number, pixel_tests, no_timeout=False):
122         """Initialize a Driver to subsequently run tests.
123
124         Typically this routine will spawn DumpRenderTree in a config
125         ready for subsequent input.
126
127         port - reference back to the port object.
128         worker_number - identifier for a particular worker/driver instance
129         """
130         self._port = port
131         self._worker_number = worker_number
132         self._no_timeout = no_timeout
133
134         self._driver_tempdir = None
135         # WebKitTestRunner/LayoutTestRelay can report back subprocess crashes by printing
136         # "#CRASHED - PROCESSNAME".  Since those can happen at any time and ServerProcess
137         # won't be aware of them (since the actual tool didn't crash, just a subprocess)
138         # we record the crashed subprocess name here.
139         self._crashed_process_name = None
140         self._crashed_pid = None
141
142         self._driver_timed_out = False
143
144         # stderr reading is scoped on a per-test (not per-block) basis, so we store the accumulated
145         # stderr output, as well as if we've seen #EOF on this driver instance.
146         # FIXME: We should probably remove _read_first_block and _read_optional_image_block and
147         # instead scope these locally in run_test.
148         self.error_from_test = str()
149         self.err_seen_eof = False
150         self._server_process = None
151
152         self._measurements = {}
153         if self._port.get_option("profile"):
154             profiler_name = self._port.get_option("profiler")
155             self._profiler = ProfilerFactory.create_profiler(self._port.host,
156                 self._port._path_to_driver(), self._port.results_directory(), profiler_name)
157         else:
158             self._profiler = None
159
160         self.web_platform_test_server_doc_root = self._port.web_platform_test_server_doc_root()
161         self.web_platform_test_server_base_url = self._port.web_platform_test_server_base_url()
162
163     def __del__(self):
164         self.stop()
165
166     def run_test(self, driver_input, stop_when_done):
167         """Run a single test and return the results.
168
169         Note that it is okay if a test times out or crashes and leaves
170         the driver in an indeterminate state. The upper layers of the program
171         are responsible for cleaning up and ensuring things are okay.
172
173         Returns a DriverOutput object.
174         """
175         start_time = time.time()
176         self.start(driver_input.should_run_pixel_test, driver_input.args)
177         test_begin_time = time.time()
178         self._driver_timed_out = False
179         self._crash_report_from_driver = None
180         self.error_from_test = str()
181         self.err_seen_eof = False
182
183         command = self._command_from_driver_input(driver_input)
184
185         # Certain timeouts are detected by the tool itself; tool detection is better,
186         # because results contain partial output in this case. Make script timeout longer
187         # by 5 seconds to avoid racing for which timeout is detected first.
188         # FIXME: It's not the job of the driver to decide what the timeouts should be.
189         # Move the additional timeout to driver_input.
190         if self._no_timeout:
191             deadline = test_begin_time + 60 * 60 * 24 * 7  # 7 days. Using sys.maxint causes a hang.
192         else:
193             deadline = test_begin_time + int(driver_input.timeout) / 1000.0 + 5
194
195         self._server_process.write(command)
196         text, audio = self._read_first_block(deadline)  # First block is either text or audio
197         image, actual_image_hash = self._read_optional_image_block(deadline)  # The second (optional) block is image data.
198
199         crashed = self.has_crashed()
200         timed_out = self._server_process.timed_out
201         driver_timed_out = self._driver_timed_out
202         pid = self._server_process.pid()
203
204         if stop_when_done or crashed or timed_out:
205             # We call stop() even if we crashed or timed out in order to get any remaining stdout/stderr output.
206             # In the timeout case, we kill the hung process as well.
207             out, err = self._server_process.stop(self._port.driver_stop_timeout() if stop_when_done else 0.0)
208             if out:
209                 text += out
210             if err:
211                 self.error_from_test += err
212             self._server_process = None
213
214         crash_log = None
215         if self._crash_report_from_driver:
216             crash_log = self._crash_report_from_driver
217         elif crashed:
218             self.error_from_test, crash_log = self._get_crash_log(text, self.error_from_test, newer_than=start_time)
219             # If we don't find a crash log use a placeholder error message instead.
220             if not crash_log:
221                 pid_str = str(self._crashed_pid) if self._crashed_pid else "unknown pid"
222                 crash_log = 'No crash log found for %s:%s.\n' % (self._crashed_process_name, pid_str)
223
224                 # Print stdout and stderr to the placeholder crash log; we want as much context as possible.
225                 if self.error_from_test:
226                     crash_log += '\nstdout:\n%s\nstderr:\n%s\n' % (text, self.error_from_test)
227
228         return DriverOutput(text, image, actual_image_hash, audio,
229             crash=crashed, test_time=time.time() - test_begin_time, measurements=self._measurements,
230             timeout=timed_out or driver_timed_out, error=self.error_from_test,
231             crashed_process_name=self._crashed_process_name,
232             crashed_pid=self._crashed_pid, crash_log=crash_log, pid=pid)
233
234     def _get_crash_log(self, stdout, stderr, newer_than):
235         return self._port._get_crash_log(self._crashed_process_name, self._crashed_pid, stdout, stderr, newer_than)
236
237     def _command_wrapper(self):
238         # Hook for injecting valgrind or other runtime instrumentation, used by e.g. tools/valgrind/valgrind_tests.py.
239         wrapper_arguments = []
240         if self._profiler:
241             wrapper_arguments = self._profiler.wrapper_arguments()
242         if self._port.get_option('wrapper'):
243             return shlex.split(self._port.get_option('wrapper')) + wrapper_arguments
244         return wrapper_arguments
245
246     HTTP_DIR = "http/tests/"
247     HTTP_LOCAL_DIR = "http/tests/local/"
248
249     def is_http_test(self, test_name):
250         return test_name.startswith(self.HTTP_DIR) and not test_name.startswith(self.HTTP_LOCAL_DIR)
251
252     def is_web_platform_test(self, test_name):
253         return test_name.startswith(self.web_platform_test_server_doc_root)
254
255     def test_to_uri(self, test_name):
256         """Convert a test name to a URI."""
257         if self.is_web_platform_test(test_name):
258             return self.web_platform_test_server_base_url + test_name[len(self.web_platform_test_server_doc_root):]
259
260         if not self.is_http_test(test_name):
261             return path.abspath_to_uri(self._port.host.platform, self._port.abspath_for_test(test_name))
262
263         relative_path = test_name[len(self.HTTP_DIR):]
264
265         # TODO(dpranke): remove the SSL reference?
266         if relative_path.startswith("ssl/"):
267             return "https://127.0.0.1:8443/" + relative_path
268         return "http://127.0.0.1:8000/" + relative_path
269
270     def uri_to_test(self, uri):
271         """Return the base layout test name for a given URI.
272
273         This returns the test name for a given URI, e.g., if you passed in
274         "file:///src/LayoutTests/fast/html/keygen.html" it would return
275         "fast/html/keygen.html".
276
277         """
278         if uri.startswith("file:///"):
279             prefix = path.abspath_to_uri(self._port.host.platform, self._port.layout_tests_dir())
280             if not prefix.endswith('/'):
281                 prefix += '/'
282             return uri[len(prefix):]
283         if uri.startswith(self.web_platform_test_server_base_url):
284             return uri.replace(self.web_platform_test_server_base_url, self.web_platform_test_server_doc_root)
285         if uri.startswith("http://"):
286             return uri.replace('http://127.0.0.1:8000/', self.HTTP_DIR)
287         if uri.startswith("https://"):
288             return uri.replace('https://127.0.0.1:8443/', self.HTTP_DIR)
289         raise NotImplementedError('unknown url type: %s' % uri)
290
291     def has_crashed(self):
292         if self._server_process is None:
293             return False
294         if self._crashed_process_name:
295             return True
296         if self._server_process.has_crashed():
297             self._crashed_process_name = self._server_process.name()
298             self._crashed_pid = self._server_process.pid()
299             return True
300         return False
301
302     def start(self, pixel_tests, per_test_args):
303         # FIXME: Callers shouldn't normally call this, since this routine
304         # may not be specifying the correct combination of pixel test and
305         # per_test args.
306         #
307         # The only reason we have this routine at all is so the perftestrunner
308         # can pause before running a test; it might be better to push that
309         # into run_test() directly.
310         if not self._server_process:
311             self._start(pixel_tests, per_test_args)
312             self._run_post_start_tasks()
313
314     def _setup_environ_for_driver(self, environment):
315         environment['DYLD_LIBRARY_PATH'] = str(self._port._build_path())
316         environment['__XPC_DYLD_LIBRARY_PATH'] = environment['DYLD_LIBRARY_PATH']
317         environment['DYLD_FRAMEWORK_PATH'] = str(self._port._build_path())
318         # Use an isolated temp directory that can be deleted after testing (especially important on Mac, as
319         # CoreMedia disk cache is in the temp directory).
320         environment['TMPDIR'] = str(self._driver_tempdir)
321         environment['DIRHELPER_USER_DIR_SUFFIX'] = str(os.path.basename(str(self._driver_tempdir)))
322         # Put certain normally persistent files into the temp directory (e.g. IndexedDB storage).
323         environment['DUMPRENDERTREE_TEMP'] = str(self._driver_tempdir)
324         environment['LOCAL_RESOURCE_ROOT'] = str(self._port.layout_tests_dir())
325         environment['ASAN_OPTIONS'] = "allocator_may_return_null=1"
326         environment['__XPC_ASAN_OPTIONS'] = environment['ASAN_OPTIONS']
327         if 'WEBKIT_OUTPUTDIR' in os.environ:
328             environment['WEBKIT_OUTPUTDIR'] = os.environ['WEBKIT_OUTPUTDIR']
329         if self._profiler:
330             environment = self._profiler.adjusted_environment(environment)
331         return environment
332
333     def _start(self, pixel_tests, per_test_args):
334         self.stop()
335         # Each driver process should be using individual directories under _driver_tempdir (which is deleted when stopping),
336         # however some subsystems on some platforms could end up using process default ones.
337         self._port._clear_global_caches_and_temporary_files()
338         self._driver_tempdir = self._port._driver_tempdir()
339         server_name = self._port.driver_name()
340         environment = self._port.setup_environ_for_server(server_name)
341         environment = self._setup_environ_for_driver(environment)
342         self._crashed_process_name = None
343         self._crashed_pid = None
344         self._server_process = self._port._server_process_constructor(self._port, server_name, self.cmd_line(pixel_tests, per_test_args), environment)
345         self._server_process.start()
346
347     def _run_post_start_tasks(self):
348         # Remote drivers may override this to delay post-start tasks until the server has ack'd.
349         if self._profiler:
350             self._profiler.attach_to_pid(self._pid_on_target())
351
352     def _pid_on_target(self):
353         # Remote drivers will override this method to return the pid on the device.
354         return self._server_process.pid()
355
356     def stop(self):
357         if self._server_process:
358             self._server_process.stop(self._port.driver_stop_timeout())
359             self._server_process = None
360             if self._profiler:
361                 self._profiler.profile_after_exit()
362
363         if self._driver_tempdir:
364             self._port._filesystem.rmtree(str(self._driver_tempdir))
365             self._port.remove_cache_directory(os.path.basename(str(self._driver_tempdir)))
366             self._driver_tempdir = None
367
368     def cmd_line(self, pixel_tests, per_test_args):
369         cmd = self._command_wrapper()
370         cmd.append(self._port._path_to_driver())
371         if self._port.get_option('gc_between_tests'):
372             cmd.append('--gc-between-tests')
373         if self._port.get_option('complex_text'):
374             cmd.append('--complex-text')
375         if self._port.get_option('accelerated_drawing'):
376             cmd.append('--accelerated-drawing')
377         if self._port.get_option('remote_layer_tree'):
378             cmd.append('--remote-layer-tree')
379         if self._port.get_option('threaded'):
380             cmd.append('--threaded')
381         if self._no_timeout:
382             cmd.append('--no-timeout')
383
384         for allowed_host in self._port.allowed_hosts():
385             cmd.append('--allowed-host')
386             cmd.append(allowed_host)
387
388         cmd.extend(self._port.get_option('additional_drt_flag', []))
389         cmd.extend(self._port.additional_drt_flag())
390
391         cmd.extend(per_test_args)
392
393         cmd.append('-')
394         return cmd
395
396     def _check_for_driver_timeout(self, out_line):
397         if out_line == "FAIL: Timed out waiting for notifyDone to be called\n":
398             self._driver_timed_out = True
399
400     def _check_for_address_sanitizer_violation(self, error_line):
401         if "ERROR: AddressSanitizer" in error_line:
402             return True
403
404     def _check_for_driver_crash_or_unresponsiveness(self, error_line):
405         crashed_check = error_line.rstrip('\r\n')
406         if crashed_check == "#CRASHED":
407             self._crashed_process_name = self._server_process.name()
408             self._crashed_pid = self._server_process.pid()
409             return True
410         elif error_line.startswith("#CRASHED - "):
411             match = re.match('#CRASHED - (\S+)', error_line)
412             self._crashed_process_name = match.group(1) if match else 'WebProcess'
413             match = re.search('pid (\d+)', error_line)
414             self._crashed_pid = int(match.group(1)) if match else None
415             _log.debug('%s crash, pid = %s' % (self._crashed_process_name, str(self._crashed_pid)))
416             return True
417         elif error_line.startswith("#PROCESS UNRESPONSIVE - "):
418             match = re.match('#PROCESS UNRESPONSIVE - (\S+)', error_line)
419             child_process_name = match.group(1) if match else 'WebProcess'
420             match = re.search('pid (\d+)', error_line)
421             child_process_pid = int(match.group(1)) if match else None
422             _log.debug('%s is unresponsive, pid = %s' % (child_process_name, str(child_process_pid)))
423             self._driver_timed_out = True
424             if child_process_pid:
425                 self._port.sample_process(child_process_name, child_process_pid)
426             self.error_from_test += error_line
427             return True
428         return self.has_crashed()
429
430     def _command_from_driver_input(self, driver_input):
431         # FIXME: performance tests pass in full URLs instead of test names.
432         if driver_input.test_name.startswith('http://') or driver_input.test_name.startswith('https://')  or driver_input.test_name == ('about:blank'):
433             command = driver_input.test_name
434         elif self.is_web_platform_test(driver_input.test_name) or (self.is_http_test(driver_input.test_name) and (self._port.get_option('webkit_test_runner') or sys.platform == "cygwin")):
435             command = self.test_to_uri(driver_input.test_name)
436         else:
437             command = self._port.abspath_for_test(driver_input.test_name)
438             if sys.platform == 'cygwin':
439                 command = path.cygpath(command)
440
441         assert not driver_input.image_hash or driver_input.should_run_pixel_test
442
443         # ' is the separator between arguments.
444         if self._port.supports_per_test_timeout():
445             command += "'--timeout'%s" % driver_input.timeout
446         if driver_input.should_run_pixel_test:
447             command += "'--pixel-test"
448         if driver_input.image_hash:
449             command += "'" + driver_input.image_hash
450         return command + "\n"
451
452     def _read_first_block(self, deadline):
453         # returns (text_content, audio_content)
454         block = self._read_block(deadline)
455         if block.malloc:
456             self._measurements['Malloc'] = float(block.malloc)
457         if block.js_heap:
458             self._measurements['JSHeap'] = float(block.js_heap)
459         if block.content_type == 'audio/wav':
460             return (None, block.decoded_content)
461         return (block.decoded_content, None)
462
463     def _read_optional_image_block(self, deadline):
464         # returns (image, actual_image_hash)
465         block = self._read_block(deadline, wait_for_stderr_eof=True)
466         if block.content and block.content_type == 'image/png':
467             return (block.decoded_content, block.content_hash)
468         return (None, block.content_hash)
469
470     def _read_header(self, block, line, header_text, header_attr, header_filter=None):
471         if line.startswith(header_text) and getattr(block, header_attr) is None:
472             value = line.split()[1]
473             if header_filter:
474                 value = header_filter(value)
475             setattr(block, header_attr, value)
476             return True
477         return False
478
479     def _process_stdout_line(self, block, line):
480         if (self._read_header(block, line, 'Content-Type: ', 'content_type')
481             or self._read_header(block, line, 'Content-Transfer-Encoding: ', 'encoding')
482             or self._read_header(block, line, 'Content-Length: ', '_content_length', int)
483             or self._read_header(block, line, 'ActualHash: ', 'content_hash')
484             or self._read_header(block, line, 'DumpMalloc: ', 'malloc')
485             or self._read_header(block, line, 'DumpJSHeap: ', 'js_heap')):
486             return
487         # Note, we're not reading ExpectedHash: here, but we could.
488         # If the line wasn't a header, we just append it to the content.
489         block.content += line
490
491     def _strip_eof(self, line):
492         if line and line.endswith("#EOF\n"):
493             return line[:-5], True
494         return line, False
495
496     def _read_block(self, deadline, wait_for_stderr_eof=False):
497         block = ContentBlock()
498         out_seen_eof = False
499         asan_violation_detected = False
500
501         while not self.has_crashed():
502             if out_seen_eof and (self.err_seen_eof or not wait_for_stderr_eof):
503                 break
504
505             if self.err_seen_eof:
506                 out_line = self._server_process.read_stdout_line(deadline)
507                 err_line = None
508             elif out_seen_eof:
509                 out_line = None
510                 err_line = self._server_process.read_stderr_line(deadline)
511             else:
512                 out_line, err_line = self._server_process.read_either_stdout_or_stderr_line(deadline)
513
514             if self._server_process.timed_out or self.has_crashed():
515                 break
516
517             if out_line:
518                 assert not out_seen_eof
519                 out_line, out_seen_eof = self._strip_eof(out_line)
520             if err_line:
521                 assert not self.err_seen_eof
522                 err_line, self.err_seen_eof = self._strip_eof(err_line)
523
524             if out_line:
525                 self._check_for_driver_timeout(out_line)
526                 if out_line[-1] != "\n":
527                     _log.error("Last character read from DRT stdout line was not a newline!  This indicates either a NRWT or DRT bug.")
528                 content_length_before_header_check = block._content_length
529                 self._process_stdout_line(block, out_line)
530                 # FIXME: Unlike HTTP, DRT dumps the content right after printing a Content-Length header.
531                 # Don't wait until we're done with headers, just read the binary blob right now.
532                 if content_length_before_header_check != block._content_length:
533                     block.content = self._server_process.read_stdout(deadline, block._content_length)
534
535             if err_line:
536                 if self._check_for_driver_crash_or_unresponsiveness(err_line):
537                     break
538                 elif self._check_for_address_sanitizer_violation(err_line):
539                     asan_violation_detected = True
540                     self._crash_report_from_driver = ""
541                     # ASan report starts with a nondescript line, we only detect the second line.
542                     end_of_previous_error_line = self.error_from_test.rfind('\n', 0, -1)
543                     if end_of_previous_error_line > 0:
544                         self.error_from_test = self.error_from_test[:end_of_previous_error_line]
545                     else:
546                         self.error_from_test = ""
547                     # Symbolication can take a very long time, give it 10 extra minutes to finish.
548                     # FIXME: This can likely be removed once <rdar://problem/18701447> is fixed.
549                     deadline += 10 * 60 * 1000
550                 if asan_violation_detected:
551                     self._crash_report_from_driver += err_line
552                 else:
553                     self.error_from_test += err_line
554
555         if asan_violation_detected and not self._crashed_process_name:
556             self._crashed_process_name = self._server_process.name()
557             self._crashed_pid = self._server_process.pid()
558
559         block.decode_content()
560         return block
561
562     @staticmethod
563     def check_driver(port):
564         # This checks if the required system dependencies for the driver are met.
565         # Since this is the generic class implementation, just return True.
566         return True
567
568
569 class IOSSimulatorDriver(Driver):
570     def cmd_line(self, pixel_tests, per_test_args):
571         cmd = super(IOSSimulatorDriver, self).cmd_line(pixel_tests, per_test_args)
572         relay_tool = self._port.relay_path
573         dump_tool = cmd[0]
574         dump_tool_args = cmd[1:]
575         product_dir = self._port._build_path()
576         relay_args = [
577             '-runtime', self._port.simulator_runtime.identifier,
578             '-deviceType', self._port.simulator_device_type.identifier,
579             '-suffix', str(self._worker_number),
580             '-productDir', product_dir,
581             '-app', dump_tool,
582         ]
583         return [relay_tool] + relay_args + ['--'] + dump_tool_args
584
585     def _setup_environ_for_driver(self, environment):
586         environment['DEVELOPER_DIR'] = self._port.developer_dir
587         return super(IOSSimulatorDriver, self)._setup_environ_for_driver(environment)
588
589
590 class ContentBlock(object):
591     def __init__(self):
592         self.content_type = None
593         self.encoding = None
594         self.content_hash = None
595         self._content_length = None
596         # Content is treated as binary data even though the text output is usually UTF-8.
597         self.content = str()  # FIXME: Should be bytearray() once we require Python 2.6.
598         self.decoded_content = None
599         self.malloc = None
600         self.js_heap = None
601
602     def decode_content(self):
603         if self.encoding == 'base64' and self.content is not None:
604             self.decoded_content = base64.b64decode(self.content)
605         else:
606             self.decoded_content = self.content
607
608 class DriverProxy(object):
609     """A wrapper for managing two Driver instances, one with pixel tests and
610     one without. This allows us to handle plain text tests and ref tests with a
611     single driver."""
612
613     def __init__(self, port, worker_number, driver_instance_constructor, pixel_tests, no_timeout):
614         self._port = port
615         self._worker_number = worker_number
616         self._driver_instance_constructor = driver_instance_constructor
617         self._no_timeout = no_timeout
618
619         # FIXME: We shouldn't need to create a driver until we actually run a test.
620         self._driver = self._make_driver(pixel_tests)
621         self._driver_cmd_line = None
622
623     def _make_driver(self, pixel_tests):
624         return self._driver_instance_constructor(self._port, self._worker_number, pixel_tests, self._no_timeout)
625
626     # FIXME: this should be a @classmethod (or implemented on Port instead).
627     def is_http_test(self, test_name):
628         return self._driver.is_http_test(test_name)
629
630     # FIXME: this should be a @classmethod (or implemented on Port instead).
631     def test_to_uri(self, test_name):
632         return self._driver.test_to_uri(test_name)
633
634     # FIXME: this should be a @classmethod (or implemented on Port instead).
635     def uri_to_test(self, uri):
636         return self._driver.uri_to_test(uri)
637
638     def run_test(self, driver_input, stop_when_done):
639         pixel_tests_needed = driver_input.should_run_pixel_test
640         cmd_line_key = self._cmd_line_as_key(pixel_tests_needed, driver_input.args)
641         if cmd_line_key != self._driver_cmd_line:
642             self._driver.stop()
643             self._driver = self._make_driver(pixel_tests_needed)
644             self._driver_cmd_line = cmd_line_key
645
646         return self._driver.run_test(driver_input, stop_when_done)
647
648     def has_crashed(self):
649         return self._driver.has_crashed()
650
651     def stop(self):
652         self._driver.stop()
653
654     # FIXME: this should be a @classmethod (or implemented on Port instead).
655     def cmd_line(self, pixel_tests=None, per_test_args=None):
656         return self._driver.cmd_line(pixel_tests, per_test_args or [])
657
658     def _cmd_line_as_key(self, pixel_tests, per_test_args):
659         return ' '.join(self.cmd_line(pixel_tests, per_test_args))