Index: third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations_unittest.py |
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations_unittest.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c87d95d0bac174730882b77682f1ed481ab092d1 |
--- /dev/null |
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations_unittest.py |
@@ -0,0 +1,957 @@ |
+# Copyright 2016 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+import logging |
+import unittest |
+import StringIO |
+from collections import OrderedDict |
+from webkitpy.common.host_mock import MockHost |
+from webkitpy.common.system.filesystem_mock import MockFileSystem |
+from webkitpy.layout_tests.builder_list import BuilderList |
+from webkitpy.layout_tests.port.factory import PortFactory |
+from webkitpy.layout_tests.port.test import LAYOUT_TEST_DIR |
+from webkitpy.layout_tests.update_test_expectations import main |
+from webkitpy.layout_tests.update_test_expectations import RemoveFlakesOMatic |
+ |
+logger = logging.getLogger() |
+logger.level = logging.DEBUG |
+ |
+ |
+class FakeBotTestExpectations(object): |
+ |
+ def __init__(self, results_by_path): |
+ self._results = results_by_path |
+ |
+ def all_results_by_path(self): |
+ return self._results |
+ |
+ |
+class FakeBotTestExpectationsFactory(object): |
+ |
+ def __init__(self): |
+ """ |
+ The distinct results seen in at least one run of the test. |
+ E.g. if the bot results for mytest.html are: |
+ PASS PASS FAIL PASS TIMEOUT |
+ then _all_results_by_builder would be: |
+ { |
+ 'WebKit Linux' : { |
+ 'mytest.html': ['FAIL', 'PASS', 'TIMEOUT'] |
+ } |
+ } |
+ """ |
+ self._all_results_by_builder = {} |
+ |
+ def expectations_for_builder(self, builder): |
+ if builder not in self._all_results_by_builder: |
+ return None |
+ |
+ return FakeBotTestExpectations(self._all_results_by_builder[builder]) |
+ |
+ |
+class FakePortFactory(PortFactory): |
+ |
+ def __init__(self, host): |
+ super(FakePortFactory, self).__init__(host) |
+ self._all_build_types = () |
+ self._all_systems = () |
+ self._configuration_specifier_macros = { |
+ 'mac': ['mac10.10'], |
+ 'win': ['win7'], |
+ 'linux': ['precise'] |
+ } |
+ |
+ def get(self, port_name=None, options=None, **kwargs): |
+ """Returns an object implementing the Port interface. This |
+ fake object will always return the 'test' port factory.""" |
+ port = super(FakePortFactory, self).get('test', None) |
+ port._all_build_types = self._all_build_types |
+ port._all_systems = self._all_systems |
+ port._configuration_specifier_macros = ( |
+ self._configuration_specifier_macros) |
+ return port |
+ |
+ |
+class UpdateTestExpectationsTest(unittest.TestCase): |
+ |
+ def setUp(self): |
+ self._host = MockHost() |
+ self._port = self._host.port_factory.get('test', None) |
+ self._expectation_factory = FakeBotTestExpectationsFactory() |
+ self._flake_remover = RemoveFlakesOMatic(self._host, |
+ self._port, |
+ self._expectation_factory) |
+ self._port._configuration_specifier_macros = { |
+ 'mac': ['mac10.10'], |
+ 'win': ['win7'], |
+ 'linux': ['precise'] |
+ } |
+ filesystem = self._host.filesystem |
+ self._write_tests_into_filesystem(filesystem) |
+ |
+ self._log_output = StringIO.StringIO() |
+ self._stream_handler = logging.StreamHandler(self._log_output) |
+ logger.addHandler(self._stream_handler) |
+ |
+ def tearDown(self): |
+ logger.removeHandler(self._stream_handler) |
+ self._log_output.close() |
+ |
+ def _write_tests_into_filesystem(self, filesystem): |
+ test_list = ['test/a.html', |
+ 'test/b.html', |
+ 'test/c.html', |
+ 'test/d.html', |
+ 'test/e.html', |
+ 'test/f.html', |
+ 'test/g.html'] |
+ for test in test_list: |
+ path = filesystem.join(LAYOUT_TEST_DIR, test) |
+ filesystem.write_binary_file(path, '') |
+ |
+ def _assert_expectations_match(self, expectations, expected_string): |
+ self.assertIsNotNone(expectations) |
+ stringified_expectations = "\n".join( |
+ x.to_string(None) for x in expectations) |
+ expected_string = "\n".join( |
+ x.strip() for x in expected_string.split("\n")) |
+ self.assertEqual(stringified_expectations, expected_string) |
+ |
+ def _parse_expectations(self, expectations): |
+ """Parses a TestExpectation file given as string. |
+ |
+ This function takes a string representing the contents of the |
+ TestExpectations file and parses it, producing the TestExpectations |
+ object and sets it on the Port object where the script will read it |
+ from. |
+ |
+ Args: |
+ expectations: A string containing the contents of the |
+ TestExpectations file to use. |
+ """ |
+ expectations_dict = OrderedDict() |
+ expectations_dict['expectations'] = expectations |
+ self._port.expectations_dict = lambda: expectations_dict |
+ |
+ def _define_builders(self, builders_dict): |
+ """Defines the available builders for the test. |
+ |
+ Args: |
+ builders_dict: A dictionary containing builder names to their |
+ attributes, see BuilderList.__init__ for the format. |
+ """ |
+ self._host.builders = BuilderList(builders_dict) |
+ |
+ def test_dont_remove_non_flakes(self): |
+ """Tests that lines that aren't flaky are not touched. |
+ |
+ Lines are flaky if they contain a PASS as well as at least one other |
+ failing result. |
+ """ |
+ test_expectations_before = """ |
+ # Even though the results show all passing, none of the |
+ # expectations are flaky so we shouldn't remove any. |
+ Bug(test) test/a.html [ Pass ] |
+ Bug(test) test/b.html [ Timeout ] |
+ Bug(test) test/c.html [ Failure Timeout ] |
+ Bug(test) test/d.html [ Rebaseline ] |
+ Bug(test) test/e.html [ NeedsManualRebaseline ] |
+ Bug(test) test/f.html [ NeedsRebaseline ]""" |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('precise', 'x86_64'),) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS"], |
+ "test/c.html": ["PASS", "PASS"], |
+ "test/d.html": ["PASS", "PASS"], |
+ "test/e.html": ["PASS", "PASS"], |
+ "test/f.html": ["PASS", "PASS"], |
+ } |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match( |
+ updated_expectations, test_expectations_before) |
+ |
+ def test_dont_remove_skip(self): |
+ """Tests that lines with Skip are untouched. |
+ |
+ If a line is marked as Skip, it will eventually contain no results, |
+ which is indistinguishable from "All Passing" so don't remove since we |
+ don't know what the results actually are. |
+ """ |
+ test_expectations_before = """ |
+ # Skip expectations should never be removed. |
+ Bug(test) test/a.html [ Skip ] |
+ Bug(test) test/b.html [ Skip ] |
+ Bug(test) test/c.html [ Skip ]""" |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('precise', 'x86_64'),) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS"], |
+ "test/b.html": ["PASS", "IMAGE"], |
+ } |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match( |
+ updated_expectations, test_expectations_before) |
+ |
+ def test_dont_remove_rebaselines(self): |
+ """Tests that lines with rebaseline expectations are untouched. |
+ """ |
+ test_expectations_before = """ |
+ # Even though the results show all passing, none of the |
+ # expectations are flaky so we shouldn't remove any. |
+ Bug(test) test/a.html [ Failure NeedsRebaseline Pass ] |
+ Bug(test) test/b.html [ Failure Pass Rebaseline ] |
+ Bug(test) test/c.html [ Failure NeedsManualRebaseline Pass ]""" |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('precise', 'x86_64'),) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS"], |
+ "test/c.html": ["PASS", "PASS"] |
+ } |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match( |
+ updated_expectations, test_expectations_before) |
+ |
+ def test_all_failure_types(self): |
+ """Tests that all failure types are treated as failure.""" |
+ test_expectations_before = ( |
+ """Bug(test) test/a.html [ Failure Pass ] |
+ Bug(test) test/b.html [ Failure Pass ] |
+ Bug(test) test/c.html [ Failure Pass ] |
+ Bug(test) test/d.html [ Failure Pass ] |
+ # Remove these two since CRASH and TIMEOUT aren't considered |
+ # Failure. |
+ Bug(test) test/e.html [ Failure Pass ] |
+ Bug(test) test/f.html [ Failure Pass ]""") |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('precise', 'x86_64'),) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "IMAGE"], |
+ "test/b.html": ["PASS", "TEXT"], |
+ "test/c.html": ["PASS", "IMAGE+TEXT"], |
+ "test/d.html": ["PASS", "AUDIO"], |
+ "test/e.html": ["PASS", "CRASH"], |
+ "test/f.html": ["PASS", "TIMEOUT"], |
+ } |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match(updated_expectations, ( |
+ """Bug(test) test/a.html [ Failure Pass ] |
+ Bug(test) test/b.html [ Failure Pass ] |
+ Bug(test) test/c.html [ Failure Pass ] |
+ Bug(test) test/d.html [ Failure Pass ]""")) |
+ |
+ def test_basic_one_builder(self): |
+ """Tests basic functionality with a single builder. |
+ |
+ Test that flaky expectations with results from a single bot showing the |
+ expected failure isn't occuring should be removed. Results with failures |
+ of the expected type shouldn't be removed but other kinds of failures |
+ allow removal. |
+ """ |
+ test_expectations_before = ( |
+ """# Remove this since it's passing all runs. |
+ Bug(test) test/a.html [ Failure Pass ] |
+ # Remove this since, although there's a failure, it's not a timeout. |
+ Bug(test) test/b.html [ Pass Timeout ] |
+ # Keep since we have both crashes and passes. |
+ Bug(test) test/c.html [ Crash Pass ]""") |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('precise', 'x86_64'),) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "IMAGE", "PASS"], |
+ "test/c.html": ["PASS", "CRASH", "PASS"], |
+ } |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match(updated_expectations, ( |
+ """# Keep since we have both crashes and passes. |
+ Bug(test) test/c.html [ Crash Pass ]""")) |
+ |
+ def test_all_failure_case(self): |
+ """Tests that results with all failures are not treated as non-flaky.""" |
+ test_expectations_before = ( |
+ """# Keep since it's all failures. |
+ Bug(test) test/a.html [ Failure Pass ]""") |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('precise', 'x86_64'),) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["IMAGE", "IMAGE", "IMAGE"], |
+ } |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match(updated_expectations, ( |
+ """# Keep since it's all failures. |
+ Bug(test) test/a.html [ Failure Pass ]""")) |
+ |
+ def test_empty_test_expectations(self): |
+ """Running on an empty TestExpectations file outputs an empty file.""" |
+ test_expectations_before = "" |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('precise', 'x86_64'),) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ } |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match(updated_expectations, "") |
+ |
+ def test_basic_multiple_builders(self): |
+ """Tests basic functionality with multiple builders.""" |
+ test_expectations_before = ( |
+ """# Remove since it's passing on both builders. |
+ Bug(test) test/a.html [ Failure Pass ] |
+ # Keep since it's failing on the Mac builder. |
+ Bug(test) test/b.html [ Failure Pass ] |
+ # Keep since it's failing on the Linux builder. |
+ Bug(test) test/c.html [ Failure Pass ]""") |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ "WebKit Mac10.10": { |
+ "port_name": "mac-mac10.10", |
+ "specifiers": ['Mac10.10', 'Release'] |
+ }, |
+ }) |
+ |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('mac10.10', 'x86'), |
+ ('precise', 'x86_64')) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS", "PASS"], |
+ "test/c.html": ["AUDIO", "AUDIO", "AUDIO"], |
+ }, |
+ 'WebKit Mac10.10': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS", "IMAGE"], |
+ "test/c.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match(updated_expectations, ( |
+ """# Keep since it's failing on the Mac builder. |
+ Bug(test) test/b.html [ Failure Pass ] |
+ # Keep since it's failing on the Linux builder. |
+ Bug(test) test/c.html [ Failure Pass ]""")) |
+ |
+ def test_multiple_builders_and_platform_specifiers(self): |
+ """Tests correct operation with platform specifiers.""" |
+ test_expectations_before = ( |
+ """# Keep since it's failing on Mac results. |
+ Bug(test) [ Mac ] test/a.html [ Failure Pass ] |
+ # Keep since it's failing on the Windows builder. |
+ Bug(test) [ Linux Win ] test/b.html [ Failure Pass ] |
+ # Remove since it's passing on both Linux and Windows builders. |
+ Bug(test) [ Linux Win ] test/c.html [ Failure Pass ] |
+ # Remove since it's passing on Mac results |
+ Bug(test) [ Mac ] test/d.html [ Failure Pass ]""") |
+ |
+ self._define_builders({ |
+ "WebKit Win7": { |
+ "port_name": "win-win7", |
+ "specifiers": ['Win7', 'Release'] |
+ }, |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ "WebKit Mac10.10": { |
+ "port_name": "mac-mac10.10", |
+ "specifiers": ['Mac10.10', 'Release'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('mac10.10', 'x86'), |
+ ('win7', 'x86'), |
+ ('precise', 'x86_64')) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS", "PASS"], |
+ "test/c.html": ["PASS", "PASS", "PASS"], |
+ "test/d.html": ["IMAGE", "PASS", "PASS"], |
+ }, |
+ 'WebKit Mac10.10': { |
+ "test/a.html": ["PASS", "PASS", "IMAGE"], |
+ "test/b.html": ["PASS", "IMAGE", "PASS"], |
+ "test/c.html": ["PASS", "IMAGE", "PASS"], |
+ "test/d.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ 'WebKit Win7': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["IMAGE", "PASS", "PASS"], |
+ "test/c.html": ["PASS", "PASS", "PASS"], |
+ "test/d.html": ["IMAGE", "PASS", "PASS"], |
+ }, |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match(updated_expectations, ( |
+ """# Keep since it's failing on Mac results. |
+ Bug(test) [ Mac ] test/a.html [ Failure Pass ] |
+ # Keep since it's failing on the Windows builder. |
+ Bug(test) [ Linux Win ] test/b.html [ Failure Pass ]""")) |
+ |
+ def test_debug_release_specifiers(self): |
+ """Tests correct operation of Debug/Release specifiers.""" |
+ test_expectations_before = ( |
+ """# Keep since it fails in debug. |
+ Bug(test) [ Linux ] test/a.html [ Failure Pass ] |
+ # Remove since the failure is in Release, Debug is all PASS. |
+ Bug(test) [ Debug ] test/b.html [ Failure Pass ] |
+ # Keep since there's a failure in Linux Release. |
+ Bug(test) [ Release ] test/c.html [ Failure Pass ] |
+ # Remove since the Release Linux builder is all passing. |
+ Bug(test) [ Release Linux ] test/d.html [ Failure Pass ] |
+ # Remove since all the Linux builders PASS. |
+ Bug(test) [ Linux ] test/e.html [ Failure Pass ]""") |
+ |
+ self._define_builders({ |
+ "WebKit Win7": { |
+ "port_name": "win-win7", |
+ "specifiers": ['Win7', 'Release'] |
+ }, |
+ "WebKit Win7 (dbg)": { |
+ "port_name": "win-win7", |
+ "specifiers": ['Win7', 'Debug'] |
+ }, |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ "WebKit Linux (dbg)": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Debug'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release', 'debug') |
+ self._port._all_systems = (('win7', 'x86'), |
+ ('precise', 'x86_64')) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "IMAGE", "PASS"], |
+ "test/c.html": ["PASS", "IMAGE", "PASS"], |
+ "test/d.html": ["PASS", "PASS", "PASS"], |
+ "test/e.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ 'WebKit Linux (dbg)': { |
+ "test/a.html": ["PASS", "IMAGE", "PASS"], |
+ "test/b.html": ["PASS", "PASS", "PASS"], |
+ "test/c.html": ["PASS", "PASS", "PASS"], |
+ "test/d.html": ["IMAGE", "PASS", "PASS"], |
+ "test/e.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ 'WebKit Win7 (dbg)': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS", "PASS"], |
+ "test/c.html": ["PASS", "PASS", "PASS"], |
+ "test/d.html": ["PASS", "IMAGE", "PASS"], |
+ "test/e.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ 'WebKit Win7': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS", "IMAGE"], |
+ "test/c.html": ["PASS", "PASS", "PASS"], |
+ "test/d.html": ["PASS", "IMAGE", "PASS"], |
+ "test/e.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match(updated_expectations, ( |
+ """# Keep since it fails in debug. |
+ Bug(test) [ Linux ] test/a.html [ Failure Pass ] |
+ # Keep since there's a failure in Linux Release. |
+ Bug(test) [ Release ] test/c.html [ Failure Pass ]""")) |
+ |
+ def test_preserve_comments_and_whitespace(self): |
+ """Tests that comments and whitespace are preserved appropriately. |
+ |
+ Comments and whitespace should be kept unless all the tests grouped |
+ below a comment are removed. In that case the comment block should also |
+ be removed. |
+ |
+ Ex: |
+ # This comment applies to the below tests. |
+ Bug(test) test/a.html [ Failure Pass ] |
+ Bug(test) test/b.html [ Failure Pass ] |
+ |
+ # <some prose> |
+ |
+ # This is another comment. |
+ Bug(test) test/c.html [ Failure Pass ] |
+ |
+ Assuming we removed a.html and c.html we get: |
+ # This comment applies to the below tests. |
+ Bug(test) test/b.html [ Failure Pass ] |
+ |
+ # <some prose> |
+ """ |
+ test_expectations_before = """ |
+ # Comment A - Keep since these aren't part of any test. |
+ # Comment B - Keep since these aren't part of any test. |
+ |
+ # Comment C - Remove since it's a block belonging to a |
+ # Comment D - and a is removed. |
+ Bug(test) test/a.html [ Failure Pass ] |
+ # Comment E - Keep since it's below a. |
+ |
+ |
+ # Comment F - Keep since only b is removed |
+ Bug(test) test/b.html [ Failure Pass ] |
+ Bug(test) test/c.html [ Failure Pass ] |
+ |
+ # Comment G - Should be removed since both d and e will be removed. |
+ Bug(test) test/d.html [ Failure Pass ] |
+ Bug(test) test/e.html [ Failure Pass ]""" |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('precise', 'x86_64'),) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS", "PASS"], |
+ "test/c.html": ["PASS", "IMAGE", "PASS"], |
+ "test/d.html": ["PASS", "PASS", "PASS"], |
+ "test/e.html": ["PASS", "PASS", "PASS"], |
+ } |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match(updated_expectations, ( |
+ """ |
+ # Comment A - Keep since these aren't part of any test. |
+ # Comment B - Keep since these aren't part of any test. |
+ # Comment E - Keep since it's below a. |
+ |
+ |
+ # Comment F - Keep since only b is removed |
+ Bug(test) test/c.html [ Failure Pass ]""")) |
+ |
+ def test_no_results_on_builders(self): |
+ """Tests that we remove a line that has no results on the builders. |
+ |
+ A test that has no results returned from the builders means that all |
+ runs passed or were skipped. A Skip expectation in TestExpectations |
+ shouldn't be removed but otherwise the test is passing. |
+ """ |
+ test_expectations_before = """ |
+ # A Skip expectation probably won't have any results but we |
+ # shouldn't consider those passing so this line should remain. |
+ Bug(test) test/a.html [ Skip ] |
+ # This line shouldn't be removed either since it's not flaky. |
+ Bug(test) test/b.html [ Failure Timeout ] |
+ # The lines below should be removed since they're flaky but all runs |
+ # are passing. |
+ Bug(test) test/c.html [ Failure Pass ] |
+ Bug(test) test/d.html [ Pass Timeout ] |
+ Bug(test) test/e.html [ Crash Pass ]""" |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ }) |
+ self._port._all_build_types = ('release',) |
+ self._port._all_systems = (('precise', 'x86_64'),) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ } |
+ } |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ self._assert_expectations_match(updated_expectations, """ |
+ # A Skip expectation probably won't have any results but we |
+ # shouldn't consider those passing so this line should remain. |
+ Bug(test) test/a.html [ Skip ] |
+ # This line shouldn't be removed either since it's not flaky. |
+ Bug(test) test/b.html [ Failure Timeout ]""") |
+ |
+ def test_log_missing_builders(self): |
+ """Tests that we emit the appropriate error for a missing builder. |
+ |
+ If a TestExpectation has a matching configuration what we can't resolve |
+ to a builder we should emit an Error. |
+ """ |
+ |
+ test_expectations_before = """ |
+ Bug(test) [ Win ] test/a.html [ Failure Pass ] |
+ Bug(test) [ Linux ] test/b.html [ Failure Pass ] |
+ # This one shouldn't emit an error since it's not flaky, we don't |
+ # have to check the builder results. |
+ Bug(test) test/c.html [ Failure ] |
+ Bug(test) test/d.html [ Failure Pass ] |
+ # This one shouldn't emit an error since it will only match the |
+ # existing Linux Release configuration |
+ Bug(test) [ Linux Release ] test/e.html [ Failure Pass ]""" |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ }) |
+ |
+ # Three errors should be emitted: |
+ # (1) There's no Windows builders so a.html will emit an error on the |
+ # first missing one. |
+ # (2) There's no Linux debug builder so b.html will emit an error. |
+ # (3) c.html is missing will match both the Windows and Linux dbg |
+ # builders which are missing so it'll emit an error on the first one. |
+ self._port._all_build_types = ('release', 'debug') |
+ self._port._all_systems = (('win7', 'x86'), |
+ ('precise', 'x86_64')) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS", "PASS"], |
+ "test/c.html": ["PASS", "IMAGE", "PASS"], |
+ "test/d.html": ["PASS", "PASS", "PASS"], |
+ "test/e.html": ["PASS", "IMAGE", "PASS"], |
+ } |
+ } |
+ |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ expected_errors = '\n'.join([ |
+ 'Failed to get builder for config [win7, x86, release]', |
+ 'Failed to get builder for config [precise, x86_64, debug]', |
+ 'Failed to get builder for config [win7, x86, release]', |
+ '']) |
+ self.assertEqual(self._log_output.getvalue(), expected_errors) |
+ |
+ # Also make sure we didn't remove any lines if some builders were |
+ # missing. |
+ self._assert_expectations_match( |
+ updated_expectations, test_expectations_before) |
+ |
+ def test_log_missing_results(self): |
+ """Tests that we emit the appropriate error for missing results. |
+ |
+ If the results dictionary we download from the builders is missing the |
+ results from one of the builders we matched we should have logged an |
+ error. |
+ """ |
+ test_expectations_before = """ |
+ Bug(test) [ Linux ] test/a.html [ Failure Pass ] |
+ # This line won't emit an error since the Linux Release results |
+ # exist. |
+ Bug(test) [ Linux Release ] test/b.html [ Failure Pass ] |
+ Bug(test) [ Release ] test/c.html [ Failure Pass ] |
+ # This line is not flaky so we shouldn't even check the results. |
+ Bug(test) [ Linux ] test/d.html [ Failure ]""" |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ "WebKit Linux (dbg)": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Debug'] |
+ }, |
+ "WebKit Win7": { |
+ "port_name": "win-win7", |
+ "specifiers": ['Win7', 'Release'] |
+ }, |
+ "WebKit Win7 (dbg)": { |
+ "port_name": "win-win7", |
+ "specifiers": ['Win7', 'Debug'] |
+ }, |
+ }) |
+ |
+ # Two warnings and two errors should be emitted: |
+ # (1) A warning since the results don't contain anything for the Linux |
+ # (dbg) builder |
+ # (2) A warning since the results don't contain anything for the Win |
+ # release builder |
+ # (3) The first line needs and is missing results for Linux (dbg). |
+ # (4) The third line needs and is missing results for Win Release. |
+ self._port._all_build_types = ('release', 'debug') |
+ self._port._all_systems = (('win7', 'x86'), |
+ ('precise', 'x86_64')) |
+ |
+ self._parse_expectations(test_expectations_before) |
+ self._expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "IMAGE", "PASS"], |
+ "test/c.html": ["PASS", "PASS", "PASS"], |
+ "test/d.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ 'WebKit Win7 (dbg)': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS", "PASS"], |
+ "test/c.html": ["PASS", "PASS", "PASS"], |
+ "test/d.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ } |
+ |
+ updated_expectations = ( |
+ self._flake_remover.get_updated_test_expectations()) |
+ expected_errors = '\n'.join([ |
+ 'Downloaded results are missing results for builder "WebKit Linux (dbg)"', |
+ 'Downloaded results are missing results for builder "WebKit Win7"', |
+ 'Failed to find results for builder "WebKit Linux (dbg)"', |
+ 'Failed to find results for builder "WebKit Win7"', |
+ '']) |
+ self.assertEqual(self._log_output.getvalue(), expected_errors) |
+ |
+ # Also make sure we didn't remove any lines if some builders were |
+ # missing. |
+ self._assert_expectations_match( |
+ updated_expectations, test_expectations_before) |
+ |
+ def test_harness_updates_file(self): |
+ """Tests that the call harness updates the TestExpectations file. |
+ """ |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ "WebKit Linux (dbg)": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Debug'] |
+ }, |
+ }) |
+ |
+ # Setup the mock host and port. |
+ host = MockHost() |
+ host.port_factory = FakePortFactory(host) |
+ host.port_factory._all_build_types = ('release', 'debug') |
+ host.port_factory._all_systems = (('precise', 'x86_64'),) |
+ |
+ # Write out a fake TestExpectations file. |
+ test_expectation_path = ( |
+ host.port_factory.get().path_to_generic_test_expectations_file()) |
+ test_expectations = """ |
+ # Remove since passing on both bots. |
+ Bug(test) [ Linux ] test/a.html [ Failure Pass ] |
+ # Keep since there's a failure on release bot. |
+ Bug(test) [ Linux Release ] test/b.html [ Failure Pass ] |
+ # Remove since it's passing on both builders. |
+ Bug(test) test/c.html [ Failure Pass ] |
+ # Keep since there's a failure on debug bot. |
+ Bug(test) [ Linux ] test/d.html [ Failure ]""" |
+ files = { |
+ test_expectation_path: test_expectations |
+ } |
+ host.filesystem = MockFileSystem(files) |
+ self._write_tests_into_filesystem(host.filesystem) |
+ |
+ # Write out the fake builder bot results. |
+ expectation_factory = FakeBotTestExpectationsFactory() |
+ expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "IMAGE", "PASS"], |
+ "test/c.html": ["PASS", "PASS", "PASS"], |
+ "test/d.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ 'WebKit Linux (dbg)': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ "test/b.html": ["PASS", "PASS", "PASS"], |
+ "test/c.html": ["PASS", "PASS", "PASS"], |
+ "test/d.html": ["IMAGE", "PASS", "PASS"], |
+ }, |
+ } |
+ |
+ main(host, expectation_factory, []) |
+ |
+ self.assertEqual(host.filesystem.files[test_expectation_path], ( |
+ """ # Keep since there's a failure on release bot. |
+ Bug(test) [ Linux Release ] test/b.html [ Failure Pass ] |
+ # Keep since there's a failure on debug bot. |
+ Bug(test) [ Linux ] test/d.html [ Failure ]""")) |
+ |
+ def test_harness_no_expectations(self): |
+ """Tests that a warning is outputted if the TestExpectations file |
+ doesn't exist. |
+ """ |
+ |
+ # Setup the mock host and port. |
+ host = MockHost() |
+ host.port_factory = FakePortFactory(host) |
+ |
+ # Write the test file but not the TestExpectations file. |
+ test_expectation_path = ( |
+ host.port_factory.get().path_to_generic_test_expectations_file()) |
+ host.filesystem = MockFileSystem() |
+ self._write_tests_into_filesystem(host.filesystem) |
+ |
+ # Write out the fake builder bot results. |
+ expectation_factory = FakeBotTestExpectationsFactory() |
+ expectation_factory._all_results_by_builder = {} |
+ |
+ self.assertFalse(host.filesystem.isfile(test_expectation_path)) |
+ |
+ main(host, expectation_factory, []) |
+ |
+ expected_warning = ( |
+ "Didn't find generic expectations file at: " + |
+ test_expectation_path + "\n") |
+ self.assertEqual(self._log_output.getvalue(), expected_warning) |
+ self.assertFalse(host.filesystem.isfile(test_expectation_path)) |
+ |
+ def test_harness_remove_all(self): |
+ """Tests that removing all expectations doesn't delete the file. |
+ |
+ Make sure we're prepared for the day when we exterminated flakes. |
+ """ |
+ |
+ self._define_builders({ |
+ "WebKit Linux": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Release'] |
+ }, |
+ "WebKit Linux (dbg)": { |
+ "port_name": "linux-precise", |
+ "specifiers": ['Precise', 'Debug'] |
+ }, |
+ }) |
+ |
+ # Setup the mock host and port. |
+ host = MockHost() |
+ host.port_factory = FakePortFactory(host) |
+ host.port_factory._all_build_types = ('release', 'debug') |
+ host.port_factory._all_systems = (('precise', 'x86_64'),) |
+ |
+ # Write out a fake TestExpectations file. |
+ test_expectation_path = ( |
+ host.port_factory.get().path_to_generic_test_expectations_file()) |
+ test_expectations = """ |
+ # Remove since passing on both bots. |
+ Bug(test) [ Linux ] test/a.html [ Failure Pass ]""" |
+ |
+ files = { |
+ test_expectation_path: test_expectations |
+ } |
+ host.filesystem = MockFileSystem(files) |
+ self._write_tests_into_filesystem(host.filesystem) |
+ |
+ # Write out the fake builder bot results. |
+ expectation_factory = FakeBotTestExpectationsFactory() |
+ expectation_factory._all_results_by_builder = { |
+ 'WebKit Linux': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ 'WebKit Linux (dbg)': { |
+ "test/a.html": ["PASS", "PASS", "PASS"], |
+ }, |
+ } |
+ |
+ main(host, expectation_factory, []) |
+ |
+ self.assertTrue(host.filesystem.isfile(test_expectation_path)) |
+ self.assertEqual(host.filesystem.files[test_expectation_path], '') |