Home | History | Annotate | Download | only in controllers
      1 # Copyright (C) 2012 Google Inc. All rights reserved.
      2 # Copyright (C) 2010 Gabor Rapcsanyi (rgabor (at] inf.u-szeged.hu), University of Szeged
      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 name of Google Inc. 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 import unittest
     31 
     32 from webkitpy.common.host_mock import MockHost
     33 from webkitpy.common.system.systemhost_mock import MockSystemHost
     34 from webkitpy.layout_tests import run_webkit_tests
     35 from webkitpy.layout_tests.controllers.layout_test_runner import LayoutTestRunner, Sharder, TestRunInterruptedException
     36 from webkitpy.layout_tests.models import test_expectations
     37 from webkitpy.layout_tests.models import test_failures
     38 from webkitpy.layout_tests.models.test_run_results import TestRunResults
     39 from webkitpy.layout_tests.models.test_input import TestInput
     40 from webkitpy.layout_tests.models.test_results import TestResult
     41 from webkitpy.layout_tests.port.test import TestPort
     42 
     43 
     44 TestExpectations = test_expectations.TestExpectations
     45 
     46 
     47 class FakePrinter(object):
     48     num_completed = 0
     49     num_tests = 0
     50 
     51     def print_expected(self, run_results, get_tests_with_result_type):
     52         pass
     53 
     54     def print_workers_and_shards(self, num_workers, num_shards, num_locked_shards):
     55         pass
     56 
     57     def print_started_test(self, test_name):
     58         pass
     59 
     60     def print_finished_test(self, result, expected, exp_str, got_str):
     61         pass
     62 
     63     def write(self, msg):
     64         pass
     65 
     66     def write_update(self, msg):
     67         pass
     68 
     69     def flush(self):
     70         pass
     71 
     72 
     73 class LockCheckingRunner(LayoutTestRunner):
     74     def __init__(self, port, options, printer, tester, http_lock):
     75         super(LockCheckingRunner, self).__init__(options, port, printer, port.results_directory(), lambda test_name: False)
     76         self._finished_list_called = False
     77         self._tester = tester
     78         self._should_have_http_lock = http_lock
     79 
     80     def handle_finished_list(self, source, list_name, num_tests, elapsed_time):
     81         if not self._finished_list_called:
     82             self._tester.assertEqual(list_name, 'locked_tests')
     83             self._tester.assertTrue(self._remaining_locked_shards)
     84             self._tester.assertTrue(self._has_http_lock is self._should_have_http_lock)
     85 
     86         super(LockCheckingRunner, self).handle_finished_list(source, list_name, num_tests, elapsed_time)
     87 
     88         if not self._finished_list_called:
     89             self._tester.assertEqual(self._remaining_locked_shards, [])
     90             self._tester.assertFalse(self._has_http_lock)
     91             self._finished_list_called = True
     92 
     93 
     94 class LayoutTestRunnerTests(unittest.TestCase):
     95     def _runner(self, port=None):
     96         # FIXME: we shouldn't have to use run_webkit_tests.py to get the options we need.
     97         options = run_webkit_tests.parse_args(['--platform', 'test-mac-snowleopard'])[0]
     98         options.child_processes = '1'
     99 
    100         host = MockHost()
    101         port = port or host.port_factory.get(options.platform, options=options)
    102         return LockCheckingRunner(port, options, FakePrinter(), self, True)
    103 
    104     def _run_tests(self, runner, tests):
    105         test_inputs = [TestInput(test, 6000) for test in tests]
    106         expectations = TestExpectations(runner._port, tests)
    107         runner.run_tests(expectations, test_inputs, set(), num_workers=1, retrying=False)
    108 
    109     def test_interrupt_if_at_failure_limits(self):
    110         runner = self._runner()
    111         runner._options.exit_after_n_failures = None
    112         runner._options.exit_after_n_crashes_or_times = None
    113         test_names = ['passes/text.html', 'passes/image.html']
    114         runner._test_inputs = [TestInput(test_name, 6000) for test_name in test_names]
    115 
    116         run_results = TestRunResults(TestExpectations(runner._port, test_names), len(test_names))
    117         run_results.unexpected_failures = 100
    118         run_results.unexpected_crashes = 50
    119         run_results.unexpected_timeouts = 50
    120         # No exception when the exit_after* options are None.
    121         runner._interrupt_if_at_failure_limits(run_results)
    122 
    123         # No exception when we haven't hit the limit yet.
    124         runner._options.exit_after_n_failures = 101
    125         runner._options.exit_after_n_crashes_or_timeouts = 101
    126         runner._interrupt_if_at_failure_limits(run_results)
    127 
    128         # Interrupt if we've exceeded either limit:
    129         runner._options.exit_after_n_crashes_or_timeouts = 10
    130         self.assertRaises(TestRunInterruptedException, runner._interrupt_if_at_failure_limits, run_results)
    131         self.assertEqual(run_results.results_by_name['passes/text.html'].type, test_expectations.SKIP)
    132         self.assertEqual(run_results.results_by_name['passes/image.html'].type, test_expectations.SKIP)
    133 
    134         runner._options.exit_after_n_crashes_or_timeouts = None
    135         runner._options.exit_after_n_failures = 10
    136         exception = self.assertRaises(TestRunInterruptedException, runner._interrupt_if_at_failure_limits, run_results)
    137 
    138     def test_update_summary_with_result(self):
    139         # Reftests expected to be image mismatch should be respected when pixel_tests=False.
    140         runner = self._runner()
    141         runner._options.pixel_tests = False
    142         test = 'failures/expected/reftest.html'
    143         expectations = TestExpectations(runner._port, tests=[test])
    144         runner._expectations = expectations
    145 
    146         run_results = TestRunResults(expectations, 1)
    147         result = TestResult(test_name=test, failures=[test_failures.FailureReftestMismatchDidNotOccur()], reftest_type=['!='])
    148         runner._update_summary_with_result(run_results, result)
    149         self.assertEqual(1, run_results.expected)
    150         self.assertEqual(0, run_results.unexpected)
    151 
    152         run_results = TestRunResults(expectations, 1)
    153         result = TestResult(test_name=test, failures=[], reftest_type=['=='])
    154         runner._update_summary_with_result(run_results, result)
    155         self.assertEqual(0, run_results.expected)
    156         self.assertEqual(1, run_results.unexpected)
    157 
    158 
    159 class SharderTests(unittest.TestCase):
    160 
    161     test_list = [
    162         "http/tests/websocket/tests/unicode.htm",
    163         "animations/keyframes.html",
    164         "http/tests/security/view-source-no-refresh.html",
    165         "http/tests/websocket/tests/websocket-protocol-ignored.html",
    166         "fast/css/display-none-inline-style-change-crash.html",
    167         "http/tests/xmlhttprequest/supported-xml-content-types.html",
    168         "dom/html/level2/html/HTMLAnchorElement03.html",
    169         "ietestcenter/Javascript/11.1.5_4-4-c-1.html",
    170         "dom/html/level2/html/HTMLAnchorElement06.html",
    171         "perf/object-keys.html",
    172         "virtual/threaded/dir/test.html",
    173         "virtual/threaded/fast/foo/test.html",
    174     ]
    175 
    176     def get_test_input(self, test_file):
    177         return TestInput(test_file, requires_lock=(test_file.startswith('http') or test_file.startswith('perf')))
    178 
    179     def get_shards(self, num_workers, fully_parallel, run_singly, test_list=None, max_locked_shards=1):
    180         port = TestPort(MockSystemHost())
    181         self.sharder = Sharder(port.split_test, max_locked_shards)
    182         test_list = test_list or self.test_list
    183         return self.sharder.shard_tests([self.get_test_input(test) for test in test_list],
    184             num_workers, fully_parallel, run_singly)
    185 
    186     def assert_shards(self, actual_shards, expected_shard_names):
    187         self.assertEqual(len(actual_shards), len(expected_shard_names))
    188         for i, shard in enumerate(actual_shards):
    189             expected_shard_name, expected_test_names = expected_shard_names[i]
    190             self.assertEqual(shard.name, expected_shard_name)
    191             self.assertEqual([test_input.test_name for test_input in shard.test_inputs],
    192                               expected_test_names)
    193 
    194     def test_shard_by_dir(self):
    195         locked, unlocked = self.get_shards(num_workers=2, fully_parallel=False, run_singly=False)
    196 
    197         # Note that although there are tests in multiple dirs that need locks,
    198         # they are crammed into a single shard in order to reduce the # of
    199         # workers hitting the server at once.
    200         self.assert_shards(locked,
    201              [('locked_shard_1',
    202                ['http/tests/security/view-source-no-refresh.html',
    203                 'http/tests/websocket/tests/unicode.htm',
    204                 'http/tests/websocket/tests/websocket-protocol-ignored.html',
    205                 'http/tests/xmlhttprequest/supported-xml-content-types.html',
    206                 'perf/object-keys.html'])])
    207         self.assert_shards(unlocked,
    208             [('virtual/threaded/dir', ['virtual/threaded/dir/test.html']),
    209              ('virtual/threaded/fast/foo', ['virtual/threaded/fast/foo/test.html']),
    210              ('animations', ['animations/keyframes.html']),
    211              ('dom/html/level2/html', ['dom/html/level2/html/HTMLAnchorElement03.html',
    212                                       'dom/html/level2/html/HTMLAnchorElement06.html']),
    213              ('fast/css', ['fast/css/display-none-inline-style-change-crash.html']),
    214              ('ietestcenter/Javascript', ['ietestcenter/Javascript/11.1.5_4-4-c-1.html'])])
    215 
    216     def test_shard_every_file(self):
    217         locked, unlocked = self.get_shards(num_workers=2, fully_parallel=True, max_locked_shards=2, run_singly=False)
    218         self.assert_shards(locked,
    219             [('locked_shard_1',
    220               ['http/tests/websocket/tests/unicode.htm',
    221                'http/tests/security/view-source-no-refresh.html',
    222                'http/tests/websocket/tests/websocket-protocol-ignored.html']),
    223              ('locked_shard_2',
    224               ['http/tests/xmlhttprequest/supported-xml-content-types.html',
    225                'perf/object-keys.html'])]),
    226         self.assert_shards(unlocked,
    227             [('virtual/threaded/dir', ['virtual/threaded/dir/test.html']),
    228              ('virtual/threaded/fast/foo', ['virtual/threaded/fast/foo/test.html']),
    229              ('.', ['animations/keyframes.html']),
    230              ('.', ['fast/css/display-none-inline-style-change-crash.html']),
    231              ('.', ['dom/html/level2/html/HTMLAnchorElement03.html']),
    232              ('.', ['ietestcenter/Javascript/11.1.5_4-4-c-1.html']),
    233              ('.', ['dom/html/level2/html/HTMLAnchorElement06.html'])])
    234 
    235     def test_shard_in_two(self):
    236         locked, unlocked = self.get_shards(num_workers=1, fully_parallel=False, run_singly=False)
    237         self.assert_shards(locked,
    238             [('locked_tests',
    239               ['http/tests/websocket/tests/unicode.htm',
    240                'http/tests/security/view-source-no-refresh.html',
    241                'http/tests/websocket/tests/websocket-protocol-ignored.html',
    242                'http/tests/xmlhttprequest/supported-xml-content-types.html',
    243                'perf/object-keys.html'])])
    244         self.assert_shards(unlocked,
    245             [('unlocked_tests',
    246               ['animations/keyframes.html',
    247                'fast/css/display-none-inline-style-change-crash.html',
    248                'dom/html/level2/html/HTMLAnchorElement03.html',
    249                'ietestcenter/Javascript/11.1.5_4-4-c-1.html',
    250                'dom/html/level2/html/HTMLAnchorElement06.html',
    251                'virtual/threaded/dir/test.html',
    252                'virtual/threaded/fast/foo/test.html'])])
    253 
    254     def test_shard_in_two_has_no_locked_shards(self):
    255         locked, unlocked = self.get_shards(num_workers=1, fully_parallel=False, run_singly=False,
    256              test_list=['animations/keyframe.html'])
    257         self.assertEqual(len(locked), 0)
    258         self.assertEqual(len(unlocked), 1)
    259 
    260     def test_shard_in_two_has_no_unlocked_shards(self):
    261         locked, unlocked = self.get_shards(num_workers=1, fully_parallel=False, run_singly=False,
    262              test_list=['http/tests/websocket/tests/unicode.htm'])
    263         self.assertEqual(len(locked), 1)
    264         self.assertEqual(len(unlocked), 0)
    265 
    266     def test_multiple_locked_shards(self):
    267         locked, unlocked = self.get_shards(num_workers=4, fully_parallel=False, max_locked_shards=2, run_singly=False)
    268         self.assert_shards(locked,
    269             [('locked_shard_1',
    270               ['http/tests/security/view-source-no-refresh.html',
    271                'http/tests/websocket/tests/unicode.htm',
    272                'http/tests/websocket/tests/websocket-protocol-ignored.html']),
    273              ('locked_shard_2',
    274               ['http/tests/xmlhttprequest/supported-xml-content-types.html',
    275                'perf/object-keys.html'])])
    276 
    277         locked, unlocked = self.get_shards(num_workers=4, fully_parallel=False, run_singly=False)
    278         self.assert_shards(locked,
    279             [('locked_shard_1',
    280               ['http/tests/security/view-source-no-refresh.html',
    281                'http/tests/websocket/tests/unicode.htm',
    282                'http/tests/websocket/tests/websocket-protocol-ignored.html',
    283                'http/tests/xmlhttprequest/supported-xml-content-types.html',
    284                'perf/object-keys.html'])])
    285 
    286     def test_virtual_shards(self):
    287         # With run_singly=False, we try to keep all of the tests in a virtual suite together even
    288         # when fully_parallel=True, so that we don't restart every time the command line args change.
    289         locked, unlocked = self.get_shards(num_workers=2, fully_parallel=True, max_locked_shards=2, run_singly=False,
    290                 test_list=['virtual/foo/bar1.html', 'virtual/foo/bar2.html'])
    291         self.assert_shards(unlocked,
    292             [('virtual/foo', ['virtual/foo/bar1.html', 'virtual/foo/bar2.html'])])
    293 
    294         # But, with run_singly=True, we have to restart every time anyway, so we want full parallelism.
    295         locked, unlocked = self.get_shards(num_workers=2, fully_parallel=True, max_locked_shards=2, run_singly=True,
    296                 test_list=['virtual/foo/bar1.html', 'virtual/foo/bar2.html'])
    297         self.assert_shards(unlocked,
    298             [('.', ['virtual/foo/bar1.html']),
    299              ('.', ['virtual/foo/bar2.html'])])
    300