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

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

Issue 2125633002: Fill out implementation of UpdateTestExpectations script (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@updateTestExpectations2
Patch Set: Quinten's Review Created 4 years, 5 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 unified diff | Download patch
OLDNEW
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 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
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 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
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 are also some rules about when
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:
79 True if the line can be removed, False otherwise.
80 """
81 expectations = test_expectation_line.expectations
82 if len(expectations) < 2:
83 return False
84
85 # Don't check lines that have expectations like NeedsRebaseline or Skip.
86 if self._has_unstrippable_expectations(expectations):
87 return False
88
89 # 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 return False
92
93 # 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 for config in test_expectation_line.matching_configurations:
96 builder_name = self._host.builders.builder_name_for_specifiers(confi g.version, config.build_type)
97
98 if not builder_name:
99 _log.error('Failed to get builder for config [%s, %s, %s]',
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 # 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 return False
105
106 if builder_name not in self._builder_results_by_path.keys():
107 _log.error('Failed to find results for builder "%s"' % builder_n ame)
108 return False
109
110 results_by_path = self._builder_results_by_path[builder_name]
111
112 # 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():
114 continue
115
116 results_for_single_test = results_by_path[test_expectation_line.path ]
117
118 if self._expectations_that_were_met(test_expectation_line, results_f or_single_test) != set(['PASS']):
119 return False
120
121 return True
122
123 def _has_pass_expectation(self, expectations):
124 return 'PASS' in expectations
125
126 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.
128
129 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
131 return [Pass Failure] since the Failure expectation is satisfied by 'TEX T', Pass
132 by 'PASS' but Crash doesn't appear in the results.
133
134 Args:
135 test_expectation_line: A TestExpectationLine object
136 results_for_single_test: A list of result strings.
137 e.g. ['IMAGE', 'IMAGE', 'PASS']
138
139 Returns:
140 A set containing expectations that occured in the results.
141 """
142 # TODO(bokan): Does this not exist in a more central place?
143 def replace_failing_with_fail(expectation):
144 if expectation in ('TEXT', 'IMAGE', 'IMAGE+TEXT', 'AUDIO'):
145 return 'FAIL'
146 else:
147 return expectation
148
149 actual_results = {replace_failing_with_fail(r) for r in results_for_sing le_test}
150
151 return set(test_expectation_line.expectations) & actual_results
152
153 def _has_unstrippable_expectations(self, expectations):
154 """Returns whether any of the given expectations are considered unstripp able.
155
156 Unstrippable expectations are those which should stop a line from being
157 removed regardless of builder bot results.
158
159 Args:
160 expectations: A list of string expectations.
161 E.g. ['PASS', 'FAIL' 'CRASH']
162
163 Returns:
164 True if at least one of the expectations is unstrippable. False
165 otherwise.
166 """
167 unstrippable_expectations = ('REBASELINE', 'NEEDSREBASELINE',
168 'NEEDSMANUALREBASELINE', 'SLOW',
169 'SKIP')
170 return any(s in expectations for s in unstrippable_expectations)
171
172 def _get_builder_results_by_path(self):
173 """Returns a dictionary of results for each builder.
174
175 Returns a dictionary where each key is a builder and value is a dictiona ry containing
176 the distinct results for each test. E.g.
177
178 {
179 'WebKit Linux': {
180 'test1.html': ['PASS', 'IMAGE'],
181 'test2.html': ['PASS'],
182 },
183 'WebKit Mac10.10': {
184 'test1.html': ['PASS', 'IMAGE'],
185 'test2.html': ['PASS', 'TEXT'],
186 }
187 }
188 """
189 builder_results_by_path = {}
190 for builder_name in self._host.builders.all_builder_names():
191 expectations_for_builder = (
192 self._expectations_factory.expectations_for_builder(builder_name )
193 )
194
195 if not expectations_for_builder:
196 # This is not fatal since we may not need to check these
197 # results. If we do need these results we'll log an error later
198 # when trying to check against them.
199 _log.warn('Downloaded results are missing results for builder "% s"' % builder_name)
200 continue
201
202 builder_results_by_path[builder_name] = (
203 expectations_for_builder.all_results_by_path()
204 )
205 return builder_results_by_path
206
207 def _remove_associated_comments_and_whitespace(self, expectations, removed_i ndex):
208 """Removes comments and whitespace from an empty expectation block.
209
210 If the removed expectation was the last in a block of expectations, this method
211 will remove any associated comments and whitespace.
212
213 Args:
214 expectations: A list of TestExpectationLine objects to be modified.
215 removed_index: The index in the above list that was just removed.
216 """
217 was_last_expectation_in_block = (removed_index == len(expectations)
218 or expectations[removed_index].is_white space()
219 or expectations[removed_index].is_comme nt())
220
221 # If the line immediately below isn't another expectation, then the bloc k of
222 # expectations definitely isn't empty so we shouldn't remove their assoc iated comments.
223 if not was_last_expectation_in_block:
224 return
225
226 did_remove_whitespace = False
227
228 # We may have removed the last expectation in a block. Remove any whites pace above.
229 while removed_index > 0 and expectations[removed_index - 1].is_whitespac e():
230 removed_index -= 1
231 expectations.pop(removed_index)
232 did_remove_whitespace = True
233
234 # If we did remove some whitespace then we shouldn't remove any comments above it
235 # since those won't have belonged to this expectation block. For example , commented
236 # out expectations, or a section header.
237 if did_remove_whitespace:
238 return
239
240 # Remove all comments above the removed line.
241 while removed_index > 0 and expectations[removed_index - 1].is_comment() :
242 removed_index -= 1
243 expectations.pop(removed_index)
244
245 # Remove all whitespace above the comments.
246 while removed_index > 0 and expectations[removed_index - 1].is_whitespac e():
247 removed_index -= 1
248 expectations.pop(removed_index)
64 249
65 def get_updated_test_expectations(self): 250 def get_updated_test_expectations(self):
66 """Filters out passing lines from TestExpectations file. 251 """Filters out passing lines from TestExpectations file.
67 252
68 Reads the current TestExpectations file and, using results from the 253 Reads the current TestExpectations file and, using results from the
69 build bots, removes lines that are passing. That is, removes lines that 254 build bots, removes lines that are passing. That is, removes lines that
70 were not needed to keep the bots green. 255 were not needed to keep the bots green.
71 256
72 Returns: 257 Returns:
73 A TestExpectations object with the passing lines filtered out. 258 A TestExpectations object with the passing lines filtered out.
74 """ 259 """
260
261 self._builder_results_by_path = self._get_builder_results_by_path()
262
263 expectations_to_remove = []
75 test_expectations = TestExpectations(self._port, include_overrides=False ).expectations() 264 test_expectations = TestExpectations(self._port, include_overrides=False ).expectations()
76 # TODO: Filter the expectations based on results. 265
266 for expectation in test_expectations:
267 if self._can_delete_line(expectation):
268 expectations_to_remove.append(expectation)
269
270 for expectation in expectations_to_remove:
271 index = test_expectations.index(expectation)
272 test_expectations.remove(expectation)
273
274 # Remove associated comments and whitespace if we've removed the las t expectation under
275 # a comment block. Only remove a comment block if it's not separated from the test
276 # expectation line by whitespace.
277 self._remove_associated_comments_and_whitespace(test_expectations, i ndex)
278
77 return test_expectations 279 return test_expectations
78 280
79 def write_test_expectations(self, test_expectations, test_expectations_file) : 281 def write_test_expectations(self, test_expectations, test_expectations_file) :
80 """Writes the given TestExpectations object to the filesystem. 282 """Writes the given TestExpectations object to the filesystem.
81 283
82 Args: 284 Args:
83 test_expectations: The TestExpectations object to write. 285 test_expectations: The TestExpectations object to write.
84 test_expectations_file: The full file path of the Blink 286 test_expectations_file: The full file path of the Blink
85 TestExpectations file. This file will be overwritten. 287 TestExpectations file. This file will be overwritten.
86 """ 288 """
87 self._host.filesystem.write_text_file( 289 self._host.filesystem.write_text_file(
88 test_expectations_file, 290 test_expectations_file,
89 TestExpectations.list_to_string(test_expectations, reconstitute_only _these=[])) 291 TestExpectations.list_to_string(test_expectations, reconstitute_only _these=[]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698