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 """ 30 This is an implementation of the Port interface that overrides other 31 ports and changes the Driver binary to "MockDRT". 32 33 The MockDRT objects emulate what a real DRT would do. In particular, they 34 return the output a real DRT would return for a given test, assuming that 35 test actually passes (except for reftests, which currently cause the 36 MockDRT to crash). 37 """ 38 39 import base64 40 import logging 41 import optparse 42 import os 43 import sys 44 import 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. 48 script_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 49 if script_dir not in sys.path: 50 sys.path.append(script_dir) 51 52 from webkitpy.common import read_checksum_from_png 53 from webkitpy.common.system.systemhost import SystemHost 54 from webkitpy.layout_tests.port.driver import DriverInput, DriverOutput 55 from webkitpy.layout_tests.port.factory import PortFactory 56 57 _log = logging.getLogger(__name__) 58 59 60 class 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, 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 141 def 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 149 def 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 169 class 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 283 if __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