e369ee4e0fd139eca35e0c7052774249eaff25f4
[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['__XPC_DUMPRENDERTREE_TEMP'] = environment['DUMPRENDERTREE_TEMP']
325         environment['LOCAL_RESOURCE_ROOT'] = str(self._port.layout_tests_dir())
326         environment['ASAN_OPTIONS'] = "allocator_may_return_null=1"
327         environment['__XPC_ASAN_OPTIONS'] = environment['ASAN_OPTIONS']
328         if 'WEBKIT_OUTPUTDIR' in os.environ:
329             environment['WEBKIT_OUTPUTDIR'] = os.environ['WEBKIT_OUTPUTDIR']
330         if self._profiler:
331             environment = self._profiler.adjusted_environment(environment)
332         return environment
333
334     def _start(self, pixel_tests, per_test_args):
335         self.stop()
336         # Each driver process should be using individual directories under _driver_tempdir (which is deleted when stopping),
337         # however some subsystems on some platforms could end up using process default ones.
338         self._port._clear_global_caches_and_temporary_files()
339         self._driver_tempdir = self._port._driver_tempdir()
340         server_name = self._port.driver_name()
341         environment = self._port.setup_environ_for_server(server_name)
342         environment = self._setup_environ_for_driver(environment)
343         self._crashed_process_name = None
344         self._crashed_pid = None
345         self._server_process = self._port._server_process_constructor(self._port, server_name, self.cmd_line(pixel_tests, per_test_args), environment)
346         self._server_process.start()
347
348     def _run_post_start_tasks(self):
349         # Remote drivers may override this to delay post-start tasks until the server has ack'd.
350         if self._profiler:
351             self._profiler.attach_to_pid(self._pid_on_target())
352
353     def _pid_on_target(self):
354         # Remote drivers will override this method to return the pid on the device.
355         return self._server_process.pid()
356
357     def stop(self):
358         if self._server_process:
359             self._server_process.stop(self._port.driver_stop_timeout())
360             self._server_process = None
361             if self._profiler:
362                 self._profiler.profile_after_exit()
363
364         if self._driver_tempdir:
365             self._port._filesystem.rmtree(str(self._driver_tempdir))
366             self._port.remove_cache_directory(os.path.basename(str(self._driver_tempdir)))
367             self._driver_tempdir = None
368
369     def cmd_line(self, pixel_tests, per_test_args):
370         cmd = self._command_wrapper()
371         cmd.append(self._port._path_to_driver())
372         if self._port.get_option('gc_between_tests'):
373             cmd.append('--gc-between-tests')
374         if self._port.get_option('complex_text'):
375             cmd.append('--complex-text')
376         if self._port.get_option('accelerated_drawing'):
377             cmd.append('--accelerated-drawing')
378         if self._port.get_option('remote_layer_tree'):
379             cmd.append('--remote-layer-tree')
380         if self._port.get_option('threaded'):
381             cmd.append('--threaded')
382         if self._no_timeout:
383             cmd.append('--no-timeout')
384
385         for allowed_host in self._port.allowed_hosts():
386             cmd.append('--allowed-host')
387             cmd.append(allowed_host)
388
389         cmd.extend(self._port.get_option('additional_drt_flag', []))
390         cmd.extend(self._port.additional_drt_flag())
391
392         cmd.extend(per_test_args)
393
394         cmd.append('-')
395         return cmd
396
397     def _check_for_driver_timeout(self, out_line):
398         if out_line == "FAIL: Timed out waiting for notifyDone to be called\n":
399             self._driver_timed_out = True
400
401     def _check_for_address_sanitizer_violation(self, error_line):
402         if "ERROR: AddressSanitizer" in error_line:
403             return True
404
405     def _check_for_driver_crash_or_unresponsiveness(self, error_line):
406         crashed_check = error_line.rstrip('\r\n')
407         if crashed_check == "#CRASHED":
408             self._crashed_process_name = self._server_process.name()
409             self._crashed_pid = self._server_process.pid()
410             return True
411         elif error_line.startswith("#CRASHED - "):
412             match = re.match('#CRASHED - (\S+)', error_line)
413             self._crashed_process_name = match.group(1) if match else 'WebProcess'
414             match = re.search('pid (\d+)', error_line)
415             self._crashed_pid = int(match.group(1)) if match else None
416             _log.debug('%s crash, pid = %s' % (self._crashed_process_name, str(self._crashed_pid)))
417             return True
418         elif error_line.startswith("#PROCESS UNRESPONSIVE - "):
419             match = re.match('#PROCESS UNRESPONSIVE - (\S+)', error_line)
420             child_process_name = match.group(1) if match else 'WebProcess'
421             match = re.search('pid (\d+)', error_line)
422             child_process_pid = int(match.group(1)) if match else None
423             _log.debug('%s is unresponsive, pid = %s' % (child_process_name, str(child_process_pid)))
424             self._driver_timed_out = True
425             if child_process_pid:
426                 self._port.sample_process(child_process_name, child_process_pid)
427             self.error_from_test += error_line
428             return True
429         return self.has_crashed()
430
431     def _command_from_driver_input(self, driver_input):
432         # FIXME: performance tests pass in full URLs instead of test names.
433         if driver_input.test_name.startswith('http://') or driver_input.test_name.startswith('https://')  or driver_input.test_name == ('about:blank'):
434             command = driver_input.test_name
435         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")):
436             command = self.test_to_uri(driver_input.test_name)
437         else:
438             command = self._port.abspath_for_test(driver_input.test_name)
439             if sys.platform == 'cygwin':
440                 command = path.cygpath(command)
441
442         assert not driver_input.image_hash or driver_input.should_run_pixel_test
443
444         # ' is the separator between arguments.
445         if self._port.supports_per_test_timeout():
446             command += "'--timeout'%s" % driver_input.timeout
447         if driver_input.should_run_pixel_test:
448             command += "'--pixel-test"
449         if driver_input.image_hash:
450             command += "'" + driver_input.image_hash
451         return command + "\n"
452
453     def _read_first_block(self, deadline):
454         # returns (text_content, audio_content)
455         block = self._read_block(deadline)
456         if block.malloc:
457             self._measurements['Malloc'] = float(block.malloc)
458         if block.js_heap:
459             self._measurements['JSHeap'] = float(block.js_heap)
460         if block.content_type == 'audio/wav':
461             return (None, block.decoded_content)
462         return (block.decoded_content, None)
463
464     def _read_optional_image_block(self, deadline):
465         # returns (image, actual_image_hash)
466         block = self._read_block(deadline, wait_for_stderr_eof=True)
467         if block.content and block.content_type == 'image/png':
468             return (block.decoded_content, block.content_hash)
469         return (None, block.content_hash)
470
471     def _read_header(self, block, line, header_text, header_attr, header_filter=None):
472         if line.startswith(header_text) and getattr(block, header_attr) is None:
473             value = line.split()[1]
474             if header_filter:
475                 value = header_filter(value)
476             setattr(block, header_attr, value)
477             return True
478         return False
479
480     def _process_stdout_line(self, block, line):
481         if (self._read_header(block, line, 'Content-Type: ', 'content_type')
482             or self._read_header(block, line, 'Content-Transfer-Encoding: ', 'encoding')
483             or self._read_header(block, line, 'Content-Length: ', '_content_length', int)
484             or self._read_header(block, line, 'ActualHash: ', 'content_hash')
485             or self._read_header(block, line, 'DumpMalloc: ', 'malloc')
486             or self._read_header(block, line, 'DumpJSHeap: ', 'js_heap')):
487             return
488         # Note, we're not reading ExpectedHash: here, but we could.
489         # If the line wasn't a header, we just append it to the content.
490         block.content += line
491
492     def _strip_eof(self, line):
493         if line and line.endswith("#EOF\n"):
494             return line[:-5], True
495         return line, False
496
497     def _read_block(self, deadline, wait_for_stderr_eof=False):
498         block = ContentBlock()
499         out_seen_eof = False
500         asan_violation_detected = False
501
502         while not self.has_crashed():
503             if out_seen_eof and (self.err_seen_eof or not wait_for_stderr_eof):
504                 break
505
506             if self.err_seen_eof:
507                 out_line = self._server_process.read_stdout_line(deadline)
508                 err_line = None
509             elif out_seen_eof:
510                 out_line = None
511                 err_line = self._server_process.read_stderr_line(deadline)
512             else:
513                 out_line, err_line = self._server_process.read_either_stdout_or_stderr_line(deadline)
514
515             if self._server_process.timed_out or self.has_crashed():
516                 break
517
518             if out_line:
519                 assert not out_seen_eof
520                 out_line, out_seen_eof = self._strip_eof(out_line)
521             if err_line:
522                 assert not self.err_seen_eof
523                 err_line, self.err_seen_eof = self._strip_eof(err_line)
524
525             if out_line:
526                 self._check_for_driver_timeout(out_line)
527                 if out_line[-1] != "\n":
528                     _log.error("Last character read from DRT stdout line was not a newline!  This indicates either a NRWT or DRT bug.")
529                 content_length_before_header_check = block._content_length
530                 self._process_stdout_line(block, out_line)
531                 # FIXME: Unlike HTTP, DRT dumps the content right after printing a Content-Length header.
532                 # Don't wait until we're done with headers, just read the binary blob right now.
533                 if content_length_before_header_check != block._content_length:
534                     block.content = self._server_process.read_stdout(deadline, block._content_length)
535
536             if err_line:
537                 if self._check_for_driver_crash_or_unresponsiveness(err_line):
538                     break
539                 elif self._check_for_address_sanitizer_violation(err_line):
540                     asan_violation_detected = True
541                     self._crash_report_from_driver = ""
542                     # ASan report starts with a nondescript line, we only detect the second line.
543                     end_of_previous_error_line = self.error_from_test.rfind('\n', 0, -1)
544                     if end_of_previous_error_line > 0:
545                         self.error_from_test = self.error_from_test[:end_of_previous_error_line]
546                     else:
547                         self.error_from_test = ""
548                     # Symbolication can take a very long time, give it 10 extra minutes to finish.
549                     # FIXME: This can likely be removed once <rdar://problem/18701447> is fixed.
550                     deadline += 10 * 60 * 1000
551                 if asan_violation_detected:
552                     self._crash_report_from_driver += err_line
553                 else:
554                     self.error_from_test += err_line
555
556         if asan_violation_detected and not self._crashed_process_name:
557             self._crashed_process_name = self._server_process.name()
558             self._crashed_pid = self._server_process.pid()
559
560         block.decode_content()
561         return block
562
563     @staticmethod
564     def check_driver(port):
565         # This checks if the required system dependencies for the driver are met.
566         # Since this is the generic class implementation, just return True.
567         return True
568
569
570 class IOSSimulatorDriver(Driver):
571     def cmd_line(self, pixel_tests, per_test_args):
572         cmd = super(IOSSimulatorDriver, self).cmd_line(pixel_tests, per_test_args)
573         relay_tool = self._port.relay_path
574         dump_tool = cmd[0]
575         dump_tool_args = cmd[1:]
576         product_dir = self._port._build_path()
577         relay_args = [
578             '-runtime', self._port.simulator_runtime.identifier,
579             '-deviceType', self._port.simulator_device_type.identifier,
580             '-suffix', str(self._worker_number),
581             '-productDir', product_dir,
582             '-app', dump_tool,
583         ]
584         return [relay_tool] + relay_args + ['--'] + dump_tool_args
585
586     def _setup_environ_for_driver(self, environment):
587         environment['DEVELOPER_DIR'] = self._port.developer_dir
588         return super(IOSSimulatorDriver, self)._setup_environ_for_driver(environment)
589
590
591 class ContentBlock(object):
592     def __init__(self):
593         self.content_type = None
594         self.encoding = None
595         self.content_hash = None
596         self._content_length = None
597         # Content is treated as binary data even though the text output is usually UTF-8.
598         self.content = str()  # FIXME: Should be bytearray() once we require Python 2.6.
599         self.decoded_content = None
600         self.malloc = None
601         self.js_heap = None
602
603     def decode_content(self):
604         if self.encoding == 'base64' and self.content is not None:
605             self.decoded_content = base64.b64decode(self.content)
606         else:
607             self.decoded_content = self.content
608
609 class DriverProxy(object):
610     """A wrapper for managing two Driver instances, one with pixel tests and
611     one without. This allows us to handle plain text tests and ref tests with a
612     single driver."""
613
614     def __init__(self, port, worker_number, driver_instance_constructor, pixel_tests, no_timeout):
615         self._port = port
616         self._worker_number = worker_number
617         self._driver_instance_constructor = driver_instance_constructor
618         self._no_timeout = no_timeout
619
620         # FIXME: We shouldn't need to create a driver until we actually run a test.
621         self._driver = self._make_driver(pixel_tests)
622         self._driver_cmd_line = None
623
624     def _make_driver(self, pixel_tests):
625         return self._driver_instance_constructor(self._port, self._worker_number, pixel_tests, self._no_timeout)
626
627     # FIXME: this should be a @classmethod (or implemented on Port instead).
628     def is_http_test(self, test_name):
629         return self._driver.is_http_test(test_name)
630
631     # FIXME: this should be a @classmethod (or implemented on Port instead).
632     def test_to_uri(self, test_name):
633         return self._driver.test_to_uri(test_name)
634
635     # FIXME: this should be a @classmethod (or implemented on Port instead).
636     def uri_to_test(self, uri):
637         return self._driver.uri_to_test(uri)
638
639     def run_test(self, driver_input, stop_when_done):
640         pixel_tests_needed = driver_input.should_run_pixel_test
641         cmd_line_key = self._cmd_line_as_key(pixel_tests_needed, driver_input.args)
642         if cmd_line_key != self._driver_cmd_line:
643             self._driver.stop()
644             self._driver = self._make_driver(pixel_tests_needed)
645             self._driver_cmd_line = cmd_line_key
646
647         return self._driver.run_test(driver_input, stop_when_done)
648
649     def has_crashed(self):
650         return self._driver.has_crashed()
651
652     def stop(self):
653         self._driver.stop()
654
655     # FIXME: this should be a @classmethod (or implemented on Port instead).
656     def cmd_line(self, pixel_tests=None, per_test_args=None):
657         return self._driver.cmd_line(pixel_tests, per_test_args or [])
658
659     def _cmd_line_as_key(self, pixel_tests, per_test_args):
660         return ' '.join(self.cmd_line(pixel_tests, per_test_args))