| OLD | NEW |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Updates TestExpectations based on results in builder bots. | 5 """Updates TestExpectations based on results in builder bots. |
| 6 | 6 |
| 7 Scans the TestExpectations file and uses results from actual builder bots runs | 7 Scans the TestExpectations file and uses results from actual builder bots runs |
| 8 to remove tests that are marked as flaky but don't fail in the specified way. | 8 to remove tests that are marked as flaky but don't fail in the specified way. |
| 9 | 9 |
| 10 E.g. If a test has this expectation: | 10 E.g. If a test has this expectation: |
| 11 bug(test) fast/test.html [ Failure Pass ] | 11 bug(test) fast/test.html [ Failure Pass ] |
| 12 | 12 |
| 13 And all the runs on builders have passed the line will be removed. | 13 And all the runs on builders have passed the line will be removed. |
| 14 | 14 |
| 15 Additionally, the runs don't all have to be Passing to remove the line; | 15 Additionally, the runs don't all have to be Passing to remove the line; |
| 16 as long as the non-Passing results are of a type not specified in the | 16 as long as the non-Passing results are of a type not specified in the |
| 17 expectation this line will be removed. For example, if this is the | 17 expectation this line will be removed. For example, if this is the |
| 18 expectation: | 18 expectation: |
| 19 | 19 |
| 20 bug(test) fast/test.html [ Crash Pass ] | 20 bug(test) fast/test.html [ Crash Pass ] |
| 21 | 21 |
| 22 But the results on the builders show only Passes and Timeouts, the line | 22 But the results on the builders show only Passes and Timeouts, the line |
| 23 will be removed since there's no Crash results. | 23 will be removed since there's no Crash results. |
| 24 """ | 24 """ |
| 25 | 25 |
| 26 import argparse | 26 import argparse |
| 27 import logging | 27 import logging |
| 28 import webbrowser | 28 import webbrowser |
| 29 | 29 |
| 30 from webkitpy.layout_tests.models.test_expectations import CHROMIUM_BUG_PREFIX |
| 30 from webkitpy.layout_tests.models.test_expectations import TestExpectations | 31 from webkitpy.layout_tests.models.test_expectations import TestExpectations |
| 31 from webkitpy.tool.commands.flaky_tests import FlakyTests | 32 from webkitpy.tool.commands.flaky_tests import FlakyTests |
| 32 | 33 |
| 33 _log = logging.getLogger(__name__) | 34 _log = logging.getLogger(__name__) |
| 34 | 35 |
| 35 | 36 |
| 36 def main(host, bot_test_expectations_factory, argv): | 37 def main(host, bot_test_expectations_factory, argv): |
| 37 parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.Ra
wTextHelpFormatter) | 38 parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.Ra
wTextHelpFormatter) |
| 38 parser.add_argument('--verbose', '-v', action='store_true', default=False, h
elp='enable more verbose logging') | 39 parser.add_argument('--verbose', '-v', action='store_true', default=False, h
elp='enable more verbose logging') |
| 39 parser.add_argument('--show-results', | 40 parser.add_argument('--show-results', |
| 40 '-s', | 41 '-s', |
| 41 action='store_true', | 42 action='store_true', |
| 42 default=False, | 43 default=False, |
| 43 help='Open results dashboard for all removed lines') | 44 help='Open results dashboard for all removed lines') |
| 44 args = parser.parse_args(argv) | 45 args = parser.parse_args(argv) |
| 45 | 46 |
| 46 logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO, f
ormat='%(levelname)s: %(message)s') | 47 logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO, f
ormat='%(levelname)s: %(message)s') |
| 47 | 48 |
| 48 port = host.port_factory.get() | 49 port = host.port_factory.get() |
| 49 expectations_file = port.path_to_generic_test_expectations_file() | 50 expectations_file = port.path_to_generic_test_expectations_file() |
| 50 if not host.filesystem.isfile(expectations_file): | 51 if not host.filesystem.isfile(expectations_file): |
| 51 _log.warning("Didn't find generic expectations file at: " + expectations
_file) | 52 _log.warning("Didn't find generic expectations file at: " + expectations
_file) |
| 52 return 1 | 53 return 1 |
| 53 | 54 |
| 54 remove_flakes_o_matic = RemoveFlakesOMatic(host, | 55 remove_flakes_o_matic = RemoveFlakesOMatic( |
| 55 port, | 56 host, port, bot_test_expectations_factory, webbrowser) |
| 56 bot_test_expectations_factory, | |
| 57 webbrowser) | |
| 58 | 57 |
| 59 test_expectations = remove_flakes_o_matic.get_updated_test_expectations() | 58 test_expectations = remove_flakes_o_matic.get_updated_test_expectations() |
| 60 | 59 |
| 61 if args.show_results: | 60 if args.show_results: |
| 62 remove_flakes_o_matic.show_removed_results() | 61 remove_flakes_o_matic.show_removed_results() |
| 63 | 62 |
| 64 remove_flakes_o_matic.write_test_expectations(test_expectations, | 63 remove_flakes_o_matic.write_test_expectations(test_expectations, expectation
s_file) |
| 65 expectations_file) | 64 remove_flakes_o_matic.print_suggested_commit_description() |
| 66 return 0 | 65 return 0 |
| 67 | 66 |
| 68 | 67 |
| 69 class RemoveFlakesOMatic(object): | 68 class RemoveFlakesOMatic(object): |
| 70 | 69 |
| 71 def __init__(self, host, port, bot_test_expectations_factory, browser): | 70 def __init__(self, host, port, bot_test_expectations_factory, browser): |
| 72 self._host = host | 71 self._host = host |
| 73 self._port = port | 72 self._port = port |
| 74 self._expectations_factory = bot_test_expectations_factory | 73 self._expectations_factory = bot_test_expectations_factory |
| 75 self._builder_results_by_path = {} | 74 self._builder_results_by_path = {} |
| (...skipping 236 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 312 test_expectations.remove(expectation) | 311 test_expectations.remove(expectation) |
| 313 | 312 |
| 314 # Remove associated comments and whitespace if we've removed the las
t expectation under | 313 # Remove associated comments and whitespace if we've removed the las
t expectation under |
| 315 # a comment block. Only remove a comment block if it's not separated
from the test | 314 # a comment block. Only remove a comment block if it's not separated
from the test |
| 316 # expectation line by whitespace. | 315 # expectation line by whitespace. |
| 317 self._remove_associated_comments_and_whitespace(test_expectations, i
ndex) | 316 self._remove_associated_comments_and_whitespace(test_expectations, i
ndex) |
| 318 | 317 |
| 319 return test_expectations | 318 return test_expectations |
| 320 | 319 |
| 321 def show_removed_results(self): | 320 def show_removed_results(self): |
| 322 """Opens removed lines in the results dashboard. | 321 """Opens a browser showing the removed lines in the results dashboard. |
| 323 | 322 |
| 324 Opens the results dashboard in the browser, showing all the tests for li
nes that the script | 323 Opens the results dashboard in the browser, showing all the tests for |
| 325 removed from the TestExpectations file and allowing the user to manually
confirm the | 324 lines removed from the TestExpectations file, allowing the user to |
| 326 results. | 325 manually confirm the results. |
| 327 """ | 326 """ |
| 328 removed_test_names = ','.join(x.name for x in self._expectations_to_remo
ve()) | 327 url = self._flakiness_dashboard_url() |
| 329 url = FlakyTests.FLAKINESS_DASHBOARD_URL % removed_test_names | |
| 330 | |
| 331 _log.info('Opening results dashboard: ' + url) | 328 _log.info('Opening results dashboard: ' + url) |
| 332 self._browser.open(url) | 329 self._browser.open(url) |
| 333 | 330 |
| 334 def write_test_expectations(self, test_expectations, test_expectations_file)
: | 331 def write_test_expectations(self, test_expectations, test_expectations_file)
: |
| 335 """Writes the given TestExpectations object to the filesystem. | 332 """Writes the given TestExpectations object to the filesystem. |
| 336 | 333 |
| 337 Args: | 334 Args: |
| 338 test_expectations: The TestExpectations object to write. | 335 test_expectations: The TestExpectations object to write. |
| 339 test_expectations_file: The full file path of the Blink | 336 test_expectations_file: The full file path of the Blink |
| 340 TestExpectations file. This file will be overwritten. | 337 TestExpectations file. This file will be overwritten. |
| 341 """ | 338 """ |
| 342 self._host.filesystem.write_text_file( | 339 self._host.filesystem.write_text_file( |
| 343 test_expectations_file, | 340 test_expectations_file, |
| 344 TestExpectations.list_to_string(test_expectations, reconstitute_only
_these=[])) | 341 TestExpectations.list_to_string(test_expectations, reconstitute_only
_these=[])) |
| 342 |
| 343 def print_suggested_commit_description(self): |
| 344 """Prints the body of a suggested CL description after removing some lin
es.""" |
| 345 dashboard_url = self._flakiness_dashboard_url() |
| 346 bugs = ','.join(self._bug_numbers()) |
| 347 message = ('Remove flaky TestExpectations for tests which appear non-fla
ky recently.\n\n' |
| 348 'This change was made by the update-test-expectations script.
\n\n' |
| 349 'Recent test results history:\n%s\n\n' |
| 350 'BUG=%s') % (dashboard_url, bugs) |
| 351 _log.info('Suggested commit description:\n' + message) |
| 352 |
| 353 def _flakiness_dashboard_url(self): |
| 354 removed_test_names = ','.join(x.name for x in self._expectations_to_remo
ve()) |
| 355 return FlakyTests.FLAKINESS_DASHBOARD_URL % removed_test_names |
| 356 |
| 357 def _bug_numbers(self): |
| 358 """Returns the list of all bug numbers affected by this change.""" |
| 359 numbers = [] |
| 360 for line in self._expectations_to_remove(): |
| 361 for bug in line.bugs: |
| 362 if bug.startswith(CHROMIUM_BUG_PREFIX): |
| 363 numbers.append(bug[len(CHROMIUM_BUG_PREFIX):]) |
| 364 return sorted(numbers) |
| OLD | NEW |