Tests don't work in iOS Simulator when ASan is enabled
[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         self._driver_user_directory_suffix = None
136         self._driver_user_cache_directory = None
137
138         # WebKitTestRunner/LayoutTestRelay can report back subprocess crashes by printing
139         # "#CRASHED - PROCESSNAME".  Since those can happen at any time and ServerProcess
140         # won't be aware of them (since the actual tool didn't crash, just a subprocess)
141         # we record the crashed subprocess name here.
142         self._crashed_process_name = None
143         self._crashed_pid = None
144
145         self._driver_timed_out = False
146
147         # stderr reading is scoped on a per-test (not per-block) basis, so we store the accumulated
148         # stderr output, as well as if we've seen #EOF on this driver instance.
149         # FIXME: We should probably remove _read_first_block and _read_optional_image_block and
150         # instead scope these locally in run_test.
151         self.error_from_test = str()
152         self.err_seen_eof = False
153         self._server_process = None
154
155         self._measurements = {}
156         if self._port.get_option("profile"):
157             profiler_name = self._port.get_option("profiler")
158             self._profiler = ProfilerFactory.create_profiler(self._port.host,
159                 self._port._path_to_driver(), self._port.results_directory(), profiler_name)
160         else:
161             self._profiler = None
162
163         self.web_platform_test_server_doc_root = self._port.web_platform_test_server_doc_root()
164         self.web_platform_test_server_base_url = self._port.web_platform_test_server_base_url()
165
166     def __del__(self):
167         self.stop()
168
169     def run_test(self, driver_input, stop_when_done):
170         """Run a single test and return the results.
171
172         Note that it is okay if a test times out or crashes and leaves
173         the driver in an indeterminate state. The upper layers of the program
174         are responsible for cleaning up and ensuring things are okay.
175
176         Returns a DriverOutput object.
177         """
178         start_time = time.time()
179         self.start(driver_input.should_run_pixel_test, driver_input.args)
180         test_begin_time = time.time()
181         self._driver_timed_out = False
182         self._crash_report_from_driver = None
183         self.error_from_test = str()
184         self.err_seen_eof = False
185
186         command = self._command_from_driver_input(driver_input)
187
188         # Certain timeouts are detected by the tool itself; tool detection is better,
189         # because results contain partial output in this case. Make script timeout longer
190         # by 5 seconds to avoid racing for which timeout is detected first.
191         # FIXME: It's not the job of the driver to decide what the timeouts should be.
192         # Move the additional timeout to driver_input.
193         if self._no_timeout:
194             deadline = test_begin_time + 60 * 60 * 24 * 7  # 7 days. Using sys.maxint causes a hang.
195         else:
196             deadline = test_begin_time + int(driver_input.timeout) / 1000.0 + 5
197
198         self._server_process.write(command)
199         text, audio = self._read_first_block(deadline)  # First block is either text or audio
200         image, actual_image_hash = self._read_optional_image_block(deadline)  # The second (optional) block is image data.
201
202         crashed = self.has_crashed()
203         timed_out = self._server_process.timed_out
204         driver_timed_out = self._driver_timed_out
205         pid = self._server_process.pid()
206
207         if stop_when_done or crashed or timed_out:
208             # We call stop() even if we crashed or timed out in order to get any remaining stdout/stderr output.
209             # In the timeout case, we kill the hung process as well.
210             out, err = self._server_process.stop(self._port.driver_stop_timeout() if stop_when_done else 0.0)
211             if out:
212                 text += out
213             if err:
214                 self.error_from_test += err
215             self._server_process = None
216
217         crash_log = None
218         if self._crash_report_from_driver:
219             crash_log = self._crash_report_from_driver
220         elif crashed:
221             self.error_from_test, crash_log = self._get_crash_log(text, self.error_from_test, newer_than=start_time)
222             # If we don't find a crash log use a placeholder error message instead.
223             if not crash_log:
224                 pid_str = str(self._crashed_pid) if self._crashed_pid else "unknown pid"
225                 crash_log = 'No crash log found for %s:%s.\n' % (self._crashed_process_name, pid_str)
226
227                 # Print stdout and stderr to the placeholder crash log; we want as much context as possible.
228                 if self.error_from_test:
229                     crash_log += '\nstdout:\n%s\nstderr:\n%s\n' % (text, self.error_from_test)
230
231         return DriverOutput(text, image, actual_image_hash, audio,
232             crash=crashed, test_time=time.time() - test_begin_time, measurements=self._measurements,
233             timeout=timed_out or driver_timed_out, error=self.error_from_test,
234             crashed_process_name=self._crashed_process_name,
235             crashed_pid=self._crashed_pid, crash_log=crash_log, pid=pid)
236
237     def _get_crash_log(self, stdout, stderr, newer_than):
238         return self._port._get_crash_log(self._crashed_process_name, self._crashed_pid, stdout, stderr, newer_than)
239
240     def _command_wrapper(self):
241         # Hook for injecting valgrind or other runtime instrumentation, used by e.g. tools/valgrind/valgrind_tests.py.
242         wrapper_arguments = []
243         if self._profiler:
244             wrapper_arguments = self._profiler.wrapper_arguments()
245         if self._port.get_option('wrapper'):
246             return shlex.split(self._port.get_option('wrapper')) + wrapper_arguments
247         return wrapper_arguments
248
249     HTTP_DIR = "http/tests/"
250     HTTP_LOCAL_DIR = "http/tests/local/"
251
252     def is_http_test(self, test_name):
253         return test_name.startswith(self.HTTP_DIR) and not test_name.startswith(self.HTTP_LOCAL_DIR)
254
255     def is_web_platform_test(self, test_name):
256         return test_name.startswith(self.web_platform_test_server_doc_root)
257
258     def test_to_uri(self, test_name):
259         """Convert a test name to a URI."""
260         if self.is_web_platform_test(test_name):
261             return self.web_platform_test_server_base_url + test_name[len(self.web_platform_test_server_doc_root):]
262
263         if not self.is_http_test(test_name):
264             return path.abspath_to_uri(self._port.host.platform, self._port.abspath_for_test(test_name))
265
266         relative_path = test_name[len(self.HTTP_DIR):]
267
268         # TODO(dpranke): remove the SSL reference?
269         if relative_path.startswith("ssl/"):
270             return "https://127.0.0.1:8443/" + relative_path
271         return "http://127.0.0.1:8000/" + relative_path
272
273     def uri_to_test(self, uri):
274         """Return the base layout test name for a given URI.
275
276         This returns the test name for a given URI, e.g., if you passed in
277         "file:///src/LayoutTests/fast/html/keygen.html" it would return
278         "fast/html/keygen.html".
279
280         """
281         if uri.startswith("file:///"):
282             prefix = path.abspath_to_uri(self._port.host.platform, self._port.layout_tests_dir())
283             if not prefix.endswith('/'):
284                 prefix += '/'
285             return uri[len(prefix):]
286         if uri.startswith(self.web_platform_test_server_base_url):
287             return uri.replace(self.web_platform_test_server_base_url, self.web_platform_test_server_doc_root)
288         if uri.startswith("http://"):
289             return uri.replace('http://127.0.0.1:8000/', self.HTTP_DIR)
290         if uri.startswith("https://"):
291             return uri.replace('https://127.0.0.1:8443/', self.HTTP_DIR)
292         raise NotImplementedError('unknown url type: %s' % uri)
293
294     def has_crashed(self):
295         if self._server_process is None:
296             return False
297         if self._crashed_process_name:
298             return True
299         if self._server_process.has_crashed():
300             self._crashed_process_name = self._server_process.name()
301             self._crashed_pid = self._server_process.pid()
302             return True
303         return False
304
305     def start(self, pixel_tests, per_test_args):
306         # FIXME: Callers shouldn't normally call this, since this routine
307         # may not be specifying the correct combination of pixel test and
308         # per_test args.
309         #
310         # The only reason we have this routine at all is so the perftestrunner
311         # can pause before running a test; it might be better to push that
312         # into run_test() directly.
313         if not self._server_process:
314             self._start(pixel_tests, per_test_args)
315             self._run_post_start_tasks()
316
317     def _append_environment_variable_path(self, environment, variable, path):
318         if variable in environment:
319             environment[variable] = environment[variable] + os.pathsep + path
320         else:
321             environment[variable] = path
322
323     def _setup_environ_for_driver(self, environment):
324         build_root_path = str(self._port._build_path())
325         # FIXME: DYLD_* variables should be Mac-only. Even iOS Simulator doesn't need them, as LayoutTestRelay is a host binary.
326         self._append_environment_variable_path(environment, 'DYLD_LIBRARY_PATH', build_root_path)
327         self._append_environment_variable_path(environment, '__XPC_DYLD_LIBRARY_PATH', build_root_path)
328         self._append_environment_variable_path(environment, 'DYLD_FRAMEWORK_PATH', build_root_path)
329         self._append_environment_variable_path(environment, '__XPC_DYLD_FRAMEWORK_PATH', build_root_path)
330         # Use an isolated temp directory that can be deleted after testing (especially important on Mac, as
331         # CoreMedia disk cache is in the temp directory).
332         environment['TMPDIR'] = str(self._driver_tempdir)
333         environment['DIRHELPER_USER_DIR_SUFFIX'] = self._driver_user_directory_suffix
334         # Put certain normally persistent files into the temp directory (e.g. IndexedDB storage).
335         environment['DUMPRENDERTREE_TEMP'] = str(self._driver_tempdir)
336         environment['LOCAL_RESOURCE_ROOT'] = str(self._port.layout_tests_dir())
337         environment['ASAN_OPTIONS'] = "allocator_may_return_null=1"
338         environment['__XPC_ASAN_OPTIONS'] = environment['ASAN_OPTIONS']
339         if 'WEBKIT_OUTPUTDIR' in os.environ:
340             environment['WEBKIT_OUTPUTDIR'] = os.environ['WEBKIT_OUTPUTDIR']
341         if self._profiler:
342             environment = self._profiler.adjusted_environment(environment)
343         return environment
344
345     def _start(self, pixel_tests, per_test_args):
346         self.stop()
347         # Each driver process should be using individual directories under _driver_tempdir (which is deleted when stopping),
348         # however some subsystems on some platforms could end up using process default ones.
349         self._port._clear_global_caches_and_temporary_files()
350         self._driver_tempdir = self._port._driver_tempdir()
351         self._driver_user_directory_suffix = os.path.basename(str(self._driver_tempdir))
352         user_cache_directory = self._port._path_to_user_cache_directory(self._driver_user_directory_suffix)
353         if user_cache_directory:
354             self._port._filesystem.maybe_make_directory(user_cache_directory)
355             self._driver_user_cache_directory = user_cache_directory
356         server_name = self._port.driver_name()
357         environment = self._port.setup_environ_for_server(server_name)
358         environment = self._setup_environ_for_driver(environment)
359         self._crashed_process_name = None
360         self._crashed_pid = None
361         self._server_process = self._port._server_process_constructor(self._port, server_name, self.cmd_line(pixel_tests, per_test_args), environment)
362         self._server_process.start()
363
364     def _run_post_start_tasks(self):
365         # Remote drivers may override this to delay post-start tasks until the server has ack'd.
366         if self._profiler:
367             self._profiler.attach_to_pid(self._pid_on_target())
368
369     def _pid_on_target(self):
370         # Remote drivers will override this method to return the pid on the device.
371         return self._server_process.pid()
372
373     def stop(self):
374         if self._server_process:
375             self._server_process.stop(self._port.driver_stop_timeout())
376             self._server_process = None
377             if self._profiler:
378                 self._profiler.profile_after_exit()
379
380         if self._driver_tempdir:
381             self._port._filesystem.rmtree(str(self._driver_tempdir))
382             self._driver_tempdir = None
383         if self._driver_user_cache_directory:
384             self._port._filesystem.rmtree(self._driver_user_cache_directory)
385             self._driver_user_cache_directory = None
386
387     def cmd_line(self, pixel_tests, per_test_args):
388         cmd = self._command_wrapper()
389         cmd.append(self._port._path_to_driver())
390         if self._port.get_option('gc_between_tests'):
391             cmd.append('--gc-between-tests')
392         if self._port.get_option('complex_text'):
393             cmd.append('--complex-text')
394         if self._port.get_option('accelerated_drawing'):
395             cmd.append('--accelerated-drawing')
396         if self._port.get_option('remote_layer_tree'):
397             cmd.append('--remote-layer-tree')
398         if self._port.get_option('threaded'):
399             cmd.append('--threaded')
400         if self._no_timeout:
401             cmd.append('--no-timeout')
402
403         for allowed_host in self._port.allowed_hosts():
404             cmd.append('--allowed-host')
405             cmd.append(allowed_host)
406
407         cmd.extend(self._port.get_option('additional_drt_flag', []))
408         cmd.extend(self._port.additional_drt_flag())
409
410         cmd.extend(per_test_args)
411
412         cmd.append('-')
413         return cmd
414
415     def _check_for_driver_timeout(self, out_line):
416         if out_line == "FAIL: Timed out waiting for notifyDone to be called\n":
417             self._driver_timed_out = True
418
419     def _check_for_address_sanitizer_violation(self, error_line):
420         if "ERROR: AddressSanitizer" in error_line:
421             return True
422
423     def _check_for_driver_crash_or_unresponsiveness(self, error_line):
424         crashed_check = error_line.rstrip('\r\n')
425         if crashed_check == "#CRASHED":
426             self._crashed_process_name = self._server_process.name()
427             self._crashed_pid = self._server_process.pid()
428             return True
429         elif error_line.startswith("#CRASHED - "):
430             match = re.match('#CRASHED - (\S+)', error_line)
431             self._crashed_process_name = match.group(1) if match else 'WebProcess'
432             match = re.search('pid (\d+)', error_line)
433             self._crashed_pid = int(match.group(1)) if match else None
434             _log.debug('%s crash, pid = %s' % (self._crashed_process_name, str(self._crashed_pid)))
435             return True
436         elif error_line.startswith("#PROCESS UNRESPONSIVE - "):
437             match = re.match('#PROCESS UNRESPONSIVE - (\S+)', error_line)
438             child_process_name = match.group(1) if match else 'WebProcess'
439             match = re.search('pid (\d+)', error_line)
440             child_process_pid = int(match.group(1)) if match else None
441             _log.debug('%s is unresponsive, pid = %s' % (child_process_name, str(child_process_pid)))
442             self._driver_timed_out = True
443             if child_process_pid:
444                 self._port.sample_process(child_process_name, child_process_pid)
445             self.error_from_test += error_line
446             return True
447         return self.has_crashed()
448
449     def _command_from_driver_input(self, driver_input):
450         # FIXME: performance tests pass in full URLs instead of test names.
451         if driver_input.test_name.startswith('http://') or driver_input.test_name.startswith('https://')  or driver_input.test_name == ('about:blank'):
452             command = driver_input.test_name
453         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")):
454             command = self.test_to_uri(driver_input.test_name)
455         else:
456             command = self._port.abspath_for_test(driver_input.test_name)
457             if sys.platform == 'cygwin':
458                 command = path.cygpath(command)
459
460         assert not driver_input.image_hash or driver_input.should_run_pixel_test
461
462         # ' is the separator between arguments.
463         if self._port.supports_per_test_timeout():
464             command += "'--timeout'%s" % driver_input.timeout
465         if driver_input.should_run_pixel_test:
466             command += "'--pixel-test"
467         if driver_input.image_hash:
468             command += "'" + driver_input.image_hash
469         return command + "\n"
470
471     def _read_first_block(self, deadline):
472         # returns (text_content, audio_content)
473         block = self._read_block(deadline)
474         if block.malloc:
475             self._measurements['Malloc'] = float(block.malloc)
476         if block.js_heap:
477             self._measurements['JSHeap'] = float(block.js_heap)
478         if block.content_type == 'audio/wav':
479             return (None, block.decoded_content)
480         return (block.decoded_content, None)
481
482     def _read_optional_image_block(self, deadline):
483         # returns (image, actual_image_hash)
484         block = self._read_block(deadline, wait_for_stderr_eof=True)
485         if block.content and block.content_type == 'image/png':
486             return (block.decoded_content, block.content_hash)
487         return (None, block.content_hash)
488
489     def _read_header(self, block, line, header_text, header_attr, header_filter=None):
490         if line.startswith(header_text) and getattr(block, header_attr) is None:
491             value = line.split()[1]
492             if header_filter:
493                 value = header_filter(value)
494             setattr(block, header_attr, value)
495             return True
496         return False
497
498     def _process_stdout_line(self, block, line):
499         if (self._read_header(block, line, 'Content-Type: ', 'content_type')
500             or self._read_header(block, line, 'Content-Transfer-Encoding: ', 'encoding')
501             or self._read_header(block, line, 'Content-Length: ', '_content_length', int)
502             or self._read_header(block, line, 'ActualHash: ', 'content_hash')
503             or self._read_header(block, line, 'DumpMalloc: ', 'malloc')
504             or self._read_header(block, line, 'DumpJSHeap: ', 'js_heap')):
505             return
506         # Note, we're not reading ExpectedHash: here, but we could.
507         # If the line wasn't a header, we just append it to the content.
508         block.content += line
509
510     def _strip_eof(self, line):
511         if line and line.endswith("#EOF\n"):
512             return line[:-5], True
513         return line, False
514
515     def _read_block(self, deadline, wait_for_stderr_eof=False):
516         block = ContentBlock()
517         out_seen_eof = False
518         asan_violation_detected = False
519
520         while not self.has_crashed():
521             if out_seen_eof and (self.err_seen_eof or not wait_for_stderr_eof):
522                 break
523
524             if self.err_seen_eof:
525                 out_line = self._server_process.read_stdout_line(deadline)
526                 err_line = None
527             elif out_seen_eof:
528                 out_line = None
529                 err_line = self._server_process.read_stderr_line(deadline)
530             else:
531                 out_line, err_line = self._server_process.read_either_stdout_or_stderr_line(deadline)
532
533             if self._server_process.timed_out or self.has_crashed():
534                 break
535
536             if out_line:
537                 assert not out_seen_eof
538                 out_line, out_seen_eof = self._strip_eof(out_line)
539             if err_line:
540                 assert not self.err_seen_eof
541                 err_line, self.err_seen_eof = self._strip_eof(err_line)
542
543             if out_line:
544                 self._check_for_driver_timeout(out_line)
545                 if out_line[-1] != "\n":
546                     _log.error("Last character read from DRT stdout line was not a newline!  This indicates either a NRWT or DRT bug.")
547                 content_length_before_header_check = block._content_length
548                 self._process_stdout_line(block, out_line)
549                 # FIXME: Unlike HTTP, DRT dumps the content right after printing a Content-Length header.
550                 # Don't wait until we're done with headers, just read the binary blob right now.
551                 if content_length_before_header_check != block._content_length:
552                     block.content = self._server_process.read_stdout(deadline, block._content_length)
553
554             if err_line:
555                 if self._check_for_driver_crash_or_unresponsiveness(err_line):
556                     break
557                 elif self._check_for_address_sanitizer_violation(err_line):
558                     asan_violation_detected = True
559                     self._crash_report_from_driver = ""
560                     # ASan report starts with a nondescript line, we only detect the second line.
561                     end_of_previous_error_line = self.error_from_test.rfind('\n', 0, -1)
562                     if end_of_previous_error_line > 0:
563                         self.error_from_test = self.error_from_test[:end_of_previous_error_line]
564                     else:
565                         self.error_from_test = ""
566                     # Symbolication can take a very long time, give it 10 extra minutes to finish.
567                     # FIXME: This can likely be removed once <rdar://problem/18701447> is fixed.
568                     deadline += 10 * 60 * 1000
569                 if asan_violation_detected:
570                     self._crash_report_from_driver += err_line
571                 else:
572                     self.error_from_test += err_line
573
574         if asan_violation_detected and not self._crashed_process_name:
575             self._crashed_process_name = self._server_process.name()
576             self._crashed_pid = self._server_process.pid()
577
578         block.decode_content()
579         return block
580
581     @staticmethod
582     def check_driver(port):
583         # This checks if the required system dependencies for the driver are met.
584         # Since this is the generic class implementation, just return True.
585         return True
586
587
588 class IOSSimulatorDriver(Driver):
589     def cmd_line(self, pixel_tests, per_test_args):
590         cmd = super(IOSSimulatorDriver, self).cmd_line(pixel_tests, per_test_args)
591         relay_tool = self._port.relay_path
592         dump_tool = cmd[0]
593         dump_tool_args = cmd[1:]
594         product_dir = self._port._build_path()
595         relay_args = [
596             '-runtime', self._port.simulator_runtime.identifier,
597             '-deviceType', self._port.simulator_device_type.identifier,
598             '-suffix', str(self._worker_number),
599             '-productDir', product_dir,
600             '-app', dump_tool,
601         ]
602         return [relay_tool] + relay_args + ['--'] + dump_tool_args
603
604     def _setup_environ_for_driver(self, environment):
605         environment['DEVELOPER_DIR'] = self._port.developer_dir
606         return super(IOSSimulatorDriver, self)._setup_environ_for_driver(environment)
607
608
609 class ContentBlock(object):
610     def __init__(self):
611         self.content_type = None
612         self.encoding = None
613         self.content_hash = None
614         self._content_length = None
615         # Content is treated as binary data even though the text output is usually UTF-8.
616         self.content = str()  # FIXME: Should be bytearray() once we require Python 2.6.
617         self.decoded_content = None
618         self.malloc = None
619         self.js_heap = None
620
621     def decode_content(self):
622         if self.encoding == 'base64' and self.content is not None:
623             self.decoded_content = base64.b64decode(self.content)
624         else:
625             self.decoded_content = self.content
626
627 class DriverProxy(object):
628     """A wrapper for managing two Driver instances, one with pixel tests and
629     one without. This allows us to handle plain text tests and ref tests with a
630     single driver."""
631
632     def __init__(self, port, worker_number, driver_instance_constructor, pixel_tests, no_timeout):
633         self._port = port
634         self._worker_number = worker_number
635         self._driver_instance_constructor = driver_instance_constructor
636         self._no_timeout = no_timeout
637
638         # FIXME: We shouldn't need to create a driver until we actually run a test.
639         self._driver = self._make_driver(pixel_tests)
640         self._driver_cmd_line = None
641
642     def _make_driver(self, pixel_tests):
643         return self._driver_instance_constructor(self._port, self._worker_number, pixel_tests, self._no_timeout)
644
645     # FIXME: this should be a @classmethod (or implemented on Port instead).
646     def is_http_test(self, test_name):
647         return self._driver.is_http_test(test_name)
648
649     # FIXME: this should be a @classmethod (or implemented on Port instead).
650     def test_to_uri(self, test_name):
651         return self._driver.test_to_uri(test_name)
652
653     # FIXME: this should be a @classmethod (or implemented on Port instead).
654     def uri_to_test(self, uri):
655         return self._driver.uri_to_test(uri)
656
657     def run_test(self, driver_input, stop_when_done):
658         pixel_tests_needed = driver_input.should_run_pixel_test
659         cmd_line_key = self._cmd_line_as_key(pixel_tests_needed, driver_input.args)
660         if cmd_line_key != self._driver_cmd_line:
661             self._driver.stop()
662             self._driver = self._make_driver(pixel_tests_needed)
663             self._driver_cmd_line = cmd_line_key
664
665         return self._driver.run_test(driver_input, stop_when_done)
666
667     def has_crashed(self):
668         return self._driver.has_crashed()
669
670     def stop(self):
671         self._driver.stop()
672
673     # FIXME: this should be a @classmethod (or implemented on Port instead).
674     def cmd_line(self, pixel_tests=None, per_test_args=None):
675         return self._driver.cmd_line(pixel_tests, per_test_args or [])
676
677     def _cmd_line_as_key(self, pixel_tests, per_test_args):
678         return ' '.join(self.cmd_line(pixel_tests, per_test_args))