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 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
55 expectations_file) | 55 expectations_file) |
56 | 56 |
57 | 57 |
58 class RemoveFlakesOMatic(object): | 58 class RemoveFlakesOMatic(object): |
59 def __init__(self, host, port, bot_test_expectations_factory): | 59 def __init__(self, host, port, bot_test_expectations_factory): |
60 self._host = host | 60 self._host = host |
61 self._port = port | 61 self._port = port |
62 self._expectations_factory = bot_test_expectations_factory | 62 self._expectations_factory = bot_test_expectations_factory |
63 self.builder_results_by_path = {} | 63 self.builder_results_by_path = {} |
64 | 64 |
65 def _can_delete_line(self, test_expectation_line): | |
66 """Returns whether a given line in the expectations can be removed. | |
67 | |
68 Uses results from builder bots to determine if a given line is stale and | |
69 can safely be removed from the TestExpectations file. (i.e. remove if | |
70 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.
| |
71 not to remove lines (e.g. never remove lines with Rebaseline | |
72 expectations, don't remove non-flaky expectations, etc.) | |
73 | |
74 Args: | |
75 test_expectation_line (TestExpectationLine): A line in the test | |
76 expectation file to test for possible removal. | |
77 | |
78 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.
| |
79 """ | |
80 expectations = test_expectation_line.expectations | |
81 if len(expectations) < 2: | |
82 return False | |
83 | |
84 if self._has_unstrippable_expectations(expectations): | |
85 return False | |
86 | |
87 if not self._has_pass_expectation(expectations): | |
88 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
| |
89 | |
90 # The line can be deleted if the only expectation on the line that appea rs in the actual | |
91 # results is the PASS expectation. | |
92 for config in test_expectation_line.matching_configurations: | |
93 builder_name = self._host.builders.builder_name_for_specifiers(confi g.version, config.build_type) | |
94 | |
95 if not builder_name: | |
96 _log.error('Failed to get builder for config [%s, %s, %s]' % (co nfig.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.
| |
97 # TODO(bokan): Matching configurations often give us bots that d on't have a | |
98 # builder in builders.py's exact_matches. Should we ignore those or be conservative | |
99 # and assume we need these expectations to make a decision? | |
100 return False | |
101 | |
102 if builder_name not in self.builder_results_by_path.keys(): | |
103 _log.error('Failed to find results for builder "%s"' % builder_n ame) | |
104 return False | |
105 | |
106 results_by_path = self.builder_results_by_path[builder_name] | |
107 | |
108 # No results means the tests were all skipped or all results are pas sing. | |
109 if test_expectation_line.path not in results_by_path.keys(): | |
110 continue | |
111 | |
112 results_for_single_test = results_by_path[test_expectation_line.path ] | |
113 | |
114 if self._expectations_that_were_met(test_expectation_line, results_f or_single_test) != set(['PASS']): | |
115 return False | |
116 | |
117 return True | |
118 | |
119 def _has_pass_expectation(self, expectations): | |
120 return 'PASS' in expectations | |
121 | |
122 def _expectations_that_were_met(self, test_expectation_line, results_for_sin gle_test): | |
123 """Returns the set of expectations that appear in the given results. | |
124 | |
125 e.g. If the test expectations is: | |
126 bug(test) fast/test.html [Crash Failure Pass] | |
127 | |
128 And the results are ['TEXT', 'PASS', 'PASS', 'TIMEOUT'] | |
129 | |
130 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.
| |
131 | |
132 Args: | |
133 test_expectation_line: A TestExpectationLine object | |
134 results_for_single_test: A list of result strings. | |
135 e.g. ['IMAGE', 'IMAGE', 'PASS'] | |
136 | |
137 Returns: | |
138 A set containing expectations that occured in the results. | |
139 """ | |
140 # 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
| |
141 def replace_failing_with_fail(expectation): | |
142 if expectation in ('TEXT', 'IMAGE', 'IMAGE+TEXT', 'AUDIO'): | |
143 return 'FAIL' | |
144 else: | |
145 return expectation | |
146 | |
147 actual_results = {replace_failing_with_fail(r) for r in results_for_sing le_test} | |
148 | |
149 return set(test_expectation_line.expectations) & actual_results | |
150 | |
151 def _has_unstrippable_expectations(self, expectations): | |
152 """ Returns whether any of the given expectations are considered unstrip pable. | |
qyearsley
2016/07/08 17:55:04
Extra space character can be removed.
bokan
2016/07/08 21:13:00
Done.
| |
153 | |
154 Unstrippable expectations are those which should stop a line from being | |
155 removed regardless of builder bot results. | |
156 | |
157 Args: | |
158 expectations: A list of string expectations. | |
159 E.g. ['PASS', 'FAIL' 'CRASH'] | |
160 | |
161 Returns: | |
162 True if at least one of the expectations is unstrippable. False | |
163 otherwise. | |
164 """ | |
165 unstrippable_expectations = ('REBASELINE', 'NEEDSREBASELINE', | |
166 'NEEDSMANUALREBASELINE', 'SLOW', | |
167 'SKIP') | |
168 return any(s in expectations for s in unstrippable_expectations) | |
169 | |
65 def get_updated_test_expectations(self): | 170 def get_updated_test_expectations(self): |
66 """Filters out passing lines from TestExpectations file. | 171 """Filters out passing lines from TestExpectations file. |
67 | 172 |
68 Reads the current TestExpectatoins file and, using results from the | 173 Reads the current TestExpectatoins file and, using results from the |
69 build bots, removes lines that are passing. That is, removes lines that | 174 build bots, removes lines that are passing. That is, removes lines that |
70 were not needed to keep the bots green. | 175 were not needed to keep the bots green. |
71 | 176 |
72 Returns: A TestExpectations object with the passing lines filtered out. | 177 Returns: A TestExpectations object with the passing lines filtered out. |
73 """ | 178 """ |
74 test_expectations = TestExpectations(self._port, include_overrides=False ).expectations() | 179 test_expectations = TestExpectations(self._port, include_overrides=False ).expectations() |
75 # TODO: Filter the expectations based on results. | 180 |
181 self.builder_results_by_path = {} | |
182 for builder_name in self._host.builders.all_builder_names(): | |
183 expectations_for_builder = ( | |
184 self._expectations_factory.expectations_for_builder(builder_name ) | |
185 ) | |
186 | |
187 if not expectations_for_builder: | |
188 # This is not fatal since we may not need to check these | |
189 # results. If we do need these results we'll log an error later | |
190 # when trying to check against them. | |
191 _log.warn('Downloaded results are missing results for builder "% s"' % builder_name) | |
192 continue | |
193 | |
194 self.builder_results_by_path[builder_name] = ( | |
195 expectations_for_builder.all_results_by_path() | |
196 ) | |
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.
| |
197 | |
198 expectations_to_remove = [] | |
199 | |
200 for expectation in test_expectations: | |
201 if self._can_delete_line(expectation): | |
202 expectations_to_remove.append(expectation) | |
203 | |
204 for expectation in expectations_to_remove: | |
205 index = test_expectations.index(expectation) | |
206 test_expectations.remove(expectation) | |
207 | |
208 # Remove associated comments and whitespace if we've removed the las t expectation under | |
209 # a comment block. Only remove a comment block if it's not separated from the test | |
210 # expectation line by whitespace. | |
211 if index == len(test_expectations) or test_expectations[index].is_wh itespace() or test_expectations[index].is_comment(): | |
212 removed_whitespace = False | |
213 while index and test_expectations[index - 1].is_whitespace(): | |
214 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.
| |
215 test_expectations.pop(index) | |
216 removed_whitespace = True | |
217 | |
218 if not removed_whitespace: | |
219 while index and test_expectations[index - 1].is_comment(): | |
220 index = index - 1 | |
221 test_expectations.pop(index) | |
222 | |
223 while index and test_expectations[index - 1].is_whitespace() : | |
224 index = index - 1 | |
225 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.
| |
226 | |
76 return test_expectations | 227 return test_expectations |
77 | 228 |
78 def write_test_expectations(self, test_expectations, test_expectations_file) : | 229 def write_test_expectations(self, test_expectations, test_expectations_file) : |
79 """Writes the given TestExpectations object to the filesystem. | 230 """Writes the given TestExpectations object to the filesystem. |
80 | 231 |
81 Args: | 232 Args: |
82 test_expectatoins: The TestExpectations object to write. | 233 test_expectatoins: The TestExpectations object to write. |
83 test_expectations_file: The full file path of the Blink | 234 test_expectations_file: The full file path of the Blink |
84 TestExpectations file. This file will be overwritten. | 235 TestExpectations file. This file will be overwritten. |
85 """ | 236 """ |
86 self._host.filesystem.write_text_file( | 237 self._host.filesystem.write_text_file( |
87 test_expectations_file, | 238 test_expectations_file, |
88 TestExpectations.list_to_string(test_expectations, reconstitute_only _these=[])) | 239 TestExpectations.list_to_string(test_expectations, reconstitute_only _these=[])) |
OLD | NEW |