OLD | NEW |
---|---|
(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 Additionally, the runs don't all have to be Passing to remove the line; | |
23 as long as the non-Passing results are of a type not specified in the | |
24 expectation (e.g. Crash) this line will be remoed. | |
qyearsley
2016/03/10 19:13:58
Excellent, this is helpful.
I'm not sure, but may
bokan
2016/03/16 20:24:53
Done.
| |
25 """), formatter_class=argparse.RawTextHelpFormatter) | |
26 args = parser.parse_args(argv) | |
27 | |
28 port = host.port_factory.get() | |
29 bot_test_expectations_factory = BotTestExpectationsFactory() | |
30 | |
31 remove_flakes_o_matic = RemoveFlakesOMatic(host, | |
32 port, | |
33 bot_test_expectations_factory) | |
34 | |
35 test_expectations = remove_flakes_o_matic.get_updated_test_expectations() | |
36 | |
37 expectations_file = port.path_to_generic_test_expectations_file() | |
38 | |
39 remove_flakes_o_matic.write_test_expectations(test_expectations, | |
40 expectations_file) | |
41 | |
42 | |
43 class RemoveFlakesOMatic: | |
44 def __init__(self, host, port, bot_test_expectations_factory): | |
45 self._host = host | |
46 self._port = port | |
qyearsley
2016/03/10 19:13:58
Host is the host on which the expectations file wi
bokan
2016/03/16 20:24:53
I'm not 100% clear on what Host and Port are but I
| |
47 self._expectations_factory = bot_test_expectations_factory | |
48 | |
49 def _can_delete_line(self, test_expectation_line): | |
50 """Returns whether a given line in the expectations can be removed based | |
51 on its results on the bots (i.e. remove if the bots show its not flaky.) | |
52 | |
53 Args: | |
54 test_expectation_line (TestExpectationLine): A line in the test | |
55 expectation file to test for possible removal. | |
56 | |
qyearsley
2016/03/10 19:13:58
Optional docstring recommendations:
- It's faste
bokan
2016/03/16 20:24:53
Thanks, done.
| |
57 """ | |
58 expectations = test_expectation_line.expectations | |
59 if len(expectations) < 2: | |
60 return False | |
61 | |
62 if self._has_unstrippable_expectations(expectations): | |
63 return False | |
64 | |
65 if not self._has_pass_expectation(expectations): | |
66 return False | |
67 | |
68 # The line can be deleted if the only expectation on the line that appea rs in the actual | |
69 # results is the PASS expectation. | |
70 for config in test_expectation_line.matching_configurations: | |
71 builder_name = builders.builder_name_for_specifiers(config.version, config.build_type) | |
72 | |
73 if not builder_name: | |
74 # TODO(bokan): We probably want to report a problem if we can't | |
75 # turn the specifiers into a builder name. | |
76 # TODO(bokan): Matching configurations often give us bots that d on't have a | |
77 # builder in builders.py's exact_matches. Should we ignore those or be conservative | |
78 # and assume we need these expectations to make a decision? Also , testing isn't | |
79 # well suited to changing the list of builders/configurations so if we want to skip | |
80 # here the tests will have to specify results for all builders. | |
81 continue | |
82 | |
83 if builder_name not in self.builder_results_by_path.keys(): | |
84 # TODO(bokan): We probably want to report a problem if we can't get results for | |
85 # a bot. | |
qyearsley
2016/03/10 19:13:58
Sounds reasonable -- question is, should it be con
bokan
2016/03/16 20:24:53
I think not because, for example, if we have an ex
| |
86 continue | |
87 | |
88 results_by_path = self.builder_results_by_path[builder_name] | |
89 | |
90 # No results means the test was all skipped or all results are passi ng. | |
91 if test_expectation_line.path not in results_by_path.keys(): | |
92 continue | |
93 | |
94 results_for_single_test = results_by_path[test_expectation_line.path ] | |
95 | |
96 if self._met_expectations(test_expectation_line, results_for_single_ test) != set(['PASS']): | |
97 return False | |
98 | |
99 return True | |
100 | |
101 def _has_pass_expectation(self, expectations): | |
102 return 'PASS' in expectations | |
103 | |
104 def _met_expectations(self, test_expectation_line, results_for_single_test): | |
qyearsley
2016/03/10 19:13:58
The name of this function currently suggests it mi
bokan
2016/03/16 20:24:53
I don't really like testing private internals but
| |
105 # TODO(bokan): Does this not exist in a more central place? | |
106 def replace_failing_with_fail(expectation): | |
107 if expectation in ('TEXT', 'IMAGE', 'IMAGE+TEXT', 'AUDIO'): | |
108 return 'FAIL' | |
109 else: | |
110 return expectation | |
111 | |
112 actual_results = {replace_failing_with_fail(r) for r in results_for_sing le_test} | |
113 | |
114 return set(test_expectation_line.expectations) & actual_results | |
115 | |
116 def _has_unstrippable_expectations(self, expectations): | |
qyearsley
2016/03/10 19:13:58
expectations here is a list of strings, right?
bokan
2016/03/16 20:24:53
Right, docstring added.
| |
117 unstrippable_expectations = ('REBASELINE', 'NEEDSREBASELINE', | |
118 'NEEDSMANUALREBASELINE', 'SLOW') | |
119 return any(s in expectations for s in unstrippable_expectations) | |
120 | |
121 def get_updated_test_expectations(self): | |
122 test_expectations = TestExpectations(self._port, include_overrides=False ).expectations() | |
123 | |
124 self.builder_results_by_path = {} | |
125 for builder_name in builders.all_builder_names(): | |
126 expectations_for_builder = ( | |
127 self._expectations_factory.expectations_for_builder(builder_name ) | |
128 ) | |
129 | |
130 # TODO(bokan): What to do if we can't get the results for a builder? | |
131 if not expectations_for_builder: | |
132 continue | |
133 | |
134 self.builder_results_by_path[builder_name] = ( | |
135 expectations_for_builder.all_results_by_path() | |
136 ) | |
qyearsley
2016/03/10 19:13:58
Does getting the builder results require making ne
bokan
2016/03/16 20:24:53
Not sure since that all happens elsewhere but my b
| |
137 | |
138 expectations_to_remove = [] | |
139 | |
140 for expectation in test_expectations: | |
141 if self._can_delete_line(expectation): | |
142 expectations_to_remove.append(expectation) | |
143 | |
144 for expectation in expectations_to_remove: | |
145 index = test_expectations.index(expectation) | |
146 test_expectations.remove(expectation) | |
147 | |
148 # Remove associated comments and whitespace if we've removed the las t expectation under | |
149 # a comment block. Only remove a comment block if it's not separated from the tests by | |
150 # whitespace. | |
151 if index == len(test_expectations) or test_expectations[index].is_wh itespace_or_comment(): | |
152 removed_whitespace = False | |
153 while index and test_expectations[index - 1].is_whitespace(): | |
154 index = index - 1 | |
155 test_expectations.pop(index) | |
156 removed_whitespace = True | |
157 | |
158 if not removed_whitespace: | |
159 while index and test_expectations[index - 1].is_comment(): | |
160 index = index - 1 | |
161 test_expectations.pop(index) | |
162 | |
163 while index and test_expectations[index - 1].is_whitespace() : | |
164 index = index - 1 | |
165 test_expectations.pop(index) | |
166 | |
167 return test_expectations | |
168 | |
169 def write_test_expectations(self, test_expectations, test_expectations_file) : | |
170 self._host.filesystem.write_text_file( | |
171 test_expectations_file, | |
172 TestExpectations.list_to_string(test_expectations, reconstitute_only _these=[])) | |
OLD | NEW |