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

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

Issue 1766583002: Added update_test_expectations script to remove non-flaky test expectations. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Mock out builders and specifiers, dont remove lines if bot results arent available Created 4 years, 9 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
(Empty)
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
3 # found in the LICENSE file.
4
5 import argparse
6
7 from webkitpy.layout_tests.port import builders
8 from webkitpy.layout_tests.models.test_expectations import TestExpectations
9 from webkitpy.layout_tests.layout_package.bot_test_expectations import BotTestEx pectationsFactory
10
11
12 def main(host, argv):
13 parser = argparse.ArgumentParser(epilog=("""
14 Scans the TestExpectations file
15 and uses results from actual builder bots runs to remove tests that are
16 marked as flaky but don't fail in the specified way.
17
18 E.g. If a test has this expectation:
19 bug(test) fast/test.html [ Failure Pass ]
20
21 And all the runs on builders have passed the line will be removed.
22
23 Additionally, the runs don't all have to be Passing to remove the line;
24 as long as the non-Passing results are of a type not specified in the
25 expectation this line will be removed. For example, if this is the
26 expectation:
27
28 bug(test) fast/test.html [ Crash Pass ]
29
30 But the results on the builders show only Passes and Timeouts, the line
31 will be removed since there's no Crash results.
32 """), formatter_class=argparse.RawTextHelpFormatter)
Dirk Pranke 2016/03/29 22:39:01 I would actually move this string up to the top-le
bokan 2016/04/05 12:28:57 Done.
33 args = parser.parse_args(argv)
34
35 port = host.port_factory.get()
36 bot_test_expectations_factory = BotTestExpectationsFactory()
37
38 remove_flakes_o_matic = RemoveFlakesOMatic(host,
39 port,
40 bot_test_expectations_factory)
41
42 test_expectations = remove_flakes_o_matic.get_updated_test_expectations()
43
44 if not test_expectations:
45 # TODO(bokan): How should we report errors?
46 return None
47
48 expectations_file = port.path_to_generic_test_expectations_file()
49
50 remove_flakes_o_matic.write_test_expectations(test_expectations,
51 expectations_file)
qyearsley 2016/03/21 21:45:25 Note, the unit test doesn't cover main(), or write
Dirk Pranke 2016/03/29 22:39:01 By passing a MockHost() to main, you can (and shou
bokan 2016/04/05 12:28:56 Added a TODO, will add shortly.
52
53
54 class RemoveFlakesOMatic:
Dirk Pranke 2016/03/29 22:39:01 derive this class from object, i.e. RemoveFlakesOM
bokan 2016/04/05 12:28:56 Done.
55 def __init__(self, host, port, bot_test_expectations_factory):
56 self._host = host
57 self._port = port
58 self._expectations_factory = bot_test_expectations_factory
59
60 def _can_delete_line(self, test_expectation_line):
61 """Returns whether a given line in the expectations can be removed.
62
63 Uses results from builder bots to determine if a given line is stale and
64 can safely be removed from the TestExpectations file. (i.e. remove if
65 the bots show that it's not flaky.) There's also some rules about when
66 not to remove lines (e.g. never remove lines with Rebaseline
67 expectations, don't remove non-flaky expectations, etc.)
68
69 Args:
70 test_expectation_line (TestExpectationLine): A line in the test
71 expectation file to test for possible removal.
72
73 Returns: True if the line can be removed, False otherwise.
74 """
75 expectations = test_expectation_line.expectations
76 if len(expectations) < 2:
77 return False
78
79 if self._has_unstrippable_expectations(expectations):
80 return False
81
82 if not self._has_pass_expectation(expectations):
83 return False
84
85 # The line can be deleted if the only expectation on the line that appea rs in the actual
86 # results is the PASS expectation.
87 for config in test_expectation_line.matching_configurations:
88 builder_name = builders.builder_name_for_specifiers(config.version, config.build_type)
89
90 if not builder_name:
91 # TODO(bokan): We probably want to report a problem if we can't
92 # turn the specifiers into a builder name.
93 # TODO(bokan): Matching configurations often give us bots that d on't have a
94 # builder in builders.py's exact_matches. Should we ignore those or be conservative
95 # and assume we need these expectations to make a decision?
Dirk Pranke 2016/03/29 22:39:01 Are you wondering about cases like Debug bots wher
bokan 2016/04/05 12:28:56 Yah, I think it's mostly debug, but not all. The m
96 return False
97
98 if builder_name not in self.builder_results_by_path.keys():
99 # TODO(bokan): We probably want to report a problem if we can't get results for
100 # a bot specified in expectatoins.
101 return False
qyearsley 2016/03/21 21:45:25 For here and above, I agree that logging an error
bokan 2016/04/05 12:28:56 Added logging messages. Will add tests shortly.
102
103 results_by_path = self.builder_results_by_path[builder_name]
104
105 # No results means the test was all skipped or all results are passi ng.
qyearsley 2016/03/21 21:45:25 test was all skipped -> tests were all skipped
bokan 2016/04/05 12:28:56 Done.
106 if test_expectation_line.path not in results_by_path.keys():
107 continue
qyearsley 2016/03/21 21:45:25 This line is not covered by the unit test right no
bokan 2016/04/05 12:28:56 Ack. Added a TODO, will do shortly.
108
109 results_for_single_test = results_by_path[test_expectation_line.path ]
110
111 if self._expectations_that_were_met(test_expectation_line, results_f or_single_test) != set(['PASS']):
112 return False
113
114 return True
115
116 def _has_pass_expectation(self, expectations):
117 return 'PASS' in expectations
118
119 def _expectations_that_were_met(self, test_expectation_line, results_for_sin gle_test):
120 """Returns the set of expectations that appear in the given results.
121
122 e.g. If the test expectations is:
123 bug(test) fast/test.html [Crash Failure Pass]
124
125 And the results are ['TEXT', 'PASS', 'PASS', 'TIMEOUT']
126
127 This method would return [Pass Failure]
128
129 Args:
130 test_expectation_line: A TestExpectationLine object
131 results_for_single_test: A list of result strings.
132 e.g. ['IMAGE', 'IMAGE', 'PASS']
133
134 Returns:
135 A set containing expectations that occured in the results.
136 """
137 # TODO(bokan): Does this not exist in a more central place?
138 def replace_failing_with_fail(expectation):
139 if expectation in ('TEXT', 'IMAGE', 'IMAGE+TEXT', 'AUDIO'):
140 return 'FAIL'
141 else:
142 return expectation
143
144 actual_results = {replace_failing_with_fail(r) for r in results_for_sing le_test}
145
146 return set(test_expectation_line.expectations) & actual_results
147
148 def _has_unstrippable_expectations(self, expectations):
149 """ Returns whether any of the given expectations are considered unstrip pable.
150
151 Unstrippable expectations are those which should stop a line from being
152 removed regardless of builder bot results.
153
154 Args:
155 expectations: A list of string expectations.
156 E.g. ['PASS', 'FAIL' 'CRASH']
157
158 Returns:
159 True if at least one of the expectations is unstrippable. False
160 otherwise.
161 """
162 unstrippable_expectations = ('REBASELINE', 'NEEDSREBASELINE',
163 'NEEDSMANUALREBASELINE', 'SLOW')
164 return any(s in expectations for s in unstrippable_expectations)
165
166 def get_updated_test_expectations(self):
167 test_expectations = TestExpectations(self._port, include_overrides=False ).expectations()
168
169 self.builder_results_by_path = {}
170 for builder_name in builders.all_builder_names():
171 expectations_for_builder = (
172 self._expectations_factory.expectations_for_builder(builder_name )
173 )
174
175 # TODO(bokan): Error message?
176 if not expectations_for_builder:
177 return None
qyearsley 2016/03/21 21:45:25 Logging an error here sounds good to me. Also, thi
bokan 2016/04/05 12:28:56 Added log message. TODO and working on test.
178
179 self.builder_results_by_path[builder_name] = (
180 expectations_for_builder.all_results_by_path()
181 )
182
183 expectations_to_remove = []
184
185 for expectation in test_expectations:
186 if self._can_delete_line(expectation):
187 expectations_to_remove.append(expectation)
188
189 for expectation in expectations_to_remove:
190 index = test_expectations.index(expectation)
191 test_expectations.remove(expectation)
192
193 # Remove associated comments and whitespace if we've removed the las t expectation under
194 # a comment block. Only remove a comment block if it's not separated from the tests by
195 # whitespace.
qyearsley 2016/03/21 21:45:25 Would it be correct to rephrase "if it's not separ
bokan 2016/04/05 12:28:56 Done. (replaced with "if it's not separated _from_
196 if index == len(test_expectations) or test_expectations[index].is_wh itespace_or_comment():
197 removed_whitespace = False
198 while index and test_expectations[index - 1].is_whitespace():
199 index = index - 1
200 test_expectations.pop(index)
201 removed_whitespace = True
qyearsley 2016/03/21 21:45:25 I think there may be no test case that covers this
bokan 2016/04/05 12:28:56 It should be covered by test_preserve_comments_and
202
203 if not removed_whitespace:
204 while index and test_expectations[index - 1].is_comment():
205 index = index - 1
206 test_expectations.pop(index)
207
208 while index and test_expectations[index - 1].is_whitespace() :
209 index = index - 1
210 test_expectations.pop(index)
211
212 return test_expectations
213
214 def write_test_expectations(self, test_expectations, test_expectations_file) :
215 self._host.filesystem.write_text_file(
216 test_expectations_file,
217 TestExpectations.list_to_string(test_expectations, reconstitute_only _these=[]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698