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

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

Issue 2381093003: Add switch to update-test-expectations to show removed lines in dashboard (Closed)
Patch Set: Addressed comments Created 4 years, 2 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:
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
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
qyearsley 2016/10/05 21:06:08 Another possible approach to consider here may be
bokan 2016/10/05 22:35:24 Done.
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 for li nes that the script
316 removed from the TestExpectations file and allowing the user to manually confirm the
317 results.
318 """
319 self._ensure_expectations_to_remove()
320 removed_test_names = ','.join(x.name for x in self._expectations_to_remo ve)
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=[]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698