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