|
Lines 1-4
a/Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py_sec1
|
| 1 |
# Copyright (c) 2010 Google Inc. All rights reserved. |
1 |
# Copyright (c) 2010 Google Inc. All rights reserved. |
|
|
2 |
# Copyright (c) 2016 Apple Inc. All rights reserved. |
| 2 |
# |
3 |
# |
| 3 |
# Redistribution and use in source and binary forms, with or without |
4 |
# Redistribution and use in source and binary forms, with or without |
| 4 |
# modification, are permitted provided that the following conditions are |
5 |
# modification, are permitted provided that the following conditions are |
|
Lines 123-149
class PatchAnalysisTask(object):
a/Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py_sec2
|
| 123 |
"Patch does not apply") |
124 |
"Patch does not apply") |
| 124 |
|
125 |
|
| 125 |
def _build(self): |
126 |
def _build(self): |
| 126 |
return self._run_command([ |
127 |
args = [ |
| 127 |
"build", |
128 |
"build", |
| 128 |
"--no-clean", |
129 |
"--no-clean", |
| 129 |
"--no-update", |
130 |
"--no-update", |
| 130 |
"--build-style=%s" % self._delegate.build_style(), |
131 |
"--build-style=%s" % self._delegate.build_style(), |
| 131 |
], |
132 |
] |
| 132 |
"Built patch", |
133 |
|
| 133 |
"Patch does not build") |
134 |
if hasattr(self._delegate, 'group'): |
|
|
135 |
args.append("--group=%s" % self._delegate.group()) |
| 136 |
|
| 137 |
return self._run_command( |
| 138 |
args, |
| 139 |
"Built patch", |
| 140 |
"Patch does not build") |
| 134 |
|
141 |
|
| 135 |
def _build_without_patch(self): |
142 |
def _build_without_patch(self): |
| 136 |
return self._run_command([ |
143 |
args = [ |
| 137 |
"build", |
144 |
"build", |
| 138 |
"--force-clean", |
145 |
"--force-clean", |
| 139 |
"--no-update", |
146 |
"--no-update", |
| 140 |
"--build-style=%s" % self._delegate.build_style(), |
147 |
"--build-style=%s" % self._delegate.build_style(), |
| 141 |
], |
148 |
] |
| 142 |
"Able to build without patch", |
149 |
|
| 143 |
"Unable to build without patch") |
150 |
if hasattr(self._delegate, 'group'): |
|
|
151 |
args.append("--group=%s" % self._delegate.group()) |
| 152 |
|
| 153 |
return self._run_command( |
| 154 |
args, |
| 155 |
"Able to build without patch", |
| 156 |
"Unable to build without patch") |
| 144 |
|
157 |
|
| 145 |
def _test(self): |
158 |
def _test(self): |
| 146 |
return self._run_command([ |
159 |
args = [ |
| 147 |
"build-and-test", |
160 |
"build-and-test", |
| 148 |
"--no-clean", |
161 |
"--no-clean", |
| 149 |
"--no-update", |
162 |
"--no-update", |
|
Lines 151-162
class PatchAnalysisTask(object):
a/Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py_sec3
|
| 151 |
"--test", |
164 |
"--test", |
| 152 |
"--non-interactive", |
165 |
"--non-interactive", |
| 153 |
"--build-style=%s" % self._delegate.build_style(), |
166 |
"--build-style=%s" % self._delegate.build_style(), |
| 154 |
], |
167 |
] |
| 155 |
"Passed tests", |
168 |
|
| 156 |
"Patch does not pass tests") |
169 |
if hasattr(self._delegate, 'group'): |
|
|
170 |
args.append("--group=%s" % self._delegate.group()) |
| 171 |
|
| 172 |
return self._run_command( |
| 173 |
args, |
| 174 |
"Passed tests", |
| 175 |
"Patch does not pass tests") |
| 157 |
|
176 |
|
| 158 |
def _build_and_test_without_patch(self): |
177 |
def _build_and_test_without_patch(self): |
| 159 |
return self._run_command([ |
178 |
args = [ |
| 160 |
"build-and-test", |
179 |
"build-and-test", |
| 161 |
"--force-clean", |
180 |
"--force-clean", |
| 162 |
"--no-update", |
181 |
"--no-update", |
|
Lines 164-172
class PatchAnalysisTask(object):
a/Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py_sec4
|
| 164 |
"--test", |
183 |
"--test", |
| 165 |
"--non-interactive", |
184 |
"--non-interactive", |
| 166 |
"--build-style=%s" % self._delegate.build_style(), |
185 |
"--build-style=%s" % self._delegate.build_style(), |
| 167 |
], |
186 |
] |
| 168 |
"Able to pass tests without patch", |
187 |
|
| 169 |
"Unable to pass tests without patch (tree is red?)") |
188 |
if hasattr(self._delegate, 'group'): |
|
|
189 |
args.append("--group=%s" % self._delegate.group()) |
| 190 |
|
| 191 |
return self._run_command( |
| 192 |
args, |
| 193 |
"Able to pass tests without patch", |
| 194 |
"Unable to pass tests without patch (tree is red?)") |
| 170 |
|
195 |
|
| 171 |
def _land(self): |
196 |
def _land(self): |
| 172 |
# Unclear if this should pass --quiet or not. If --parent-command always does the reporting, then it should. |
197 |
# Unclear if this should pass --quiet or not. If --parent-command always does the reporting, then it should. |
|
Lines 210-278
class PatchAnalysisTask(object):
a/Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py_sec5
|
| 210 |
if self._test(): |
235 |
if self._test(): |
| 211 |
return True |
236 |
return True |
| 212 |
|
237 |
|
| 213 |
# Note: archive_last_test_results deletes the results directory, making these calls order-sensitve. |
238 |
# FIXME: Abstract out the retry logic instead of casing on the test suite here. |
| 214 |
# We could remove this dependency by building the test_results from the archive. |
239 |
if hasattr(self._delegate, 'group') and self._delegate.group() == "jsc": |
| 215 |
first_results = self._delegate.test_results() |
240 |
first_results = self._delegate.test_results() |
| 216 |
first_results_archive = self._delegate.archive_last_test_results(self._patch) |
241 |
first_script_error = self._script_error |
| 217 |
first_script_error = self._script_error |
242 |
if first_results.all_passed(): |
| 218 |
first_failure_status_id = self.failure_status_id |
243 |
return True |
| 219 |
|
|
|
| 220 |
if self._test() and not first_results.did_exceed_test_failure_limit(): |
| 221 |
# Only report flaky tests if we were successful at parsing results.json and archiving results. |
| 222 |
if first_results and first_results_archive: |
| 223 |
self._report_flaky_tests(first_results.failing_test_results(), first_results_archive) |
| 224 |
return True |
| 225 |
|
| 226 |
second_results = self._delegate.test_results() |
| 227 |
second_results_archive = self._delegate.archive_last_test_results(self._patch) |
| 228 |
second_script_error = self._script_error |
| 229 |
second_failure_status_id = self.failure_status_id |
| 230 |
|
244 |
|
| 231 |
if second_results.did_exceed_test_failure_limit() and first_results.did_exceed_test_failure_limit(): |
|
|
| 232 |
self._build_and_test_without_patch() |
245 |
self._build_and_test_without_patch() |
| 233 |
clean_tree_results = self._delegate.test_results() |
246 |
clean_tree_results = self._delegate.test_results() |
| 234 |
|
247 |
|
| 235 |
if (len(first_results.failing_tests()) - len(clean_tree_results.failing_tests())) <= 5: |
248 |
# If the patch's failures are a subset of the tree's failures, the patch should pass EWS. |
| 236 |
return False |
249 |
if first_results <= clean_tree_results: |
|
|
250 |
return True |
| 237 |
|
251 |
|
| 238 |
self.failure_status_id = first_failure_status_id |
252 |
return self.report_failure(None, first_results, first_script_error) |
| 239 |
|
253 |
|
| 240 |
return self.report_failure(first_results_archive, first_results, first_script_error) |
254 |
else: |
|
|
255 |
# Note: archive_last_test_results deletes the results directory, making these calls order-sensitve. |
| 256 |
# We could remove this dependency by building the test_results from the archive. |
| 257 |
first_results = self._delegate.test_results() |
| 258 |
first_results_archive = self._delegate.archive_last_test_results(self._patch) |
| 259 |
first_script_error = self._script_error |
| 260 |
first_failure_status_id = self.failure_status_id |
| 241 |
|
261 |
|
| 242 |
if second_results.did_exceed_test_failure_limit(): |
262 |
if self._test() and not first_results.did_exceed_test_failure_limit(): |
| 243 |
self._should_defer_patch_or_throw(first_results.failing_test_results(), first_results_archive, first_script_error, first_failure_status_id) |
263 |
# Only report flaky tests if we were successful at parsing results.json and archiving results. |
| 244 |
return False |
264 |
if first_results and first_results_archive: |
|
|
265 |
self._report_flaky_tests(first_results.failing_test_results(), first_results_archive) |
| 266 |
return True |
| 245 |
|
267 |
|
| 246 |
if first_results.did_exceed_test_failure_limit(): |
268 |
second_results = self._delegate.test_results() |
| 247 |
self._should_defer_patch_or_throw(second_results.failing_test_results(), second_results_archive, second_script_error, second_failure_status_id) |
269 |
second_results_archive = self._delegate.archive_last_test_results(self._patch) |
| 248 |
return False |
270 |
second_script_error = self._script_error |
|
|
271 |
second_failure_status_id = self.failure_status_id |
| 249 |
|
272 |
|
| 250 |
if self._results_failed_different_tests(first_results, second_results): |
273 |
if second_results.did_exceed_test_failure_limit() and first_results.did_exceed_test_failure_limit(): |
| 251 |
first_failing_results_set = frozenset(first_results.failing_test_results()) |
274 |
self._build_and_test_without_patch() |
| 252 |
second_failing_results_set = frozenset(second_results.failing_test_results()) |
275 |
clean_tree_results = self._delegate.test_results() |
| 253 |
|
276 |
|
| 254 |
tests_that_only_failed_first = first_failing_results_set.difference(second_failing_results_set) |
277 |
if (len(first_results.failing_tests()) - len(clean_tree_results.failing_tests())) <= 5: |
| 255 |
self._report_flaky_tests(tests_that_only_failed_first, first_results_archive) |
278 |
return False |
| 256 |
|
279 |
|
| 257 |
tests_that_only_failed_second = second_failing_results_set.difference(first_failing_results_set) |
280 |
self.failure_status_id = first_failure_status_id |
| 258 |
self._report_flaky_tests(tests_that_only_failed_second, second_results_archive) |
|
|
| 259 |
|
281 |
|
| 260 |
tests_that_consistently_failed = first_failing_results_set.intersection(second_failing_results_set) |
282 |
return self.report_failure(first_results_archive, first_results, first_script_error) |
| 261 |
if tests_that_consistently_failed: |
|
|
| 262 |
if self._should_defer_patch_or_throw(tests_that_consistently_failed, first_results_archive, first_script_error, first_failure_status_id): |
| 263 |
return False # Defer patch |
| 264 |
|
283 |
|
| 265 |
# At this point we know that at least one test flaked, but no consistent failures |
284 |
if second_results.did_exceed_test_failure_limit(): |
| 266 |
# were introduced. This is a bit of a grey-zone. |
285 |
self._should_defer_patch_or_throw(first_results.failing_test_results(), first_results_archive, first_script_error, first_failure_status_id) |
| 267 |
return False # Defer patch |
286 |
return False |
| 268 |
|
287 |
|
| 269 |
if self._should_defer_patch_or_throw(first_results.failing_test_results(), first_results_archive, first_script_error, first_failure_status_id): |
288 |
if first_results.did_exceed_test_failure_limit(): |
| 270 |
return False # Defer patch |
289 |
self._should_defer_patch_or_throw(second_results.failing_test_results(), second_results_archive, second_script_error, second_failure_status_id) |
|
|
290 |
return False |
| 271 |
|
291 |
|
| 272 |
# At this point, we know that the first and second runs had the exact same failures, |
292 |
if self._results_failed_different_tests(first_results, second_results): |
| 273 |
# and that those failures are all present on the clean tree, so we can say with certainty |
293 |
first_failing_results_set = frozenset(first_results.failing_test_results()) |
| 274 |
# that the patch is good. |
294 |
second_failing_results_set = frozenset(second_results.failing_test_results()) |
| 275 |
return True |
295 |
|
|
|
296 |
tests_that_only_failed_first = first_failing_results_set.difference(second_failing_results_set) |
| 297 |
self._report_flaky_tests(tests_that_only_failed_first, first_results_archive) |
| 298 |
|
| 299 |
tests_that_only_failed_second = second_failing_results_set.difference(first_failing_results_set) |
| 300 |
self._report_flaky_tests(tests_that_only_failed_second, second_results_archive) |
| 301 |
|
| 302 |
tests_that_consistently_failed = first_failing_results_set.intersection(second_failing_results_set) |
| 303 |
if tests_that_consistently_failed: |
| 304 |
if self._should_defer_patch_or_throw(tests_that_consistently_failed, first_results_archive, first_script_error, first_failure_status_id): |
| 305 |
return False # Defer patch |
| 306 |
|
| 307 |
# At this point we know that at least one test flaked, but no consistent failures |
| 308 |
# were introduced. This is a bit of a grey-zone. |
| 309 |
return False # Defer patch |
| 310 |
|
| 311 |
if self._should_defer_patch_or_throw(first_results.failing_test_results(), first_results_archive, first_script_error, first_failure_status_id): |
| 312 |
return False # Defer patch |
| 313 |
|
| 314 |
# At this point, we know that the first and second runs had the exact same failures, |
| 315 |
# and that those failures are all present on the clean tree, so we can say with certainty |
| 316 |
# that the patch is good. |
| 317 |
return True |
| 276 |
|
318 |
|
| 277 |
def results_archive_from_patch_test_run(self, patch): |
319 |
def results_archive_from_patch_test_run(self, patch): |
| 278 |
assert(self._patch.id() == patch.id()) # PatchAnalysisTask is not currently re-useable. |
320 |
assert(self._patch.id() == patch.id()) # PatchAnalysisTask is not currently re-useable. |