Chromium Code Reviews| 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..655d6a4b9301680fc5f4afe50bfdf8ab05df3e02 |
| --- /dev/null |
| +++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations_unittest.py |
| @@ -0,0 +1,330 @@ |
| +# 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 |
|
qyearsley
2016/07/08 00:06:21
Nit: In most of the files, there's a blank line be
bokan
2016/07/08 14:29:49
Done.
|
| +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.""" |
|
qyearsley
2016/07/08 00:06:21
I personally think that docstrings with a one-line
bokan
2016/07/08 14:29:49
Done.
|
| + 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) |
|
Dirk Pranke
2016/07/06 21:51:44
as suggested in the earlier CL, update these names
bokan
2016/07/07 22:08:32
Done.
|
| + 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) |
|
qyearsley
2016/07/06 23:18:04
Note: I wonder if it would be nice to change TestE
bokan
2016/07/07 22:08:32
It turns out this is the only use of TestExpectati
|
| + 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. |
| + """ |
|
qyearsley
2016/07/06 23:18:04
Nit: end quote can go on the line above.
bokan
2016/07/07 22:08:32
Done. Does this go for multiline comments too or j
qyearsley
2016/07/08 00:06:21
The Python style guide for Blink just say to use P
bokan
2016/07/08 14:29:49
Thanks!
|
| + test_expectations_before = """ |
| + # Even though the results show all passing, none of the |
|
qyearsley
2016/07/08 00:06:21
I assume it's not a problem that these lines start
bokan
2016/07/08 14:29:49
I think this is easier to read and the assert_expe
|
| + # 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_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_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)) |