Chromium Code Reviews| Index: third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations.py |
| diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations.py |
| index ed1be7297a62b05e1315ba677989d72c965d3e50..0fb450abee8a9f72fb010cd7a09bd1880e86e76c 100644 |
| --- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations.py |
| +++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations.py |
| @@ -62,6 +62,111 @@ class RemoveFlakesOMatic(object): |
| self._expectations_factory = bot_test_expectations_factory |
| self.builder_results_by_path = {} |
| + def _can_delete_line(self, test_expectation_line): |
| + """Returns whether a given line in the expectations can be removed. |
| + |
| + Uses results from builder bots to determine if a given line is stale and |
| + can safely be removed from the TestExpectations file. (i.e. remove if |
| + the bots show that it's not flaky.) There's also some rules about when |
|
qyearsley
2016/07/08 17:55:03
There's also some rules -> There are also some rul
bokan
2016/07/08 21:13:00
Done.
|
| + not to remove lines (e.g. never remove lines with Rebaseline |
| + expectations, don't remove non-flaky expectations, etc.) |
| + |
| + Args: |
| + test_expectation_line (TestExpectationLine): A line in the test |
| + expectation file to test for possible removal. |
| + |
| + Returns: True if the line can be removed, False otherwise. |
|
qyearsley
2016/07/08 17:55:04
To be consistent with the docstrings below, this c
bokan
2016/07/08 21:13:00
Done.
|
| + """ |
| + expectations = test_expectation_line.expectations |
| + if len(expectations) < 2: |
| + return False |
| + |
| + if self._has_unstrippable_expectations(expectations): |
| + return False |
| + |
| + if not self._has_pass_expectation(expectations): |
| + return False |
|
qyearsley
2016/07/08 17:55:03
Is "PASS" an "unstrippable" expectation? Would it
bokan
2016/07/08 21:13:00
No, it's a bit subtle but has_unstrippable_expecta
|
| + |
| + # The line can be deleted if the only expectation on the line that appears in the actual |
| + # results is the PASS expectation. |
| + for config in test_expectation_line.matching_configurations: |
| + builder_name = self._host.builders.builder_name_for_specifiers(config.version, config.build_type) |
| + |
| + if not builder_name: |
| + _log.error('Failed to get builder for config [%s, %s, %s]' % (config.version, config.architecture, config.build_type)) |
|
qyearsley
2016/07/08 17:55:03
For logging methods, if you give more than one arg
bokan
2016/07/08 21:13:00
Done.
|
| + # TODO(bokan): Matching configurations often give us bots that don't have a |
| + # builder in builders.py's exact_matches. Should we ignore those or be conservative |
| + # and assume we need these expectations to make a decision? |
| + return False |
| + |
| + if builder_name not in self.builder_results_by_path.keys(): |
| + _log.error('Failed to find results for builder "%s"' % builder_name) |
| + return False |
| + |
| + results_by_path = self.builder_results_by_path[builder_name] |
| + |
| + # No results means the tests were all skipped or all results are passing. |
| + if test_expectation_line.path not in results_by_path.keys(): |
| + continue |
| + |
| + results_for_single_test = results_by_path[test_expectation_line.path] |
| + |
| + if self._expectations_that_were_met(test_expectation_line, results_for_single_test) != set(['PASS']): |
| + return False |
| + |
| + return True |
| + |
| + def _has_pass_expectation(self, expectations): |
| + return 'PASS' in expectations |
| + |
| + def _expectations_that_were_met(self, test_expectation_line, results_for_single_test): |
| + """Returns the set of expectations that appear in the given results. |
| + |
| + e.g. If the test expectations is: |
| + bug(test) fast/test.html [Crash Failure Pass] |
| + |
| + And the results are ['TEXT', 'PASS', 'PASS', 'TIMEOUT'] |
| + |
| + This method would return [Pass Failure] |
|
qyearsley
2016/07/08 17:55:03
Maybe the "flowiness" of this section could be imp
bokan
2016/07/08 21:13:00
Done.
|
| + |
| + Args: |
| + test_expectation_line: A TestExpectationLine object |
| + results_for_single_test: A list of result strings. |
| + e.g. ['IMAGE', 'IMAGE', 'PASS'] |
| + |
| + Returns: |
| + A set containing expectations that occured in the results. |
| + """ |
| + # TODO(bokan): Does this not exist in a more central place? |
|
qyearsley
2016/07/08 17:55:03
Good question, it seems like it should, so if it d
bokan
2016/07/08 21:13:00
Ok, I'll leave this TODO here for now and either f
|
| + def replace_failing_with_fail(expectation): |
| + if expectation in ('TEXT', 'IMAGE', 'IMAGE+TEXT', 'AUDIO'): |
| + return 'FAIL' |
| + else: |
| + return expectation |
| + |
| + actual_results = {replace_failing_with_fail(r) for r in results_for_single_test} |
| + |
| + return set(test_expectation_line.expectations) & actual_results |
| + |
| + def _has_unstrippable_expectations(self, expectations): |
| + """ Returns whether any of the given expectations are considered unstrippable. |
|
qyearsley
2016/07/08 17:55:04
Extra space character can be removed.
bokan
2016/07/08 21:13:00
Done.
|
| + |
| + Unstrippable expectations are those which should stop a line from being |
| + removed regardless of builder bot results. |
| + |
| + Args: |
| + expectations: A list of string expectations. |
| + E.g. ['PASS', 'FAIL' 'CRASH'] |
| + |
| + Returns: |
| + True if at least one of the expectations is unstrippable. False |
| + otherwise. |
| + """ |
| + unstrippable_expectations = ('REBASELINE', 'NEEDSREBASELINE', |
| + 'NEEDSMANUALREBASELINE', 'SLOW', |
| + 'SKIP') |
| + return any(s in expectations for s in unstrippable_expectations) |
| + |
| def get_updated_test_expectations(self): |
| """Filters out passing lines from TestExpectations file. |
| @@ -72,7 +177,53 @@ class RemoveFlakesOMatic(object): |
| Returns: A TestExpectations object with the passing lines filtered out. |
| """ |
| test_expectations = TestExpectations(self._port, include_overrides=False).expectations() |
| - # TODO: Filter the expectations based on results. |
| + |
| + self.builder_results_by_path = {} |
| + for builder_name in self._host.builders.all_builder_names(): |
| + expectations_for_builder = ( |
| + self._expectations_factory.expectations_for_builder(builder_name) |
| + ) |
| + |
| + if not expectations_for_builder: |
| + # This is not fatal since we may not need to check these |
| + # results. If we do need these results we'll log an error later |
| + # when trying to check against them. |
| + _log.warn('Downloaded results are missing results for builder "%s"' % builder_name) |
| + continue |
| + |
| + self.builder_results_by_path[builder_name] = ( |
| + expectations_for_builder.all_results_by_path() |
| + ) |
|
qyearsley
2016/07/08 17:55:04
This block of code (lines 181-196), which builds u
bokan
2016/07/08 21:13:00
Done.
|
| + |
| + expectations_to_remove = [] |
| + |
| + for expectation in test_expectations: |
| + if self._can_delete_line(expectation): |
| + expectations_to_remove.append(expectation) |
| + |
| + for expectation in expectations_to_remove: |
| + index = test_expectations.index(expectation) |
| + test_expectations.remove(expectation) |
| + |
| + # Remove associated comments and whitespace if we've removed the last expectation under |
| + # a comment block. Only remove a comment block if it's not separated from the test |
| + # expectation line by whitespace. |
| + if index == len(test_expectations) or test_expectations[index].is_whitespace() or test_expectations[index].is_comment(): |
| + removed_whitespace = False |
| + while index and test_expectations[index - 1].is_whitespace(): |
| + index = index - 1 |
|
qyearsley
2016/07/08 17:55:03
`index = index - 1` can also be written as `index
bokan
2016/07/08 21:13:00
Done.
|
| + test_expectations.pop(index) |
| + removed_whitespace = True |
| + |
| + if not removed_whitespace: |
| + while index and test_expectations[index - 1].is_comment(): |
| + index = index - 1 |
| + test_expectations.pop(index) |
| + |
| + while index and test_expectations[index - 1].is_whitespace(): |
| + index = index - 1 |
| + test_expectations.pop(index) |
|
qyearsley
2016/07/08 17:55:03
This block of code (removing associated comments a
bokan
2016/07/08 21:13:00
Done.
|
| + |
| return test_expectations |
| def write_test_expectations(self, test_expectations, test_expectations_file): |