1 # Copyright (C) 2010 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 name of Google Inc. 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 """Unit testing base class for Port implementations.""" 30 31 import collections 32 import errno 33 import logging 34 import os 35 import socket 36 import sys 37 import time 38 import unittest 39 40 from webkitpy.common.system.executive_mock import MockExecutive, MockExecutive2 41 from webkitpy.common.system.filesystem_mock import MockFileSystem 42 from webkitpy.common.system.outputcapture import OutputCapture 43 from webkitpy.common.system.systemhost import SystemHost 44 from webkitpy.common.system.systemhost_mock import MockSystemHost 45 from webkitpy.layout_tests.models import test_run_results 46 from webkitpy.layout_tests.port.base import Port, TestConfiguration 47 from webkitpy.layout_tests.port.server_process_mock import MockServerProcess 48 from webkitpy.tool.mocktool import MockOptions 49 50 51 # FIXME: get rid of this fixture 52 class TestWebKitPort(Port): 53 port_name = "testwebkitport" 54 55 def __init__(self, port_name=None, symbols_string=None, 56 expectations_file=None, skips_file=None, host=None, config=None, 57 **kwargs): 58 port_name = port_name or TestWebKitPort.port_name 59 self.symbols_string = symbols_string # Passing "" disables all staticly-detectable features. 60 host = host or MockSystemHost() 61 super(TestWebKitPort, self).__init__(host, port_name=port_name, **kwargs) 62 63 def all_test_configurations(self): 64 return [self.test_configuration()] 65 66 def _symbols_string(self): 67 return self.symbols_string 68 69 def _tests_for_disabled_features(self): 70 return ["accessibility", ] 71 72 73 class FakePrinter(object): 74 def write_update(self, msg): 75 pass 76 77 def write_throttled_update(self, msg): 78 pass 79 80 81 82 class PortTestCase(unittest.TestCase): 83 """Tests that all Port implementations must pass.""" 84 HTTP_PORTS = (8000, 8080, 8443) 85 WEBSOCKET_PORTS = (8880,) 86 87 # Subclasses override this to point to their Port subclass. 88 os_name = None 89 os_version = None 90 port_maker = TestWebKitPort 91 port_name = None 92 full_port_name = None 93 94 def make_port(self, host=None, port_name=None, options=None, os_name=None, os_version=None, **kwargs): 95 host = host or MockSystemHost(os_name=(os_name or self.os_name), os_version=(os_version or self.os_version)) 96 options = options or MockOptions(configuration='Release') 97 port_name = port_name or self.port_name 98 port_name = self.port_maker.determine_full_port_name(host, options, port_name) 99 port = self.port_maker(host, port_name, options=options, **kwargs) 100 port._config.build_directory = lambda configuration: '/mock-build' 101 return port 102 103 def make_wdiff_available(self, port): 104 port._wdiff_available = True 105 106 def test_check_build(self): 107 port = self.make_port() 108 port._check_file_exists = lambda path, desc: True 109 if port._dump_reader: 110 port._dump_reader.check_is_functional = lambda: True 111 port._options.build = True 112 port._check_driver_build_up_to_date = lambda config: True 113 port.check_httpd = lambda: True 114 oc = OutputCapture() 115 try: 116 oc.capture_output() 117 self.assertEqual(port.check_build(needs_http=True, printer=FakePrinter()), 118 test_run_results.OK_EXIT_STATUS) 119 finally: 120 out, err, logs = oc.restore_output() 121 self.assertIn('pretty patches', logs) # We should get a warning about PrettyPatch being missing, 122 self.assertNotIn('build requirements', logs) # but not the driver itself. 123 124 port._check_file_exists = lambda path, desc: False 125 port._check_driver_build_up_to_date = lambda config: False 126 try: 127 oc.capture_output() 128 self.assertEqual(port.check_build(needs_http=True, printer=FakePrinter()), 129 test_run_results.UNEXPECTED_ERROR_EXIT_STATUS) 130 finally: 131 out, err, logs = oc.restore_output() 132 self.assertIn('pretty patches', logs) # And, hereere we should get warnings about both. 133 self.assertIn('build requirements', logs) 134 135 def test_default_max_locked_shards(self): 136 port = self.make_port() 137 port.default_child_processes = lambda: 16 138 self.assertEqual(port.default_max_locked_shards(), 4) 139 port.default_child_processes = lambda: 2 140 self.assertEqual(port.default_max_locked_shards(), 1) 141 142 def test_default_timeout_ms(self): 143 self.assertEqual(self.make_port(options=MockOptions(configuration='Release')).default_timeout_ms(), 6000) 144 self.assertEqual(self.make_port(options=MockOptions(configuration='Debug')).default_timeout_ms(), 18000) 145 146 def test_default_pixel_tests(self): 147 self.assertEqual(self.make_port().default_pixel_tests(), True) 148 149 def test_driver_cmd_line(self): 150 port = self.make_port() 151 self.assertTrue(len(port.driver_cmd_line())) 152 153 options = MockOptions(additional_drt_flag=['--foo=bar', '--foo=baz']) 154 port = self.make_port(options=options) 155 cmd_line = port.driver_cmd_line() 156 self.assertTrue('--foo=bar' in cmd_line) 157 self.assertTrue('--foo=baz' in cmd_line) 158 159 def assert_servers_are_down(self, host, ports): 160 for port in ports: 161 try: 162 test_socket = socket.socket() 163 test_socket.connect((host, port)) 164 self.fail() 165 except IOError, e: 166 self.assertTrue(e.errno in (errno.ECONNREFUSED, errno.ECONNRESET)) 167 finally: 168 test_socket.close() 169 170 def assert_servers_are_up(self, host, ports): 171 for port in ports: 172 try: 173 test_socket = socket.socket() 174 test_socket.connect((host, port)) 175 except IOError, e: 176 self.fail('failed to connect to %s:%d' % (host, port)) 177 finally: 178 test_socket.close() 179 180 def test_diff_image__missing_both(self): 181 port = self.make_port() 182 self.assertEqual(port.diff_image(None, None), (None, None)) 183 self.assertEqual(port.diff_image(None, ''), (None, None)) 184 self.assertEqual(port.diff_image('', None), (None, None)) 185 186 self.assertEqual(port.diff_image('', ''), (None, None)) 187 188 def test_diff_image__missing_actual(self): 189 port = self.make_port() 190 self.assertEqual(port.diff_image(None, 'foo'), ('foo', None)) 191 self.assertEqual(port.diff_image('', 'foo'), ('foo', None)) 192 193 def test_diff_image__missing_expected(self): 194 port = self.make_port() 195 self.assertEqual(port.diff_image('foo', None), ('foo', None)) 196 self.assertEqual(port.diff_image('foo', ''), ('foo', None)) 197 198 def test_diff_image(self): 199 def _path_to_image_diff(): 200 return "/path/to/image_diff" 201 202 port = self.make_port() 203 port._path_to_image_diff = _path_to_image_diff 204 205 mock_image_diff = "MOCK Image Diff" 206 207 def mock_run_command(args): 208 port._filesystem.write_binary_file(args[4], mock_image_diff) 209 return 1 210 211 # Images are different. 212 port._executive = MockExecutive2(run_command_fn=mock_run_command) 213 self.assertEqual(mock_image_diff, port.diff_image("EXPECTED", "ACTUAL")[0]) 214 215 # Images are the same. 216 port._executive = MockExecutive2(exit_code=0) 217 self.assertEqual(None, port.diff_image("EXPECTED", "ACTUAL")[0]) 218 219 # There was some error running image_diff. 220 port._executive = MockExecutive2(exit_code=2) 221 exception_raised = False 222 try: 223 port.diff_image("EXPECTED", "ACTUAL") 224 except ValueError, e: 225 exception_raised = True 226 self.assertFalse(exception_raised) 227 228 def test_diff_image_crashed(self): 229 port = self.make_port() 230 port._executive = MockExecutive2(exit_code=2) 231 self.assertEqual(port.diff_image("EXPECTED", "ACTUAL"), (None, 'Image diff returned an exit code of 2. See http://crbug.com/278596')) 232 233 def test_check_wdiff(self): 234 port = self.make_port() 235 port.check_wdiff() 236 237 def test_wdiff_text_fails(self): 238 host = MockSystemHost(os_name=self.os_name, os_version=self.os_version) 239 host.executive = MockExecutive(should_throw=True) 240 port = self.make_port(host=host) 241 port._executive = host.executive # AndroidPortTest.make_port sets its own executive, so reset that as well. 242 243 # This should raise a ScriptError that gets caught and turned into the 244 # error text, and also mark wdiff as not available. 245 self.make_wdiff_available(port) 246 self.assertTrue(port.wdiff_available()) 247 diff_txt = port.wdiff_text("/tmp/foo.html", "/tmp/bar.html") 248 self.assertEqual(diff_txt, port._wdiff_error_html) 249 self.assertFalse(port.wdiff_available()) 250 251 def test_missing_symbol_to_skipped_tests(self): 252 # Test that we get the chromium skips and not the webkit default skips 253 port = self.make_port() 254 skip_dict = port._missing_symbol_to_skipped_tests() 255 if port.PORT_HAS_AUDIO_CODECS_BUILT_IN: 256 self.assertEqual(skip_dict, {}) 257 else: 258 self.assertTrue('ff_mp3_decoder' in skip_dict) 259 self.assertFalse('WebGLShader' in skip_dict) 260 261 def test_test_configuration(self): 262 port = self.make_port() 263 self.assertTrue(port.test_configuration()) 264 265 def test_all_test_configurations(self): 266 """Validate the complete set of configurations this port knows about.""" 267 port = self.make_port() 268 self.assertEqual(set(port.all_test_configurations()), set([ 269 TestConfiguration('snowleopard', 'x86', 'debug'), 270 TestConfiguration('snowleopard', 'x86', 'release'), 271 TestConfiguration('lion', 'x86', 'debug'), 272 TestConfiguration('lion', 'x86', 'release'), 273 TestConfiguration('retina', 'x86', 'debug'), 274 TestConfiguration('retina', 'x86', 'release'), 275 TestConfiguration('mountainlion', 'x86', 'debug'), 276 TestConfiguration('mountainlion', 'x86', 'release'), 277 TestConfiguration('mavericks', 'x86', 'debug'), 278 TestConfiguration('mavericks', 'x86', 'release'), 279 TestConfiguration('xp', 'x86', 'debug'), 280 TestConfiguration('xp', 'x86', 'release'), 281 TestConfiguration('win7', 'x86', 'debug'), 282 TestConfiguration('win7', 'x86', 'release'), 283 TestConfiguration('lucid', 'x86', 'debug'), 284 TestConfiguration('lucid', 'x86', 'release'), 285 TestConfiguration('lucid', 'x86_64', 'debug'), 286 TestConfiguration('lucid', 'x86_64', 'release'), 287 TestConfiguration('icecreamsandwich', 'x86', 'debug'), 288 TestConfiguration('icecreamsandwich', 'x86', 'release'), 289 ])) 290 def test_get_crash_log(self): 291 port = self.make_port() 292 self.assertEqual(port._get_crash_log(None, None, None, None, newer_than=None), 293 (None, 294 'crash log for <unknown process name> (pid <unknown>):\n' 295 'STDOUT: <empty>\n' 296 'STDERR: <empty>\n')) 297 298 self.assertEqual(port._get_crash_log('foo', 1234, 'out bar\nout baz', 'err bar\nerr baz\n', newer_than=None), 299 ('err bar\nerr baz\n', 300 'crash log for foo (pid 1234):\n' 301 'STDOUT: out bar\n' 302 'STDOUT: out baz\n' 303 'STDERR: err bar\n' 304 'STDERR: err baz\n')) 305 306 self.assertEqual(port._get_crash_log('foo', 1234, 'foo\xa6bar', 'foo\xa6bar', newer_than=None), 307 ('foo\xa6bar', 308 u'crash log for foo (pid 1234):\n' 309 u'STDOUT: foo\ufffdbar\n' 310 u'STDERR: foo\ufffdbar\n')) 311 312 self.assertEqual(port._get_crash_log('foo', 1234, 'foo\xa6bar', 'foo\xa6bar', newer_than=1.0), 313 ('foo\xa6bar', 314 u'crash log for foo (pid 1234):\n' 315 u'STDOUT: foo\ufffdbar\n' 316 u'STDERR: foo\ufffdbar\n')) 317 318 def assert_build_path(self, options, dirs, expected_path): 319 port = self.make_port(options=options) 320 for directory in dirs: 321 port.host.filesystem.maybe_make_directory(directory) 322 self.assertEqual(port._build_path(), expected_path) 323 324 def test_expectations_files(self): 325 port = self.make_port() 326 327 generic_path = port.path_to_generic_test_expectations_file() 328 never_fix_tests_path = port._filesystem.join(port.layout_tests_dir(), 'NeverFixTests') 329 stale_tests_path = port._filesystem.join(port.layout_tests_dir(), 'StaleTestExpectations') 330 slow_tests_path = port._filesystem.join(port.layout_tests_dir(), 'SlowTests') 331 flaky_tests_path = port._filesystem.join(port.layout_tests_dir(), 'FlakyTests') 332 skia_overrides_path = port.path_from_chromium_base( 333 'skia', 'skia_test_expectations.txt') 334 335 port._filesystem.write_text_file(skia_overrides_path, 'dummy text') 336 337 port._options.builder_name = 'DUMMY_BUILDER_NAME' 338 self.assertEqual(port.expectations_files(), 339 [generic_path, skia_overrides_path, 340 never_fix_tests_path, stale_tests_path, slow_tests_path, 341 flaky_tests_path]) 342 343 port._options.builder_name = 'builder (deps)' 344 self.assertEqual(port.expectations_files(), 345 [generic_path, skia_overrides_path, 346 never_fix_tests_path, stale_tests_path, slow_tests_path, 347 flaky_tests_path]) 348 349 # A builder which does NOT observe the Chromium test_expectations, 350 # but still observes the Skia test_expectations... 351 port._options.builder_name = 'builder' 352 self.assertEqual(port.expectations_files(), 353 [generic_path, skia_overrides_path, 354 never_fix_tests_path, stale_tests_path, slow_tests_path, 355 flaky_tests_path]) 356 357 def test_check_sys_deps(self): 358 port = self.make_port() 359 port._executive = MockExecutive2(exit_code=0) 360 self.assertEqual(port.check_sys_deps(needs_http=False), test_run_results.OK_EXIT_STATUS) 361 port._executive = MockExecutive2(exit_code=1, output='testing output failure') 362 self.assertEqual(port.check_sys_deps(needs_http=False), test_run_results.SYS_DEPS_EXIT_STATUS) 363 364 def test_expectations_ordering(self): 365 port = self.make_port() 366 for path in port.expectations_files(): 367 port._filesystem.write_text_file(path, '') 368 ordered_dict = port.expectations_dict() 369 self.assertEqual(port.path_to_generic_test_expectations_file(), ordered_dict.keys()[0]) 370 371 options = MockOptions(additional_expectations=['/tmp/foo', '/tmp/bar']) 372 port = self.make_port(options=options) 373 for path in port.expectations_files(): 374 port._filesystem.write_text_file(path, '') 375 port._filesystem.write_text_file('/tmp/foo', 'foo') 376 port._filesystem.write_text_file('/tmp/bar', 'bar') 377 ordered_dict = port.expectations_dict() 378 self.assertEqual(ordered_dict.keys()[-2:], options.additional_expectations) # pylint: disable=E1101 379 self.assertEqual(ordered_dict.values()[-2:], ['foo', 'bar']) 380 381 def test_skipped_directories_for_symbols(self): 382 # This first test confirms that the commonly found symbols result in the expected skipped directories. 383 symbols_string = " ".join(["fooSymbol"]) 384 expected_directories = set([ 385 "webaudio/codec-tests/mp3", 386 "webaudio/codec-tests/aac", 387 ]) 388 389 result_directories = set(TestWebKitPort(symbols_string=symbols_string)._skipped_tests_for_unsupported_features(test_list=['webaudio/codec-tests/mp3/foo.html'])) 390 self.assertEqual(result_directories, expected_directories) 391 392 # Test that the nm string parsing actually works: 393 symbols_string = """ 394 000000000124f498 s __ZZN7WebCore13ff_mp3_decoder12replaceChildEPS0_S1_E19__PRETTY_FUNCTION__ 395 000000000124f500 s __ZZN7WebCore13ff_mp3_decoder13addChildAboveEPS0_S1_E19__PRETTY_FUNCTION__ 396 000000000124f670 s __ZZN7WebCore13ff_mp3_decoder13addChildBelowEPS0_S1_E19__PRETTY_FUNCTION__ 397 """ 398 # Note 'compositing' is not in the list of skipped directories (hence the parsing of GraphicsLayer worked): 399 expected_directories = set([ 400 "webaudio/codec-tests/aac", 401 ]) 402 result_directories = set(TestWebKitPort(symbols_string=symbols_string)._skipped_tests_for_unsupported_features(test_list=['webaudio/codec-tests/mp3/foo.html'])) 403 self.assertEqual(result_directories, expected_directories) 404 405 def _assert_config_file_for_platform(self, port, platform, config_file): 406 self.assertEqual(port._apache_config_file_name_for_platform(platform), config_file) 407 408 def test_linux_distro_detection(self): 409 port = TestWebKitPort() 410 self.assertFalse(port._is_redhat_based()) 411 self.assertFalse(port._is_debian_based()) 412 413 port._filesystem = MockFileSystem({'/etc/redhat-release': ''}) 414 self.assertTrue(port._is_redhat_based()) 415 self.assertFalse(port._is_debian_based()) 416 417 port._filesystem = MockFileSystem({'/etc/debian_version': ''}) 418 self.assertFalse(port._is_redhat_based()) 419 self.assertTrue(port._is_debian_based()) 420 421 def test_apache_config_file_name_for_platform(self): 422 port = TestWebKitPort() 423 self._assert_config_file_for_platform(port, 'cygwin', 'cygwin-httpd.conf') 424 425 self._assert_config_file_for_platform(port, 'linux2', 'apache2-httpd.conf') 426 self._assert_config_file_for_platform(port, 'linux3', 'apache2-httpd.conf') 427 428 port._is_redhat_based = lambda: True 429 port._apache_version = lambda: '2.2' 430 self._assert_config_file_for_platform(port, 'linux2', 'fedora-httpd-2.2.conf') 431 432 port = TestWebKitPort() 433 port._is_debian_based = lambda: True 434 port._apache_version = lambda: '2.2' 435 self._assert_config_file_for_platform(port, 'linux2', 'debian-httpd-2.2.conf') 436 437 self._assert_config_file_for_platform(port, 'mac', 'apache2-httpd.conf') 438 self._assert_config_file_for_platform(port, 'win32', 'apache2-httpd.conf') # win32 isn't a supported sys.platform. AppleWin/WinCairo/WinCE ports all use cygwin. 439 self._assert_config_file_for_platform(port, 'barf', 'apache2-httpd.conf') 440 441 def test_path_to_apache_config_file(self): 442 port = TestWebKitPort() 443 444 saved_environ = os.environ.copy() 445 try: 446 os.environ['WEBKIT_HTTP_SERVER_CONF_PATH'] = '/path/to/httpd.conf' 447 self.assertRaises(IOError, port.path_to_apache_config_file) 448 port._filesystem.write_text_file('/existing/httpd.conf', 'Hello, world!') 449 os.environ['WEBKIT_HTTP_SERVER_CONF_PATH'] = '/existing/httpd.conf' 450 self.assertEqual(port.path_to_apache_config_file(), '/existing/httpd.conf') 451 finally: 452 os.environ = saved_environ.copy() 453 454 # Mock out _apache_config_file_name_for_platform to ignore the passed sys.platform value. 455 port._apache_config_file_name_for_platform = lambda platform: 'httpd.conf' 456 self.assertEqual(port.path_to_apache_config_file(), '/mock-checkout/third_party/WebKit/LayoutTests/http/conf/httpd.conf') 457 458 # Check that even if we mock out _apache_config_file_name, the environment variable takes precedence. 459 saved_environ = os.environ.copy() 460 try: 461 os.environ['WEBKIT_HTTP_SERVER_CONF_PATH'] = '/existing/httpd.conf' 462 self.assertEqual(port.path_to_apache_config_file(), '/existing/httpd.conf') 463 finally: 464 os.environ = saved_environ.copy() 465 466 def test_additional_platform_directory(self): 467 port = self.make_port(options=MockOptions(additional_platform_directory=['/tmp/foo'])) 468 self.assertEqual(port.baseline_search_path()[0], '/tmp/foo') 469 470 def test_virtual_test_suites(self): 471 # We test that we can load the real LayoutTests/VirtualTestSuites file properly, so we 472 # use a real SystemHost(). We don't care what virtual_test_suites() returns as long 473 # as it is iterable. 474 port = self.make_port(host=SystemHost(), port_name=self.full_port_name) 475 self.assertTrue(isinstance(port.virtual_test_suites(), collections.Iterable)) 476