Home | History | Annotate | Download | only in port
      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