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