Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(415)

Unified Diff: third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations.py

Issue 2895863003: Rename update-test-expectations -> update-flaky-expectations (Closed)
Patch Set: Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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
deleted file mode 100644
index 0ade6bf2c0acf25754a79f44199b5dcf8039bbf1..0000000000000000000000000000000000000000
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/update_test_expectations.py
+++ /dev/null
@@ -1,344 +0,0 @@
-# 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.
-
-"""Updates TestExpectations based on results in builder bots.
-
-Scans the TestExpectations file and uses results from actual builder bots runs
-to remove tests that are marked as flaky but don't fail in the specified way.
-
-E.g. If a test has this expectation:
- bug(test) fast/test.html [ Failure Pass ]
-
-And all the runs on builders have passed the line will be removed.
-
-Additionally, the runs don't all have to be Passing to remove the line;
-as long as the non-Passing results are of a type not specified in the
-expectation this line will be removed. For example, if this is the
-expectation:
-
- bug(test) fast/test.html [ Crash Pass ]
-
-But the results on the builders show only Passes and Timeouts, the line
-will be removed since there's no Crash results.
-"""
-
-import argparse
-import logging
-import webbrowser
-
-from webkitpy.layout_tests.models.test_expectations import TestExpectations
-from webkitpy.tool.commands.flaky_tests import FlakyTests
-
-_log = logging.getLogger(__name__)
-
-
-def main(host, bot_test_expectations_factory, argv):
- parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.RawTextHelpFormatter)
- parser.add_argument('--verbose', '-v', action='store_true', default=False, help='enable more verbose logging')
- parser.add_argument('--show-results',
- '-s',
- action='store_true',
- default=False,
- help='Open results dashboard for all removed lines')
- args = parser.parse_args(argv)
-
- logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO, format='%(levelname)s: %(message)s')
-
- port = host.port_factory.get()
- expectations_file = port.path_to_generic_test_expectations_file()
- if not host.filesystem.isfile(expectations_file):
- _log.warning("Didn't find generic expectations file at: " + expectations_file)
- return 1
-
- remove_flakes_o_matic = RemoveFlakesOMatic(host,
- port,
- bot_test_expectations_factory,
- webbrowser)
-
- test_expectations = remove_flakes_o_matic.get_updated_test_expectations()
-
- if args.show_results:
- remove_flakes_o_matic.show_removed_results()
-
- remove_flakes_o_matic.write_test_expectations(test_expectations,
- expectations_file)
- return 0
-
-
-class RemoveFlakesOMatic(object):
-
- def __init__(self, host, port, bot_test_expectations_factory, browser):
- self._host = host
- self._port = port
- self._expectations_factory = bot_test_expectations_factory
- self._builder_results_by_path = {}
- self._browser = browser
- self._expectations_to_remove_list = None
-
- 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 are also some rules about when
- 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.
- """
- expectations = test_expectation_line.expectations
- if len(expectations) < 2:
- return False
-
- # Don't check lines that have expectations like NeedsRebaseline or Skip.
- if self._has_unstrippable_expectations(expectations):
- return False
-
- # Don't check lines unless they're flaky. i.e. At least one expectation is a PASS.
- if not self._has_pass_expectation(expectations):
- return False
-
- # Don't check lines that have expectations for directories, since
- # the flakiness of all sub-tests isn't as easy to check.
- if self._port.test_isdir(test_expectation_line.name):
- return False
-
- # The line can be deleted if the only expectation on the line that appears in the actual
- # results is the PASS expectation.
- builders_checked = []
- 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.debug('No builder with config %s', config)
- # For many configurations, there is no matching builder in
- # webkitpy/common/config/builders.py. We ignore these
- # configurations and make decisions based only on configurations
- # with actual builders.
- continue
-
- builders_checked.append(builder_name)
-
- 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
-
- if builders_checked:
- _log.debug('Checked builders:\n %s', '\n '.join(builders_checked))
- else:
- _log.warning('No matching builders for line, deleting line.')
- _log.info('Deleting line "%s"', test_expectation_line.original_string.strip())
- 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'], then this method would
- return [Pass Failure] since the Failure expectation is satisfied by 'TEXT', Pass
- by 'PASS' but Crash doesn't appear in the results.
-
- 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 occurred in the results.
- """
- # TODO(bokan): Does this not exist in a more central place?
- 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.
-
- 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_builder_results_by_path(self):
- """Returns a dictionary of results for each builder.
-
- Returns a dictionary where each key is a builder and value is a dictionary containing
- the distinct results for each test. E.g.
-
- {
- 'WebKit Linux Precise': {
- 'test1.html': ['PASS', 'IMAGE'],
- 'test2.html': ['PASS'],
- },
- 'WebKit Mac10.10': {
- 'test1.html': ['PASS', 'IMAGE'],
- 'test2.html': ['PASS', 'TEXT'],
- }
- }
- """
- builder_results_by_path = {}
- for builder_name in self._host.builders.all_continuous_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.warning('Downloaded results are missing results for builder "%s"', builder_name)
- continue
-
- builder_results_by_path[builder_name] = (
- expectations_for_builder.all_results_by_path()
- )
- return builder_results_by_path
-
- def _remove_associated_comments_and_whitespace(self, expectations, removed_index):
- """Removes comments and whitespace from an empty expectation block.
-
- If the removed expectation was the last in a block of expectations, this method
- will remove any associated comments and whitespace.
-
- Args:
- expectations: A list of TestExpectationLine objects to be modified.
- removed_index: The index in the above list that was just removed.
- """
- was_last_expectation_in_block = (removed_index == len(expectations)
- or expectations[removed_index].is_whitespace()
- or expectations[removed_index].is_comment())
-
- # If the line immediately below isn't another expectation, then the block of
- # expectations definitely isn't empty so we shouldn't remove their associated comments.
- if not was_last_expectation_in_block:
- return
-
- did_remove_whitespace = False
-
- # We may have removed the last expectation in a block. Remove any whitespace above.
- while removed_index > 0 and expectations[removed_index - 1].is_whitespace():
- removed_index -= 1
- expectations.pop(removed_index)
- did_remove_whitespace = True
-
- # If we did remove some whitespace then we shouldn't remove any comments above it
- # since those won't have belonged to this expectation block. For example, commented
- # out expectations, or a section header.
- if did_remove_whitespace:
- return
-
- # Remove all comments above the removed line.
- while removed_index > 0 and expectations[removed_index - 1].is_comment():
- removed_index -= 1
- expectations.pop(removed_index)
-
- # Remove all whitespace above the comments.
- while removed_index > 0 and expectations[removed_index - 1].is_whitespace():
- removed_index -= 1
- expectations.pop(removed_index)
-
- def _expectations_to_remove(self):
- """Computes and returns the expectation lines that should be removed.
-
- Returns:
- A list of TestExpectationLine objects for lines that can be removed
- from the test expectations file. The result is memoized so that
- subsequent calls will not recompute the result.
- """
- if self._expectations_to_remove_list is not None:
- return self._expectations_to_remove_list
-
- self._builder_results_by_path = self._get_builder_results_by_path()
- self._expectations_to_remove_list = []
- test_expectations = TestExpectations(self._port, include_overrides=False).expectations()
-
- for expectation in test_expectations:
- if self._can_delete_line(expectation):
- self._expectations_to_remove_list.append(expectation)
-
- return self._expectations_to_remove_list
-
- def get_updated_test_expectations(self):
- """Filters out passing lines from TestExpectations file.
-
- Reads the current TestExpectations file and, using results from the
- build bots, removes lines that are passing. That is, removes lines that
- were not needed to keep the bots green.
-
- Returns:
- A TestExpectations object with the passing lines filtered out.
- """
-
- test_expectations = TestExpectations(self._port, include_overrides=False).expectations()
- for expectation in self._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.
- self._remove_associated_comments_and_whitespace(test_expectations, index)
-
- return test_expectations
-
- def show_removed_results(self):
- """Opens removed lines in the results dashboard.
-
- Opens the results dashboard in the browser, showing all the tests for lines that the script
- removed from the TestExpectations file and allowing the user to manually confirm the
- results.
- """
- removed_test_names = ','.join(x.name for x in self._expectations_to_remove())
- url = FlakyTests.FLAKINESS_DASHBOARD_URL % removed_test_names
-
- _log.info('Opening results dashboard: ' + url)
- self._browser.open(url)
-
- def write_test_expectations(self, test_expectations, test_expectations_file):
- """Writes the given TestExpectations object to the filesystem.
-
- Args:
- test_expectations: The TestExpectations object to write.
- test_expectations_file: The full file path of the Blink
- TestExpectations file. This file will be overwritten.
- """
- self._host.filesystem.write_text_file(
- test_expectations_file,
- TestExpectations.list_to_string(test_expectations, reconstitute_only_these=[]))

Powered by Google App Engine
This is Rietveld 408576698