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