1#!/usr/bin/env python
2# Copyright (C) 2011 Google 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"""
31This is an implementation of the Port interface that overrides other
32ports and changes the Driver binary to "MockDRT".
33"""
34
35import base64
36import logging
37import optparse
38import os
39import sys
40
41from webkitpy.common.system import filesystem
42
43from webkitpy.layout_tests.port import base
44from webkitpy.layout_tests.port import factory
45
46_log = logging.getLogger(__name__)
47
48
49class MockDRTPort(object):
50    """MockPort implementation of the Port interface."""
51
52    def __init__(self, **kwargs):
53        prefix = 'mock-'
54        if 'port_name' in kwargs:
55            kwargs['port_name'] = kwargs['port_name'][len(prefix):]
56        self.__delegate = factory.get(**kwargs)
57        self.__real_name = prefix + self.__delegate.name()
58
59    def real_name(self):
60        return self.__real_name
61
62    def __getattr__(self, name):
63        return getattr(self.__delegate, name)
64
65    def acquire_http_lock(self):
66        pass
67
68    def release_http_lock(self):
69        pass
70
71    def check_build(self, needs_http):
72        return True
73
74    def check_sys_deps(self, needs_http):
75        return True
76
77    def driver_cmd_line(self):
78        driver = self.create_driver(0)
79        return driver.cmd_line()
80
81    def _path_to_driver(self):
82        return os.path.abspath(__file__)
83
84    def create_driver(self, worker_number):
85        # We need to create a driver object as the delegate would, but
86        # overwrite the path to the driver binary in its command line. We do
87        # this by actually overwriting its cmd_line() method with a proxy
88        # method that splices in the mock_drt path and command line arguments
89        # in place of the actual path to the driver binary.
90
91        def overriding_cmd_line():
92            cmd = self.__original_driver_cmd_line()
93            index = cmd.index(self.__delegate._path_to_driver())
94            cmd[index:index + 1] = [sys.executable, self._path_to_driver(),
95                                    '--platform', self.name()]
96            return cmd
97
98        delegated_driver = self.__delegate.create_driver(worker_number)
99        self.__original_driver_cmd_line = delegated_driver.cmd_line
100        delegated_driver.cmd_line = overriding_cmd_line
101        return delegated_driver
102
103    def start_helper(self):
104        pass
105
106    def start_http_server(self):
107        pass
108
109    def start_websocket_server(self):
110        pass
111
112    def stop_helper(self):
113        pass
114
115    def stop_http_server(self):
116        pass
117
118    def stop_websocket_server(self):
119        pass
120
121
122def main(argv, fs, stdin, stdout, stderr):
123    """Run the tests."""
124
125    options, args = parse_options(argv)
126    if options.chromium:
127        drt = MockChromiumDRT(options, args, fs, stdin, stdout, stderr)
128    else:
129        drt = MockDRT(options, args, fs, stdin, stdout, stderr)
130    return drt.run()
131
132
133def parse_options(argv):
134    # FIXME: We have to do custom arg parsing instead of using the optparse
135    # module.  First, Chromium and non-Chromium DRTs have a different argument
136    # syntax.  Chromium uses --pixel-tests=<path>, and non-Chromium uses
137    # --pixel-tests as a boolean flag. Second, we don't want to have to list
138    # every command line flag DRT accepts, but optparse complains about
139    # unrecognized flags. At some point it might be good to share a common
140    # DRT options class between this file and webkit.py and chromium.py
141    # just to get better type checking.
142    platform_index = argv.index('--platform')
143    platform = argv[platform_index + 1]
144
145    pixel_tests = False
146    pixel_path = None
147    chromium = False
148    if platform.startswith('chromium'):
149        chromium = True
150        for arg in argv:
151            if arg.startswith('--pixel-tests'):
152                pixel_tests = True
153                pixel_path = arg[len('--pixel-tests='):]
154    else:
155        pixel_tests = '--pixel-tests' in argv
156    options = base.DummyOptions(chromium=chromium,
157                                platform=platform,
158                                pixel_tests=pixel_tests,
159                                pixel_path=pixel_path)
160    return (options, [])
161
162
163# FIXME: Should probably change this to use DriverInput after
164# https://bugs.webkit.org/show_bug.cgi?id=53004 lands.
165class _DRTInput(object):
166    def __init__(self, line):
167        vals = line.strip().split("'")
168        if len(vals) == 1:
169            self.uri = vals[0]
170            self.checksum = None
171        else:
172            self.uri = vals[0]
173            self.checksum = vals[1]
174
175
176class MockDRT(object):
177    def __init__(self, options, args, filesystem, stdin, stdout, stderr):
178        self._options = options
179        self._args = args
180        self._filesystem = filesystem
181        self._stdout = stdout
182        self._stdin = stdin
183        self._stderr = stderr
184
185        port_name = None
186        if options.platform:
187            port_name = options.platform
188        self._port = factory.get(port_name, options=options, filesystem=filesystem)
189
190    def run(self):
191        while True:
192            line = self._stdin.readline()
193            if not line:
194                break
195            self.run_one_test(self.parse_input(line))
196        return 0
197
198    def parse_input(self, line):
199        return _DRTInput(line)
200
201    def run_one_test(self, test_input):
202        port = self._port
203        if test_input.uri.startswith('http'):
204            test_name = port.uri_to_test_name(test_input.uri)
205            test_path = self._filesystem.join(port.layout_tests_dir(), test_name)
206        else:
207            test_path = test_input.uri
208
209        actual_text = port.expected_text(test_path)
210        actual_audio = port.expected_audio(test_path)
211        if self._options.pixel_tests and test_input.checksum:
212            actual_checksum = port.expected_checksum(test_path)
213            actual_image = port.expected_image(test_path)
214
215        if actual_audio:
216            self._stdout.write('Content-Type: audio/wav\n')
217            self._stdout.write('Content-Transfer-Encoding: base64\n')
218            output = base64.b64encode(actual_audio)
219            self._stdout.write('Content-Length: %s\n' % len(output))
220            self._stdout.write(output)
221        else:
222            self._stdout.write('Content-Type: text/plain\n')
223            # FIXME: Note that we don't ensure there is a trailing newline!
224            # This mirrors actual (Mac) DRT behavior but is a bug.
225            self._stdout.write(actual_text)
226
227        self._stdout.write('#EOF\n')
228
229        if self._options.pixel_tests and test_input.checksum:
230            self._stdout.write('\n')
231            self._stdout.write('ActualHash: %s\n' % actual_checksum)
232            self._stdout.write('ExpectedHash: %s\n' % test_input.checksum)
233            if actual_checksum != test_input.checksum:
234                self._stdout.write('Content-Type: image/png\n')
235                self._stdout.write('Content-Length: %s\n' % len(actual_image))
236                self._stdout.write(actual_image)
237        self._stdout.write('#EOF\n')
238        self._stdout.flush()
239        self._stderr.flush()
240
241
242# FIXME: Should probably change this to use DriverInput after
243# https://bugs.webkit.org/show_bug.cgi?id=53004 lands.
244class _ChromiumDRTInput(_DRTInput):
245    def __init__(self, line):
246        vals = line.strip().split()
247        if len(vals) == 3:
248            self.uri, self.timeout, self.checksum = vals
249        else:
250            self.uri = vals[0]
251            self.timeout = vals[1]
252            self.checksum = None
253
254
255class MockChromiumDRT(MockDRT):
256    def parse_input(self, line):
257        return _ChromiumDRTInput(line)
258
259    def run_one_test(self, test_input):
260        port = self._port
261        test_name = self._port.uri_to_test_name(test_input.uri)
262        test_path = self._filesystem.join(port.layout_tests_dir(), test_name)
263
264        actual_text = port.expected_text(test_path)
265        actual_image = ''
266        actual_checksum = ''
267        if self._options.pixel_tests and test_input.checksum:
268            actual_checksum = port.expected_checksum(test_path)
269            if actual_checksum != test_input.checksum:
270                actual_image = port.expected_image(test_path)
271
272        self._stdout.write("#URL:%s\n" % test_input.uri)
273        if self._options.pixel_tests and test_input.checksum:
274            self._stdout.write("#MD5:%s\n" % actual_checksum)
275            self._filesystem.write_binary_file(self._options.pixel_path,
276                                               actual_image)
277        self._stdout.write(actual_text)
278
279        # FIXME: (See above FIXME as well). Chromium DRT appears to always
280        # ensure the text output has a trailing newline. Mac DRT does not.
281        if not actual_text.endswith('\n'):
282            self._stdout.write('\n')
283        self._stdout.write('#EOF\n')
284        self._stdout.flush()
285
286
287if __name__ == '__main__':
288    fs = filesystem.FileSystem()
289    sys.exit(main(sys.argv[1:], fs, sys.stdin, sys.stdout, sys.stderr))
290