Chromium Code Reviews| 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 | 29 |
| 29 from webkitpy.layout_tests.models.test_expectations import TestExpectations | 30 from webkitpy.layout_tests.models.test_expectations import TestExpectations |
| 31 from webkitpy.tool.commands.flaky_tests import FlakyTests | |
| 30 | 32 |
| 31 _log = logging.getLogger(__name__) | 33 _log = logging.getLogger(__name__) |
| 32 | 34 |
| 33 | 35 |
| 34 def main(host, bot_test_expectations_factory, argv): | 36 def main(host, bot_test_expectations_factory, argv): |
| 35 parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.Ra wTextHelpFormatter) | 37 parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.Ra wTextHelpFormatter) |
| 36 parser.add_argument('--verbose', '-v', action='store_true', default=False, h elp='enable more verbose logging') | 38 parser.add_argument('--verbose', '-v', action='store_true', default=False, h elp='enable more verbose logging') |
| 39 parser.add_argument('--show-results', | |
| 40 '-s', | |
| 41 action='store_true', | |
| 42 default=False, | |
| 43 help='Open results dashboard for all removed lines') | |
| 37 args = parser.parse_args(argv) | 44 args = parser.parse_args(argv) |
| 38 | 45 |
| 39 logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO, f ormat="%(levelname)s: %(message)s") | 46 logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO, f ormat="%(levelname)s: %(message)s") |
| 40 | 47 |
| 41 port = host.port_factory.get() | 48 port = host.port_factory.get() |
| 42 expectations_file = port.path_to_generic_test_expectations_file() | 49 expectations_file = port.path_to_generic_test_expectations_file() |
| 43 if not host.filesystem.isfile(expectations_file): | 50 if not host.filesystem.isfile(expectations_file): |
| 44 _log.warning("Didn't find generic expectations file at: " + expectations _file) | 51 _log.warning("Didn't find generic expectations file at: " + expectations _file) |
| 45 return 1 | 52 return 1 |
| 46 | 53 |
| 47 remove_flakes_o_matic = RemoveFlakesOMatic(host, | 54 remove_flakes_o_matic = RemoveFlakesOMatic(host, |
| 48 port, | 55 port, |
| 49 bot_test_expectations_factory) | 56 bot_test_expectations_factory) |
| 50 | 57 |
| 51 test_expectations = remove_flakes_o_matic.get_updated_test_expectations() | 58 test_expectations = remove_flakes_o_matic.get_updated_test_expectations() |
| 52 | 59 |
| 60 if args.show_results: | |
| 61 remove_flakes_o_matic.show_removed_results() | |
| 62 | |
| 53 remove_flakes_o_matic.write_test_expectations(test_expectations, | 63 remove_flakes_o_matic.write_test_expectations(test_expectations, |
| 54 expectations_file) | 64 expectations_file) |
| 55 return 0 | 65 return 0 |
| 56 | 66 |
| 57 | 67 |
| 58 class RemoveFlakesOMatic(object): | 68 class RemoveFlakesOMatic(object): |
| 59 | 69 |
| 60 def __init__(self, host, port, bot_test_expectations_factory): | 70 def __init__(self, host, port, bot_test_expectations_factory): |
| 61 self._host = host | 71 self._host = host |
| 62 self._port = port | 72 self._port = port |
| 63 self._expectations_factory = bot_test_expectations_factory | 73 self._expectations_factory = bot_test_expectations_factory |
| 64 self._builder_results_by_path = {} | 74 self._builder_results_by_path = {} |
| 75 self._expectations_to_remove = None | |
| 65 | 76 |
| 66 def _can_delete_line(self, test_expectation_line): | 77 def _can_delete_line(self, test_expectation_line): |
| 67 """Returns whether a given line in the expectations can be removed. | 78 """Returns whether a given line in the expectations can be removed. |
| 68 | 79 |
| 69 Uses results from builder bots to determine if a given line is stale and | 80 Uses results from builder bots to determine if a given line is stale and |
| 70 can safely be removed from the TestExpectations file. (i.e. remove if | 81 can safely be removed from the TestExpectations file. (i.e. remove if |
| 71 the bots show that it's not flaky.) There are also some rules about when | 82 the bots show that it's not flaky.) There are also some rules about when |
| 72 not to remove lines (e.g. never remove lines with Rebaseline | 83 not to remove lines (e.g. never remove lines with Rebaseline |
| 73 expectations, don't remove non-flaky expectations, etc.) | 84 expectations, don't remove non-flaky expectations, etc.) |
| 74 | 85 |
| (...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 248 # Remove all comments above the removed line. | 259 # Remove all comments above the removed line. |
| 249 while removed_index > 0 and expectations[removed_index - 1].is_comment() : | 260 while removed_index > 0 and expectations[removed_index - 1].is_comment() : |
| 250 removed_index -= 1 | 261 removed_index -= 1 |
| 251 expectations.pop(removed_index) | 262 expectations.pop(removed_index) |
| 252 | 263 |
| 253 # Remove all whitespace above the comments. | 264 # Remove all whitespace above the comments. |
| 254 while removed_index > 0 and expectations[removed_index - 1].is_whitespac e(): | 265 while removed_index > 0 and expectations[removed_index - 1].is_whitespac e(): |
| 255 removed_index -= 1 | 266 removed_index -= 1 |
| 256 expectations.pop(removed_index) | 267 expectations.pop(removed_index) |
| 257 | 268 |
| 269 def _ensure_expectations_to_remove(self): | |
| 270 """Ensures the _expectations_to_remove member is filled in. | |
| 271 | |
| 272 This member starts uninitialized. Several public methods need this membe r to be filled in to | |
| 273 operate but we don't want to build it multiple times and using the const ructor would make | |
| 274 testing inconvenient. Instead, each calls this method which early-outs i f the work has | |
| 275 already been done. | |
| 276 """ | |
| 277 if self._expectations_to_remove is not None: | |
| 278 return | |
| 279 | |
| 280 self._builder_results_by_path = self._get_builder_results_by_path() | |
| 281 self._expectations_to_remove = [] | |
| 282 test_expectations = TestExpectations(self._port, include_overrides=False ).expectations() | |
| 283 | |
| 284 for expectation in test_expectations: | |
| 285 if self._can_delete_line(expectation): | |
| 286 self._expectations_to_remove.append(expectation) | |
| 287 | |
| 258 def get_updated_test_expectations(self): | 288 def get_updated_test_expectations(self): |
| 259 """Filters out passing lines from TestExpectations file. | 289 """Filters out passing lines from TestExpectations file. |
| 260 | 290 |
| 261 Reads the current TestExpectations file and, using results from the | 291 Reads the current TestExpectations file and, using results from the |
| 262 build bots, removes lines that are passing. That is, removes lines that | 292 build bots, removes lines that are passing. That is, removes lines that |
| 263 were not needed to keep the bots green. | 293 were not needed to keep the bots green. |
| 264 | 294 |
| 265 Returns: | 295 Returns: |
| 266 A TestExpectations object with the passing lines filtered out. | 296 A TestExpectations object with the passing lines filtered out. |
| 267 """ | 297 """ |
| 298 self._ensure_expectations_to_remove() | |
| 268 | 299 |
| 269 self._builder_results_by_path = self._get_builder_results_by_path() | |
| 270 | |
| 271 expectations_to_remove = [] | |
| 272 test_expectations = TestExpectations(self._port, include_overrides=False ).expectations() | 300 test_expectations = TestExpectations(self._port, include_overrides=False ).expectations() |
| 273 | 301 for expectation in self._expectations_to_remove: |
| 274 for expectation in test_expectations: | |
| 275 if self._can_delete_line(expectation): | |
| 276 expectations_to_remove.append(expectation) | |
| 277 | |
| 278 for expectation in expectations_to_remove: | |
| 279 index = test_expectations.index(expectation) | 302 index = test_expectations.index(expectation) |
| 280 test_expectations.remove(expectation) | 303 test_expectations.remove(expectation) |
| 281 | 304 |
| 282 # Remove associated comments and whitespace if we've removed the las t expectation under | 305 # Remove associated comments and whitespace if we've removed the las t expectation under |
| 283 # a comment block. Only remove a comment block if it's not separated from the test | 306 # a comment block. Only remove a comment block if it's not separated from the test |
| 284 # expectation line by whitespace. | 307 # expectation line by whitespace. |
| 285 self._remove_associated_comments_and_whitespace(test_expectations, i ndex) | 308 self._remove_associated_comments_and_whitespace(test_expectations, i ndex) |
| 286 | 309 |
| 287 return test_expectations | 310 return test_expectations |
| 288 | 311 |
| 312 def show_removed_results(self): | |
| 313 """Opens removed lines in the results dashboard. | |
| 314 | |
| 315 Opens the results dashboard in the browser, showing all the tests that t he script will/did | |
| 316 remove from the TestExpectations file and allowing the user to manually confirm the | |
|
qyearsley
2016/09/30 23:26:26
Possible rephrasing:
"... tests that the script
bokan
2016/10/04 16:13:46
Done.
| |
| 317 results. | |
| 318 """ | |
| 319 self._ensure_expectations_to_remove() | |
| 320 removed_test_names = ','.join([x.name for x in self._expectations_to_rem ove]) | |
|
qyearsley
2016/09/30 23:26:26
Since str.join can take any iterable (including a
bokan
2016/10/04 16:13:46
Neat, thanks. Done.
| |
| 321 url = FlakyTests.FLAKINESS_DASHBOARD_URL % removed_test_names | |
| 322 | |
| 323 _log.info('Opening results dashboard: ' + url) | |
| 324 webbrowser.open(url) | |
| 325 | |
| 289 def write_test_expectations(self, test_expectations, test_expectations_file) : | 326 def write_test_expectations(self, test_expectations, test_expectations_file) : |
| 290 """Writes the given TestExpectations object to the filesystem. | 327 """Writes the given TestExpectations object to the filesystem. |
| 291 | 328 |
| 292 Args: | 329 Args: |
| 293 test_expectations: The TestExpectations object to write. | 330 test_expectations: The TestExpectations object to write. |
| 294 test_expectations_file: The full file path of the Blink | 331 test_expectations_file: The full file path of the Blink |
| 295 TestExpectations file. This file will be overwritten. | 332 TestExpectations file. This file will be overwritten. |
| 296 """ | 333 """ |
| 297 self._host.filesystem.write_text_file( | 334 self._host.filesystem.write_text_file( |
| 298 test_expectations_file, | 335 test_expectations_file, |
| 299 TestExpectations.list_to_string(test_expectations, reconstitute_only _these=[])) | 336 TestExpectations.list_to_string(test_expectations, reconstitute_only _these=[])) |
| OLD | NEW |