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 """ 31 This is an implementation of the Port interface that overrides other 32 ports and changes the Driver binary to "MockDRT". 33 """ 34 35 import base64 36 import logging 37 import optparse 38 import os 39 import sys 40 41 from webkitpy.common.system import filesystem 42 43 from webkitpy.layout_tests.port import base 44 from webkitpy.layout_tests.port import factory 45 46 _log = logging.getLogger(__name__) 47 48 49 class 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 122 def 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 133 def 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. 165 class _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 176 class 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. 244 class _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 255 class 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 287 if __name__ == '__main__': 288 fs = filesystem.FileSystem() 289 sys.exit(main(sys.argv[1:], fs, sys.stdin, sys.stdout, sys.stderr)) 290