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