| 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 |