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