| 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: |
| (...skipping 15 matching lines...) Expand all Loading... |
| 26 import argparse | 26 import argparse |
| 27 import logging | 27 import logging |
| 28 | 28 |
| 29 from webkitpy.layout_tests.models.test_expectations import TestExpectations | 29 from webkitpy.layout_tests.models.test_expectations import TestExpectations |
| 30 | 30 |
| 31 _log = logging.getLogger(__name__) | 31 _log = logging.getLogger(__name__) |
| 32 | 32 |
| 33 | 33 |
| 34 def main(host, bot_test_expectations_factory, argv): | 34 def main(host, bot_test_expectations_factory, argv): |
| 35 parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.Ra
wTextHelpFormatter) | 35 parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.Ra
wTextHelpFormatter) |
| 36 parser.parse_args(argv) | 36 parser.add_argument('--verbose', '-v', action='store_true', default=False, h
elp='enable more verbose logging') |
| 37 args = parser.parse_args(argv) |
| 38 |
| 39 logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO, f
ormat="%(levelname)s: %(message)s") |
| 37 | 40 |
| 38 port = host.port_factory.get() | 41 port = host.port_factory.get() |
| 39 | |
| 40 logging.basicConfig(level=logging.INFO, format="%(message)s") | |
| 41 | |
| 42 expectations_file = port.path_to_generic_test_expectations_file() | 42 expectations_file = port.path_to_generic_test_expectations_file() |
| 43 if not host.filesystem.isfile(expectations_file): | 43 if not host.filesystem.isfile(expectations_file): |
| 44 _log.warning("Didn't find generic expectations file at: " + expectations
_file) | 44 _log.warning("Didn't find generic expectations file at: " + expectations
_file) |
| 45 return 1 | 45 return 1 |
| 46 | 46 |
| 47 remove_flakes_o_matic = RemoveFlakesOMatic(host, | 47 remove_flakes_o_matic = RemoveFlakesOMatic(host, |
| 48 port, | 48 port, |
| 49 bot_test_expectations_factory) | 49 bot_test_expectations_factory) |
| 50 | 50 |
| 51 test_expectations = remove_flakes_o_matic.get_updated_test_expectations() | 51 test_expectations = remove_flakes_o_matic.get_updated_test_expectations() |
| 52 | 52 |
| 53 remove_flakes_o_matic.write_test_expectations(test_expectations, | 53 remove_flakes_o_matic.write_test_expectations(test_expectations, |
| 54 expectations_file) | 54 expectations_file) |
| 55 return 0 | 55 return 0 |
| 56 | 56 |
| 57 | 57 |
| 58 class RemoveFlakesOMatic(object): | 58 class RemoveFlakesOMatic(object): |
| 59 |
| 59 def __init__(self, host, port, bot_test_expectations_factory): | 60 def __init__(self, host, port, bot_test_expectations_factory): |
| 60 self._host = host | 61 self._host = host |
| 61 self._port = port | 62 self._port = port |
| 62 self._expectations_factory = bot_test_expectations_factory | 63 self._expectations_factory = bot_test_expectations_factory |
| 63 self._builder_results_by_path = {} | 64 self._builder_results_by_path = {} |
| 64 | 65 |
| 65 def _can_delete_line(self, test_expectation_line): | 66 def _can_delete_line(self, test_expectation_line): |
| 66 """Returns whether a given line in the expectations can be removed. | 67 """Returns whether a given line in the expectations can be removed. |
| 67 | 68 |
| 68 Uses results from builder bots to determine if a given line is stale and | 69 Uses results from builder bots to determine if a given line is stale and |
| (...skipping 16 matching lines...) Expand all Loading... |
| 85 # Don't check lines that have expectations like NeedsRebaseline or Skip. | 86 # Don't check lines that have expectations like NeedsRebaseline or Skip. |
| 86 if self._has_unstrippable_expectations(expectations): | 87 if self._has_unstrippable_expectations(expectations): |
| 87 return False | 88 return False |
| 88 | 89 |
| 89 # Don't check lines unless they're flaky. i.e. At least one expectation
is a PASS. | 90 # Don't check lines unless they're flaky. i.e. At least one expectation
is a PASS. |
| 90 if not self._has_pass_expectation(expectations): | 91 if not self._has_pass_expectation(expectations): |
| 91 return False | 92 return False |
| 92 | 93 |
| 93 # The line can be deleted if the only expectation on the line that appea
rs in the actual | 94 # The line can be deleted if the only expectation on the line that appea
rs in the actual |
| 94 # results is the PASS expectation. | 95 # results is the PASS expectation. |
| 96 builders_checked = [] |
| 95 for config in test_expectation_line.matching_configurations: | 97 for config in test_expectation_line.matching_configurations: |
| 96 builder_name = self._host.builders.builder_name_for_specifiers(confi
g.version, config.build_type) | 98 builder_name = self._host.builders.builder_name_for_specifiers(confi
g.version, config.build_type) |
| 97 | 99 |
| 98 if not builder_name: | 100 if not builder_name: |
| 99 _log.error('Failed to get builder for config [%s, %s, %s]', | 101 _log.debug('No builder with config %s', config) |
| 100 config.version, config.architecture, config.build_typ
e) | |
| 101 # TODO(bokan): Matching configurations often give us bots that d
on't have a | 102 # TODO(bokan): Matching configurations often give us bots that d
on't have a |
| 102 # builder in builders.py's exact_matches. Should we ignore those
or be conservative | 103 # builder in builders.py's exact_matches. Should we ignore those
or be conservative |
| 103 # and assume we need these expectations to make a decision? | 104 # and assume we need these expectations to make a decision? |
| 104 return False | 105 continue |
| 106 |
| 107 builders_checked.append(builder_name) |
| 105 | 108 |
| 106 if builder_name not in self._builder_results_by_path.keys(): | 109 if builder_name not in self._builder_results_by_path.keys(): |
| 107 _log.error('Failed to find results for builder "%s"', builder_na
me) | 110 _log.error('Failed to find results for builder "%s"', builder_na
me) |
| 108 return False | 111 return False |
| 109 | 112 |
| 110 results_by_path = self._builder_results_by_path[builder_name] | 113 results_by_path = self._builder_results_by_path[builder_name] |
| 111 | 114 |
| 112 # No results means the tests were all skipped or all results are pas
sing. | 115 # No results means the tests were all skipped or all results are pas
sing. |
| 113 if test_expectation_line.path not in results_by_path.keys(): | 116 if test_expectation_line.path not in results_by_path.keys(): |
| 114 continue | 117 continue |
| 115 | 118 |
| 116 results_for_single_test = results_by_path[test_expectation_line.path
] | 119 results_for_single_test = results_by_path[test_expectation_line.path
] |
| 117 | 120 |
| 118 if self._expectations_that_were_met(test_expectation_line, results_f
or_single_test) != set(['PASS']): | 121 if self._expectations_that_were_met(test_expectation_line, results_f
or_single_test) != set(['PASS']): |
| 119 return False | 122 return False |
| 120 | 123 |
| 124 if builders_checked: |
| 125 _log.debug('Checked builders:\n %s', '\n '.join(builders_checked)) |
| 126 else: |
| 127 _log.warning('No matching builders for line, deleting line.') |
| 128 _log.info('Deleting line "%s"', test_expectation_line.original_string.st
rip()) |
| 121 return True | 129 return True |
| 122 | 130 |
| 123 def _has_pass_expectation(self, expectations): | 131 def _has_pass_expectation(self, expectations): |
| 124 return 'PASS' in expectations | 132 return 'PASS' in expectations |
| 125 | 133 |
| 126 def _expectations_that_were_met(self, test_expectation_line, results_for_sin
gle_test): | 134 def _expectations_that_were_met(self, test_expectation_line, results_for_sin
gle_test): |
| 127 """Returns the set of expectations that appear in the given results. | 135 """Returns the set of expectations that appear in the given results. |
| 128 | 136 |
| 129 e.g. If the test expectations is "bug(test) fast/test.html [Crash Failur
e Pass]" | 137 e.g. If the test expectations is "bug(test) fast/test.html [Crash Failur
e Pass]" |
| 130 and the results are ['TEXT', 'PASS', 'PASS', 'TIMEOUT'], then this metho
d would | 138 and the results are ['TEXT', 'PASS', 'PASS', 'TIMEOUT'], then this metho
d would |
| (...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 282 """Writes the given TestExpectations object to the filesystem. | 290 """Writes the given TestExpectations object to the filesystem. |
| 283 | 291 |
| 284 Args: | 292 Args: |
| 285 test_expectations: The TestExpectations object to write. | 293 test_expectations: The TestExpectations object to write. |
| 286 test_expectations_file: The full file path of the Blink | 294 test_expectations_file: The full file path of the Blink |
| 287 TestExpectations file. This file will be overwritten. | 295 TestExpectations file. This file will be overwritten. |
| 288 """ | 296 """ |
| 289 self._host.filesystem.write_text_file( | 297 self._host.filesystem.write_text_file( |
| 290 test_expectations_file, | 298 test_expectations_file, |
| 291 TestExpectations.list_to_string(test_expectations, reconstitute_only
_these=[])) | 299 TestExpectations.list_to_string(test_expectations, reconstitute_only
_these=[])) |
| OLD | NEW |