OLD | NEW |
| (Empty) |
1 # Copyright (c) 2010 Google Inc. All rights reserved. | |
2 # | |
3 # Redistribution and use in source and binary forms, with or without | |
4 # modification, are permitted provided that the following conditions are | |
5 # met: | |
6 # | |
7 # * Redistributions of source code must retain the above copyright | |
8 # notice, this list of conditions and the following disclaimer. | |
9 # * Redistributions in binary form must reproduce the above | |
10 # copyright notice, this list of conditions and the following disclaimer | |
11 # in the documentation and/or other materials provided with the | |
12 # distribution. | |
13 # * Neither the name of Google Inc. nor the names of its | |
14 # contributors may be used to endorse or promote products derived from | |
15 # this software without specific prior written permission. | |
16 # | |
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
28 | |
29 from webkitpy.common.system.executive import ScriptError | |
30 from webkitpy.common.net.layouttestresults import LayoutTestResults | |
31 | |
32 | |
33 class UnableToApplyPatch(Exception): | |
34 def __init__(self, patch): | |
35 Exception.__init__(self) | |
36 self.patch = patch | |
37 | |
38 | |
39 class PatchAnalysisTaskDelegate(object): | |
40 def parent_command(self): | |
41 raise NotImplementedError("subclasses must implement") | |
42 | |
43 def run_command(self, command): | |
44 raise NotImplementedError("subclasses must implement") | |
45 | |
46 def command_passed(self, message, patch): | |
47 raise NotImplementedError("subclasses must implement") | |
48 | |
49 def command_failed(self, message, script_error, patch): | |
50 raise NotImplementedError("subclasses must implement") | |
51 | |
52 def refetch_patch(self, patch): | |
53 raise NotImplementedError("subclasses must implement") | |
54 | |
55 def expected_failures(self): | |
56 raise NotImplementedError("subclasses must implement") | |
57 | |
58 def test_results(self): | |
59 raise NotImplementedError("subclasses must implement") | |
60 | |
61 def archive_last_test_results(self, patch): | |
62 raise NotImplementedError("subclasses must implement") | |
63 | |
64 def build_style(self): | |
65 raise NotImplementedError("subclasses must implement") | |
66 | |
67 # We could make results_archive optional, but for now it's required. | |
68 def report_flaky_tests(self, patch, flaky_tests, results_archive): | |
69 raise NotImplementedError("subclasses must implement") | |
70 | |
71 | |
72 class PatchAnalysisTask(object): | |
73 def __init__(self, delegate, patch): | |
74 self._delegate = delegate | |
75 self._patch = patch | |
76 self._script_error = None | |
77 self._results_archive_from_patch_test_run = None | |
78 self._results_from_patch_test_run = None | |
79 self._expected_failures = delegate.expected_failures() | |
80 | |
81 def _run_command(self, command, success_message, failure_message): | |
82 try: | |
83 self._delegate.run_command(command) | |
84 self._delegate.command_passed(success_message, patch=self._patch) | |
85 return True | |
86 except ScriptError, e: | |
87 self._script_error = e | |
88 self.failure_status_id = self._delegate.command_failed(failure_messa
ge, script_error=self._script_error, patch=self._patch) | |
89 return False | |
90 | |
91 def _clean(self): | |
92 return self._run_command([ | |
93 "clean", | |
94 ], | |
95 "Cleaned working directory", | |
96 "Unable to clean working directory") | |
97 | |
98 def _update(self): | |
99 # FIXME: Ideally the status server log message should include which revi
sion we updated to. | |
100 return self._run_command([ | |
101 "update", | |
102 ], | |
103 "Updated working directory", | |
104 "Unable to update working directory") | |
105 | |
106 def _apply(self): | |
107 return self._run_command([ | |
108 "apply-attachment", | |
109 "--no-update", | |
110 "--non-interactive", | |
111 self._patch.id(), | |
112 ], | |
113 "Applied patch", | |
114 "Patch does not apply") | |
115 | |
116 def _build(self): | |
117 return self._run_command([ | |
118 "build", | |
119 "--no-clean", | |
120 "--no-update", | |
121 "--build-style=%s" % self._delegate.build_style(), | |
122 ], | |
123 "Built patch", | |
124 "Patch does not build") | |
125 | |
126 def _build_without_patch(self): | |
127 return self._run_command([ | |
128 "build", | |
129 "--force-clean", | |
130 "--no-update", | |
131 "--build-style=%s" % self._delegate.build_style(), | |
132 ], | |
133 "Able to build without patch", | |
134 "Unable to build without patch") | |
135 | |
136 def _test(self): | |
137 return self._run_command([ | |
138 "build-and-test", | |
139 "--no-clean", | |
140 "--no-update", | |
141 # Notice that we don't pass --build, which means we won't build! | |
142 "--test", | |
143 "--non-interactive", | |
144 ], | |
145 "Passed tests", | |
146 "Patch does not pass tests") | |
147 | |
148 def _build_and_test_without_patch(self): | |
149 return self._run_command([ | |
150 "build-and-test", | |
151 "--force-clean", | |
152 "--no-update", | |
153 "--build", | |
154 "--test", | |
155 "--non-interactive", | |
156 ], | |
157 "Able to pass tests without patch", | |
158 "Unable to pass tests without patch (tree is red?)") | |
159 | |
160 def _land(self): | |
161 # Unclear if this should pass --quiet or not. If --parent-command alway
s does the reporting, then it should. | |
162 return self._run_command([ | |
163 "land-attachment", | |
164 "--force-clean", | |
165 "--non-interactive", | |
166 "--parent-command=" + self._delegate.parent_command(), | |
167 self._patch.id(), | |
168 ], | |
169 "Landed patch", | |
170 "Unable to land patch") | |
171 | |
172 def _report_flaky_tests(self, flaky_test_results, results_archive): | |
173 self._delegate.report_flaky_tests(self._patch, flaky_test_results, resul
ts_archive) | |
174 | |
175 def _results_failed_different_tests(self, first, second): | |
176 first_failing_tests = [] if not first else first.failing_tests() | |
177 second_failing_tests = [] if not second else second.failing_tests() | |
178 return first_failing_tests != second_failing_tests | |
179 | |
180 def _test_patch(self): | |
181 if self._test(): | |
182 return True | |
183 | |
184 # Note: archive_last_test_results deletes the results directory, making
these calls order-sensitve. | |
185 # We could remove this dependency by building the test_results from the
archive. | |
186 first_results = self._delegate.test_results() | |
187 first_results_archive = self._delegate.archive_last_test_results(self._p
atch) | |
188 first_script_error = self._script_error | |
189 first_failure_status_id = self.failure_status_id | |
190 | |
191 if self._expected_failures.failures_were_expected(first_results): | |
192 return True | |
193 | |
194 if self._test(): | |
195 # Only report flaky tests if we were successful at parsing results.j
son and archiving results. | |
196 if first_results and first_results_archive: | |
197 self._report_flaky_tests(first_results.failing_test_results(), f
irst_results_archive) | |
198 return True | |
199 | |
200 second_results = self._delegate.test_results() | |
201 if self._results_failed_different_tests(first_results, second_results): | |
202 # We could report flaky tests here, but we would need to be careful | |
203 # to use similar checks to ExpectedFailures._can_trust_results | |
204 # to make sure we don't report constant failures as flakes when | |
205 # we happen to hit the --exit-after-N-failures limit. | |
206 # See https://bugs.webkit.org/show_bug.cgi?id=51272 | |
207 return False | |
208 | |
209 # Archive (and remove) second results so test_results() after | |
210 # build_and_test_without_patch won't use second results instead of the c
lean-tree results. | |
211 second_results_archive = self._delegate.archive_last_test_results(self._
patch) | |
212 | |
213 if self._build_and_test_without_patch(): | |
214 # The error from the previous ._test() run is real, report it. | |
215 return self.report_failure(first_results_archive, first_results, fir
st_script_error) | |
216 | |
217 clean_tree_results = self._delegate.test_results() | |
218 self._expected_failures.update(clean_tree_results) | |
219 | |
220 # Re-check if the original results are now to be expected to avoid a ful
l re-try. | |
221 if self._expected_failures.failures_were_expected(first_results): | |
222 return True | |
223 | |
224 # Now that we have updated information about failing tests with a clean
checkout, we can | |
225 # tell if our original failures were unexpected and fail the patch if ne
cessary. | |
226 if self._expected_failures.unexpected_failures_observed(first_results): | |
227 self.failure_status_id = first_failure_status_id | |
228 return self.report_failure(first_results_archive, first_results, fir
st_script_error) | |
229 | |
230 # We don't know what's going on. The tree is likely very red (beyond ou
r layout-test-results | |
231 # failure limit), just keep retrying the patch. until someone fixes the
tree. | |
232 return False | |
233 | |
234 def results_archive_from_patch_test_run(self, patch): | |
235 assert(self._patch.id() == patch.id()) # PatchAnalysisTask is not curre
ntly re-useable. | |
236 return self._results_archive_from_patch_test_run | |
237 | |
238 def results_from_patch_test_run(self, patch): | |
239 assert(self._patch.id() == patch.id()) # PatchAnalysisTask is not curre
ntly re-useable. | |
240 return self._results_from_patch_test_run | |
241 | |
242 def report_failure(self, results_archive=None, results=None, script_error=No
ne): | |
243 if not self.validate(): | |
244 return False | |
245 self._results_archive_from_patch_test_run = results_archive | |
246 self._results_from_patch_test_run = results | |
247 raise script_error or self._script_error | |
248 | |
249 def validate(self): | |
250 raise NotImplementedError("subclasses must implement") | |
251 | |
252 def run(self): | |
253 raise NotImplementedError("subclasses must implement") | |
OLD | NEW |