1# Copyright (c) 2012 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"""
30This is an implementation of the Port interface that overrides other
31ports and changes the Driver binary to "MockDRT".
32
33The MockDRT objects emulate what a real DRT would do. In particular, they
34return the output a real DRT would return for a given test, assuming that
35test actually passes (except for reftests, which currently cause the
36MockDRT to crash).
37"""
38
39import base64
40import logging
41import optparse
42import os
43import sys
44import types
45
46# Since we execute this script directly as part of the unit tests, we need to ensure
47# that Tools/Scripts is in sys.path for the next imports to work correctly.
48script_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
49if script_dir not in sys.path:
50    sys.path.append(script_dir)
51
52from webkitpy.common import read_checksum_from_png
53from webkitpy.common.system.systemhost import SystemHost
54from webkitpy.layout_tests.port.driver import DriverInput, DriverOutput
55from webkitpy.layout_tests.port.factory import PortFactory
56
57_log = logging.getLogger(__name__)
58
59
60class MockDRTPort(object):
61    port_name = 'mock'
62
63    @classmethod
64    def determine_full_port_name(cls, host, options, port_name):
65        return port_name
66
67    def __init__(self, host, port_name, **kwargs):
68        self.__delegate = PortFactory(host).get(port_name.replace('mock-', ''), **kwargs)
69        self.__delegate_driver_class = self.__delegate._driver_class
70        self.__delegate._driver_class = types.MethodType(self._driver_class, self.__delegate)
71
72    def __getattr__(self, name):
73        return getattr(self.__delegate, name)
74
75    def check_build(self, needs_http, printer):
76        return True
77
78    def check_sys_deps(self, needs_http):
79        return True
80
81    def _driver_class(self, delegate):
82        return self._mocked_driver_maker
83
84    def _mocked_driver_maker(self, port, worker_number, pixel_tests, no_timeout=False):
85        path_to_this_file = self.host.filesystem.abspath(__file__.replace('.pyc', '.py'))
86        driver = self.__delegate_driver_class()(self, worker_number, pixel_tests, no_timeout)
87        driver.cmd_line = self._overriding_cmd_line(driver.cmd_line,
88                                                    self.__delegate._path_to_driver(),
89                                                    sys.executable,
90                                                    path_to_this_file,
91                                                    self.__delegate.name())
92        return driver
93
94    @staticmethod
95    def _overriding_cmd_line(original_cmd_line, driver_path, python_exe, this_file, port_name):
96        def new_cmd_line(pixel_tests, per_test_args):
97            cmd_line = original_cmd_line(pixel_tests, per_test_args)
98            index = cmd_line.index(driver_path)
99            cmd_line[index:index + 1] = [python_exe, this_file, '--platform', port_name]
100            return cmd_line
101
102        return new_cmd_line
103
104    def start_helper(self):
105        pass
106
107    def start_http_server(self, additional_dirs, number_of_servers):
108        pass
109
110    def start_websocket_server(self):
111        pass
112
113    def acquire_http_lock(self):
114        pass
115
116    def stop_helper(self):
117        pass
118
119    def stop_http_server(self):
120        pass
121
122    def stop_websocket_server(self):
123        pass
124
125    def release_http_lock(self):
126        pass
127
128    def _make_wdiff_available(self):
129        self.__delegate._wdiff_available = True
130
131    def setup_environ_for_server(self, server_name):
132        env = self.__delegate.setup_environ_for_server()
133        # We need to propagate PATH down so the python code can find the checkout.
134        env['PATH'] = os.environ['PATH']
135        return env
136
137    def lookup_virtual_test_args(self, test_name):
138        suite = self.__delegate.lookup_virtual_suite(test_name)
139        return suite.args + ['--virtual-test-suite-name', suite.name, '--virtual-test-suite-base', suite.base]
140
141def main(argv, host, stdin, stdout, stderr):
142    """Run the tests."""
143
144    options, args = parse_options(argv)
145    drt = MockDRT(options, args, host, stdin, stdout, stderr)
146    return drt.run()
147
148
149def parse_options(argv):
150    # We do custom arg parsing instead of using the optparse module
151    # because we don't want to have to list every command line flag DRT
152    # accepts, and optparse complains about unrecognized flags.
153
154    def get_arg(arg_name):
155        if arg_name in argv:
156            index = argv.index(arg_name)
157            return argv[index + 1]
158        return None
159
160    options = optparse.Values({
161        'actual_directory':        get_arg('--actual-directory'),
162        'platform':                get_arg('--platform'),
163        'virtual_test_suite_base': get_arg('--virtual-test-suite-base'),
164        'virtual_test_suite_name': get_arg('--virtual-test-suite-name'),
165    })
166    return (options, argv)
167
168
169class MockDRT(object):
170    def __init__(self, options, args, host, stdin, stdout, stderr):
171        self._options = options
172        self._args = args
173        self._host = host
174        self._stdout = stdout
175        self._stdin = stdin
176        self._stderr = stderr
177
178        port_name = None
179        if options.platform:
180            port_name = options.platform
181        self._port = PortFactory(host).get(port_name=port_name, options=options)
182        self._driver = self._port.create_driver(0)
183
184    def run(self):
185        while True:
186            line = self._stdin.readline()
187            if not line:
188                return 0
189            driver_input = self.input_from_line(line)
190            dirname, basename = self._port.split_test(driver_input.test_name)
191            is_reftest = (self._port.reference_files(driver_input.test_name) or
192                          self._port.is_reference_html_file(self._port._filesystem, dirname, basename))
193            output = self.output_for_test(driver_input, is_reftest)
194            self.write_test_output(driver_input, output, is_reftest)
195
196    def input_from_line(self, line):
197        vals = line.strip().split("'")
198        uri = vals[0]
199        checksum = None
200        should_run_pixel_tests = False
201        if len(vals) == 2 and vals[1] == '--pixel-test':
202            should_run_pixel_tests = True
203        elif len(vals) == 3 and vals[1] == '--pixel-test':
204            should_run_pixel_tests = True
205            checksum = vals[2]
206        elif len(vals) != 1:
207            raise NotImplementedError
208
209        if uri.startswith('http://') or uri.startswith('https://'):
210            test_name = self._driver.uri_to_test(uri)
211        else:
212            test_name = self._port.relative_test_filename(uri)
213
214        return DriverInput(test_name, 0, checksum, should_run_pixel_tests, args=[])
215
216    def output_for_test(self, test_input, is_reftest):
217        port = self._port
218        if self._options.virtual_test_suite_name:
219            test_input.test_name = test_input.test_name.replace(self._options.virtual_test_suite_base, self._options.virtual_test_suite_name)
220        actual_text = port.expected_text(test_input.test_name)
221        actual_audio = port.expected_audio(test_input.test_name)
222        actual_image = None
223        actual_checksum = None
224        if is_reftest:
225            # Make up some output for reftests.
226            actual_text = 'reference text\n'
227            actual_checksum = 'mock-checksum'
228            actual_image = 'blank'
229            if test_input.test_name.endswith('-mismatch.html'):
230                actual_text = 'not reference text\n'
231                actual_checksum = 'not-mock-checksum'
232                actual_image = 'not blank'
233        elif test_input.should_run_pixel_test and test_input.image_hash:
234            actual_checksum = port.expected_checksum(test_input.test_name)
235            actual_image = port.expected_image(test_input.test_name)
236
237        if self._options.actual_directory:
238            actual_path = port._filesystem.join(self._options.actual_directory, test_input.test_name)
239            root, _ = port._filesystem.splitext(actual_path)
240            text_path = root + '-actual.txt'
241            if port._filesystem.exists(text_path):
242                actual_text = port._filesystem.read_binary_file(text_path)
243            audio_path = root + '-actual.wav'
244            if port._filesystem.exists(audio_path):
245                actual_audio = port._filesystem.read_binary_file(audio_path)
246            image_path = root + '-actual.png'
247            if port._filesystem.exists(image_path):
248                actual_image = port._filesystem.read_binary_file(image_path)
249                with port._filesystem.open_binary_file_for_reading(image_path) as filehandle:
250                    actual_checksum = read_checksum_from_png.read_checksum(filehandle)
251
252        return DriverOutput(actual_text, actual_image, actual_checksum, actual_audio)
253
254    def write_test_output(self, test_input, output, is_reftest):
255        if output.audio:
256            self._stdout.write('Content-Type: audio/wav\n')
257            self._stdout.write('Content-Transfer-Encoding: base64\n')
258            self._stdout.write(base64.b64encode(output.audio))
259            self._stdout.write('\n')
260        else:
261            self._stdout.write('Content-Type: text/plain\n')
262            # FIXME: Note that we don't ensure there is a trailing newline!
263            # This mirrors actual (Mac) DRT behavior but is a bug.
264            if output.text:
265                self._stdout.write(output.text)
266
267        self._stdout.write('#EOF\n')
268
269        if test_input.should_run_pixel_test and output.image_hash:
270            self._stdout.write('\n')
271            self._stdout.write('ActualHash: %s\n' % output.image_hash)
272            self._stdout.write('ExpectedHash: %s\n' % test_input.image_hash)
273            if output.image_hash != test_input.image_hash:
274                self._stdout.write('Content-Type: image/png\n')
275                self._stdout.write('Content-Length: %s\n' % len(output.image))
276                self._stdout.write(output.image)
277        self._stdout.write('#EOF\n')
278        self._stdout.flush()
279        self._stderr.write('#EOF\n')
280        self._stderr.flush()
281
282
283if __name__ == '__main__':
284    # Note that the Mock in MockDRT refers to the fact that it is emulating a
285    # real DRT, and as such, it needs access to a real SystemHost, not a MockSystemHost.
286    sys.exit(main(sys.argv[1:], SystemHost(), sys.stdin, sys.stdout, sys.stderr))
287