|
Lines 56-61
WithProperties = properties.WithProperties
a/Tools/CISupport/ews-build/steps.py_sec1
|
| 56 |
Interpolate = properties.Interpolate |
56 |
Interpolate = properties.Interpolate |
| 57 |
|
57 |
|
| 58 |
|
58 |
|
|
|
59 |
class BufferLogHeaderObserver(logobserver.BufferLogObserver): |
| 60 |
|
| 61 |
def __init__(self, **kwargs): |
| 62 |
self.headers = [] |
| 63 |
super(BufferLogHeaderObserver, self).__init__(**kwargs) |
| 64 |
|
| 65 |
def headerReceived(self, data): |
| 66 |
self.headers.append(data) |
| 67 |
|
| 68 |
def getHeaders(self): |
| 69 |
return self._get(self.headers) |
| 70 |
|
| 71 |
|
| 59 |
class ConfigureBuild(buildstep.BuildStep): |
72 |
class ConfigureBuild(buildstep.BuildStep): |
| 60 |
name = 'configure-build' |
73 |
name = 'configure-build' |
| 61 |
description = ['configuring build'] |
74 |
description = ['configuring build'] |
|
Lines 734-739
class BugzillaMixin(object):
a/Tools/CISupport/ews-build/steps.py_sec2
|
| 734 |
return False |
747 |
return False |
| 735 |
return True |
748 |
return True |
| 736 |
|
749 |
|
|
|
750 |
def send_email_for_infrastructure_issue(self, infrastructure_issue_text): |
| 751 |
try: |
| 752 |
builder_name = self.getProperty('buildername', '') |
| 753 |
worker_name = self.getProperty('workername', '') |
| 754 |
build_url = '{}#/builders/{}/builds/{}'.format(self.master.config.buildbotURL, self.build._builderid, self.build.number) |
| 755 |
email_subject = 'Infrastructure issue at {}'.format(builder_name) |
| 756 |
email_text = 'The following infrastructure issue happened at:\n\n' |
| 757 |
email_text += ' - Build : {}\n'.format(build_url) |
| 758 |
email_text += ' - Builder : {}\n'.format(builder_name) |
| 759 |
email_text += ' - Worker : {}\n'.format(worker_name) |
| 760 |
email_text += ' - Issue: {}\n'.format(infrastructure_issue_text) |
| 761 |
send_email_to_bot_watchers(email_subject, email_text, builder_name, 'infrastructure-{}'.format(builder_name)) |
| 762 |
except Exception as e: |
| 763 |
print('Error in sending email for infrastructure issue: {}'.format(e)) |
| 764 |
|
| 737 |
def get_bugzilla_api_key(self): |
765 |
def get_bugzilla_api_key(self): |
| 738 |
try: |
766 |
try: |
| 739 |
passwords = json.load(open('passwords.json')) |
767 |
passwords = json.load(open('passwords.json')) |
|
Lines 1170-1175
class Trigger(trigger.Trigger):
a/Tools/CISupport/ews-build/steps.py_sec3
|
| 1170 |
properties_to_pass['ews_revision'] = properties.Property('got_revision') |
1198 |
properties_to_pass['ews_revision'] = properties.Property('got_revision') |
| 1171 |
if self.triggers: |
1199 |
if self.triggers: |
| 1172 |
properties_to_pass['triggers'] = self.triggers |
1200 |
properties_to_pass['triggers'] = self.triggers |
|
|
1201 |
properties_to_pass['retry_count'] = properties.Property('retry_count', default=0) |
| 1173 |
return properties_to_pass |
1202 |
return properties_to_pass |
| 1174 |
|
1203 |
|
| 1175 |
|
1204 |
|
|
Lines 2210-2216
class RunWebKitTests(shell.Test):
a/Tools/CISupport/ews-build/steps.py_sec4
|
| 2210 |
if patch_author in ['webkit-wpt-import-bot@igalia.com']: |
2239 |
if patch_author in ['webkit-wpt-import-bot@igalia.com']: |
| 2211 |
self.setCommand(self.command + ['imported/w3c/web-platform-tests']) |
2240 |
self.setCommand(self.command + ['imported/w3c/web-platform-tests']) |
| 2212 |
else: |
2241 |
else: |
| 2213 |
self.setCommand(self.command + ['--exit-after-n-failures', self.EXIT_AFTER_FAILURES, '--skip-failing-tests']) |
2242 |
if self.EXIT_AFTER_FAILURES is not None: |
|
|
2243 |
self.setCommand(self.command + ['--exit-after-n-failures', '{}'.format(self.EXIT_AFTER_FAILURES)]) |
| 2244 |
self.setCommand(self.command + ['--skip-failing-tests']) |
| 2214 |
|
2245 |
|
| 2215 |
if additionalArguments: |
2246 |
if additionalArguments: |
| 2216 |
self.setCommand(self.command + additionalArguments) |
2247 |
self.setCommand(self.command + additionalArguments) |
|
Lines 2243-2250
class RunWebKitTests(shell.Test):
a/Tools/CISupport/ews-build/steps.py_sec5
|
| 2243 |
list_retry_tests = sorted(first_results_failing_tests.union(second_results_failing_tests)) |
2274 |
list_retry_tests = sorted(first_results_failing_tests.union(second_results_failing_tests)) |
| 2244 |
self.setCommand(self.command + list_retry_tests) |
2275 |
self.setCommand(self.command + list_retry_tests) |
| 2245 |
|
2276 |
|
| 2246 |
def start(self): |
2277 |
def start(self, BufferLogObserverClass=logobserver.BufferLogObserver): |
| 2247 |
self.log_observer = logobserver.BufferLogObserver(wantStderr=True) |
2278 |
self.log_observer = BufferLogObserverClass(wantStderr=True) |
| 2248 |
self.addLogObserver('stdio', self.log_observer) |
2279 |
self.addLogObserver('stdio', self.log_observer) |
| 2249 |
self.log_observer_json = logobserver.BufferLogObserver() |
2280 |
self.log_observer_json = logobserver.BufferLogObserver() |
| 2250 |
self.addLogObserver('json', self.log_observer_json) |
2281 |
self.addLogObserver('json', self.log_observer_json) |
|
Lines 2306-2311
class RunWebKitTests(shell.Test):
a/Tools/CISupport/ews-build/steps.py_sec6
|
| 2306 |
if first_results: |
2337 |
if first_results: |
| 2307 |
self.setProperty('first_results_exceed_failure_limit', first_results.did_exceed_test_failure_limit) |
2338 |
self.setProperty('first_results_exceed_failure_limit', first_results.did_exceed_test_failure_limit) |
| 2308 |
self.setProperty('first_run_failures', sorted(first_results.failing_tests)) |
2339 |
self.setProperty('first_run_failures', sorted(first_results.failing_tests)) |
|
|
2340 |
self.setProperty('first_run_flakies', sorted(first_results.flaky_tests)) |
| 2309 |
if first_results.failing_tests: |
2341 |
if first_results.failing_tests: |
| 2310 |
self._addToLog(self.test_failures_log_name, '\n'.join(first_results.failing_tests)) |
2342 |
self._addToLog(self.test_failures_log_name, '\n'.join(first_results.failing_tests)) |
| 2311 |
self._parseRunWebKitTestsOutput(logText) |
2343 |
self._parseRunWebKitTestsOutput(logText) |
|
Lines 2417-2422
class ReRunWebKitTests(RunWebKitTests):
a/Tools/CISupport/ews-build/steps.py_sec7
|
| 2417 |
first_results_failing_tests = set(self.getProperty('first_run_failures', [])) |
2449 |
first_results_failing_tests = set(self.getProperty('first_run_failures', [])) |
| 2418 |
second_results_did_exceed_test_failure_limit = self.getProperty('second_results_exceed_failure_limit') |
2450 |
second_results_did_exceed_test_failure_limit = self.getProperty('second_results_exceed_failure_limit') |
| 2419 |
second_results_failing_tests = set(self.getProperty('second_run_failures', [])) |
2451 |
second_results_failing_tests = set(self.getProperty('second_run_failures', [])) |
|
|
2452 |
# FIXME: here it can be a good idea to also use the info from second_run_flakies and first_run_flakies |
| 2420 |
tests_that_consistently_failed = first_results_failing_tests.intersection(second_results_failing_tests) |
2453 |
tests_that_consistently_failed = first_results_failing_tests.intersection(second_results_failing_tests) |
| 2421 |
flaky_failures = first_results_failing_tests.union(second_results_failing_tests) - first_results_failing_tests.intersection(second_results_failing_tests) |
2454 |
flaky_failures = first_results_failing_tests.union(second_results_failing_tests) - first_results_failing_tests.intersection(second_results_failing_tests) |
| 2422 |
flaky_failures = sorted(list(flaky_failures))[:self.NUM_FAILURES_TO_DISPLAY] |
2455 |
flaky_failures = sorted(list(flaky_failures))[:self.NUM_FAILURES_TO_DISPLAY] |
|
Lines 2455-2460
class ReRunWebKitTests(RunWebKitTests):
a/Tools/CISupport/ews-build/steps.py_sec8
|
| 2455 |
if second_results: |
2488 |
if second_results: |
| 2456 |
self.setProperty('second_results_exceed_failure_limit', second_results.did_exceed_test_failure_limit) |
2489 |
self.setProperty('second_results_exceed_failure_limit', second_results.did_exceed_test_failure_limit) |
| 2457 |
self.setProperty('second_run_failures', sorted(second_results.failing_tests)) |
2490 |
self.setProperty('second_run_failures', sorted(second_results.failing_tests)) |
|
|
2491 |
self.setProperty('second_run_flakies', sorted(second_results.flaky_tests)) |
| 2458 |
if second_results.failing_tests: |
2492 |
if second_results.failing_tests: |
| 2459 |
self._addToLog(self.test_failures_log_name, '\n'.join(second_results.failing_tests)) |
2493 |
self._addToLog(self.test_failures_log_name, '\n'.join(second_results.failing_tests)) |
| 2460 |
self._parseRunWebKitTestsOutput(logText) |
2494 |
self._parseRunWebKitTestsOutput(logText) |
|
Lines 2494-2499
class RunWebKitTestsWithoutPatch(RunWebKitTests):
a/Tools/CISupport/ews-build/steps.py_sec9
|
| 2494 |
if clean_tree_results: |
2528 |
if clean_tree_results: |
| 2495 |
self.setProperty('clean_tree_results_exceed_failure_limit', clean_tree_results.did_exceed_test_failure_limit) |
2529 |
self.setProperty('clean_tree_results_exceed_failure_limit', clean_tree_results.did_exceed_test_failure_limit) |
| 2496 |
self.setProperty('clean_tree_run_failures', clean_tree_results.failing_tests) |
2530 |
self.setProperty('clean_tree_run_failures', clean_tree_results.failing_tests) |
|
|
2531 |
self.setProperty('clean_tree_run_flakies', sorted(clean_tree_results.flaky_tests)) |
| 2497 |
if clean_tree_results.failing_tests: |
2532 |
if clean_tree_results.failing_tests: |
| 2498 |
self._addToLog(self.test_failures_log_name, '\n'.join(clean_tree_results.failing_tests)) |
2533 |
self._addToLog(self.test_failures_log_name, '\n'.join(clean_tree_results.failing_tests)) |
| 2499 |
self._parseRunWebKitTestsOutput(logText) |
2534 |
self._parseRunWebKitTestsOutput(logText) |
|
Lines 2505-2522
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec10
|
| 2505 |
descriptionDone = ['analyze-layout-tests-results'] |
2540 |
descriptionDone = ['analyze-layout-tests-results'] |
| 2506 |
NUM_FAILURES_TO_DISPLAY = 10 |
2541 |
NUM_FAILURES_TO_DISPLAY = 10 |
| 2507 |
|
2542 |
|
| 2508 |
def report_failure(self, new_failures): |
2543 |
def report_failure(self, new_failures, exceed_failure_limit=False): |
| 2509 |
self.finished(FAILURE) |
2544 |
self.finished(FAILURE) |
| 2510 |
self.build.results = FAILURE |
2545 |
self.build.results = FAILURE |
| 2511 |
if not new_failures: |
2546 |
if not new_failures: |
| 2512 |
message = 'Found unexpected failure with patch' |
2547 |
message = 'Found unexpected failure with patch' |
| 2513 |
else: |
2548 |
else: |
| 2514 |
pluralSuffix = 's' if len(new_failures) > 1 else '' |
2549 |
pluralSuffix = 's' if len(new_failures) > 1 else '' |
|
|
2550 |
if exceed_failure_limit: |
| 2551 |
message = 'Failure limit exceed. At least found' |
| 2552 |
else: |
| 2553 |
message = 'Found' |
| 2515 |
new_failures_string = ', '.join(sorted(new_failures)[:self.NUM_FAILURES_TO_DISPLAY]) |
2554 |
new_failures_string = ', '.join(sorted(new_failures)[:self.NUM_FAILURES_TO_DISPLAY]) |
| 2516 |
message = 'Found {} new test failure{}: {}'.format(len(new_failures), pluralSuffix, new_failures_string) |
2555 |
message += ' {} new test failure{}: {}'.format(len(new_failures), pluralSuffix, new_failures_string) |
| 2517 |
if len(new_failures) > self.NUM_FAILURES_TO_DISPLAY: |
2556 |
if len(new_failures) > self.NUM_FAILURES_TO_DISPLAY: |
| 2518 |
message += ' ...' |
2557 |
message += ' ...' |
| 2519 |
self.send_email_for_new_test_failures(new_failures) |
2558 |
self.send_email_for_new_test_failures(new_failures, exceed_failure_limit) |
| 2520 |
self.descriptionDone = message |
2559 |
self.descriptionDone = message |
| 2521 |
self.setProperty('build_finish_summary', message) |
2560 |
self.setProperty('build_finish_summary', message) |
| 2522 |
|
2561 |
|
|
Lines 2573-2579
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec11
|
| 2573 |
def _results_failed_different_tests(self, first_results_failing_tests, second_results_failing_tests): |
2612 |
def _results_failed_different_tests(self, first_results_failing_tests, second_results_failing_tests): |
| 2574 |
return first_results_failing_tests != second_results_failing_tests |
2613 |
return first_results_failing_tests != second_results_failing_tests |
| 2575 |
|
2614 |
|
| 2576 |
def send_email_for_flaky_failure(self, test_name): |
2615 |
def send_email_for_flaky_failure(self, test_name, step_str=None): |
| 2577 |
try: |
2616 |
try: |
| 2578 |
builder_name = self.getProperty('buildername', '') |
2617 |
builder_name = self.getProperty('buildername', '') |
| 2579 |
worker_name = self.getProperty('workername', '') |
2618 |
worker_name = self.getProperty('workername', '') |
|
Lines 2582-2587
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec12
|
| 2582 |
|
2621 |
|
| 2583 |
email_subject = 'Flaky test: {}'.format(test_name) |
2622 |
email_subject = 'Flaky test: {}'.format(test_name) |
| 2584 |
email_text = 'Flaky test: {}\n\nBuild: {}\n\nBuilder: {}\n\nWorker: {}\n\nHistory: {}'.format(test_name, build_url, builder_name, worker_name, history_url) |
2623 |
email_text = 'Flaky test: {}\n\nBuild: {}\n\nBuilder: {}\n\nWorker: {}\n\nHistory: {}'.format(test_name, build_url, builder_name, worker_name, history_url) |
|
|
2624 |
if step_str: |
| 2625 |
email_text += '\nThis test was flaky on the steps: {}'.format(step_str) |
| 2585 |
send_email_to_bot_watchers(email_subject, email_text, builder_name, 'flaky-{}'.format(test_name)) |
2626 |
send_email_to_bot_watchers(email_subject, email_text, builder_name, 'flaky-{}'.format(test_name)) |
| 2586 |
except Exception as e: |
2627 |
except Exception as e: |
| 2587 |
print('Error in sending email for flaky failure: {}'.format(e)) |
2628 |
print('Error in sending email for flaky failure: {}'.format(e)) |
|
Lines 2599-2605
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec13
|
| 2599 |
except Exception as e: |
2640 |
except Exception as e: |
| 2600 |
print('Error in sending email for pre-existing failure: {}'.format(e)) |
2641 |
print('Error in sending email for pre-existing failure: {}'.format(e)) |
| 2601 |
|
2642 |
|
| 2602 |
def send_email_for_new_test_failures(self, test_names): |
2643 |
def send_email_for_new_test_failures(self, test_names, exceed_failure_limit=False): |
| 2603 |
try: |
2644 |
try: |
| 2604 |
patch_id = self.getProperty('patch_id', '') |
2645 |
patch_id = self.getProperty('patch_id', '') |
| 2605 |
if not self.should_send_email(patch_id): |
2646 |
if not self.should_send_email(patch_id): |
|
Lines 2621-2626
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec14
|
| 2621 |
email_text += ' while testing <a href="{}">Patch {}</a>'.format(Bugzilla.patch_url(patch_id), patch_id) |
2662 |
email_text += ' while testing <a href="{}">Patch {}</a>'.format(Bugzilla.patch_url(patch_id), patch_id) |
| 2622 |
email_text += ' for <a href="{}">Bug {}</a>.'.format(Bugzilla.bug_url(bug_id), bug_id) |
2663 |
email_text += ' for <a href="{}">Bug {}</a>.'.format(Bugzilla.bug_url(bug_id), bug_id) |
| 2623 |
email_text += '\n\nFull details are available at: {}\n\nPatch author: {}'.format(build_url, patch_author) |
2664 |
email_text += '\n\nFull details are available at: {}\n\nPatch author: {}'.format(build_url, patch_author) |
|
|
2665 |
if exceed_failure_limit: |
| 2666 |
email_text += '\n\nAditionally the failure limit has been exceeded, so the test suite has been terminated early. It is likely that there would be more failures than the ones listed below.' |
| 2624 |
email_text += '\n\nLayout test failure{}:\n{}'.format(pluralSuffix, test_names_string) |
2667 |
email_text += '\n\nLayout test failure{}:\n{}'.format(pluralSuffix, test_names_string) |
| 2625 |
email_text += '\n\nTo unsubscrible from these notifications or to provide any feedback please email aakash_jain@apple.com' |
2668 |
email_text += '\n\nTo unsubscrible from these notifications or to provide any feedback please email aakash_jain@apple.com' |
| 2626 |
self._addToLog('stdio', 'Sending email notification to {}'.format(patch_author)) |
2669 |
self._addToLog('stdio', 'Sending email notification to {}'.format(patch_author)) |
|
Lines 2647-2653
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec15
|
| 2647 |
clean_tree_run_status = self.getProperty('clean_tree_run_status', FAILURE) |
2690 |
clean_tree_run_status = self.getProperty('clean_tree_run_status', FAILURE) |
| 2648 |
if clean_tree_run_status == SUCCESS: |
2691 |
if clean_tree_run_status == SUCCESS: |
| 2649 |
return self.report_failure(set()) |
2692 |
return self.report_failure(set()) |
| 2650 |
# TODO: email EWS admins |
2693 |
self.send_email_for_infrastructure_issue('Both first and second layout-test runs with patch generated no list of results but exited with error, and the clean_tree without patch retry also failed.') |
| 2651 |
return self.retry_build('Unexpected infrastructure issue, retrying build') |
2694 |
return self.retry_build('Unexpected infrastructure issue, retrying build') |
| 2652 |
|
2695 |
|
| 2653 |
if first_results_did_exceed_test_failure_limit and second_results_did_exceed_test_failure_limit: |
2696 |
if first_results_did_exceed_test_failure_limit and second_results_did_exceed_test_failure_limit: |
|
Lines 2674-2679
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec16
|
| 2674 |
return self.report_failure(failures_introduced_by_patch) |
2717 |
return self.report_failure(failures_introduced_by_patch) |
| 2675 |
return self.retry_build() |
2718 |
return self.retry_build() |
| 2676 |
|
2719 |
|
|
|
2720 |
# FIXME: Here it could be a good idea to also use the info of results.flaky_tests from the runs |
| 2677 |
if self._results_failed_different_tests(first_results_failing_tests, second_results_failing_tests): |
2721 |
if self._results_failed_different_tests(first_results_failing_tests, second_results_failing_tests): |
| 2678 |
tests_that_only_failed_first = first_results_failing_tests.difference(second_results_failing_tests) |
2722 |
tests_that_only_failed_first = first_results_failing_tests.difference(second_results_failing_tests) |
| 2679 |
self._report_flaky_tests(tests_that_only_failed_first) |
2723 |
self._report_flaky_tests(tests_that_only_failed_first) |
|
Lines 2712-2717
class RunWebKit1Tests(RunWebKitTests):
a/Tools/CISupport/ews-build/steps.py_sec17
|
| 2712 |
return RunWebKitTests.start(self) |
2756 |
return RunWebKitTests.start(self) |
| 2713 |
|
2757 |
|
| 2714 |
|
2758 |
|
|
|
2759 |
# This is a specialized class designed to cope with a tree that is not always green. |
| 2760 |
# It tries hard to avoid reporting any false positive, so it will only report new |
| 2761 |
# consistent failures (fail always with the patch and pass always without it). |
| 2762 |
class RunWebKitTestsRedTree(RunWebKitTests): |
| 2763 |
EXIT_AFTER_FAILURES = 500 |
| 2764 |
|
| 2765 |
def _did_command_timed_out(self, logHeadersText): |
| 2766 |
timed_out_line_start = 'command timed out: {} seconds elapsed running'.format(self.MAX_SECONDS_STEP_RUN) |
| 2767 |
timed_out_line_end = 'attempting to kill' |
| 2768 |
for line in logHeadersText.splitlines(): |
| 2769 |
line = line.strip() |
| 2770 |
if line.startswith(timed_out_line_start) and line.endswith(timed_out_line_end): |
| 2771 |
return True |
| 2772 |
return False |
| 2773 |
|
| 2774 |
def evaluateCommand(self, cmd): |
| 2775 |
first_results_failing_tests = set(self.getProperty('first_run_failures', [])) |
| 2776 |
first_results_flaky_tests = set(self.getProperty('first_run_flakies', [])) |
| 2777 |
rc = self.evaluateResult(cmd) |
| 2778 |
next_steps = [ArchiveTestResults(), UploadTestResults(), ExtractTestResults()] |
| 2779 |
if first_results_failing_tests: |
| 2780 |
next_steps.extend([ValidatePatch(verifyBugClosed=False, addURLs=False), KillOldProcesses(), RunWebKitTestsRepeatFailuresRedTree()]) |
| 2781 |
elif first_results_flaky_tests: |
| 2782 |
next_steps.append(AnalyzeLayoutTestsResultsRedTree()) |
| 2783 |
elif rc == SUCCESS or rc == WARNINGS: |
| 2784 |
next_steps = None |
| 2785 |
message = 'Passed layout tests' |
| 2786 |
self.descriptionDone = message |
| 2787 |
self.build.results = SUCCESS |
| 2788 |
self.setProperty('build_summary', message) |
| 2789 |
else: |
| 2790 |
# We have a failure return code, but not a list of failed or flaky tests. |
| 2791 |
# So retry re-running the _whole_ layout tests without-patch to see if |
| 2792 |
# this unexpected failure was pre-existent. If the failure was not pre-existent, |
| 2793 |
# then we would not report a list of failed test, just a generic "unknown" failure. |
| 2794 |
self.setProperty('patchFailedTests', True) |
| 2795 |
next_steps.extend([UnApplyPatchIfRequired(), CompileWebKitWithoutPatch(retry_build_on_failure=True), ValidatePatch(verifyBugClosed=False, addURLs=False), RunWebKitTestsWithoutPatchRedTree()]) |
| 2796 |
if next_steps: |
| 2797 |
self.build.addStepsAfterCurrentStep(next_steps) |
| 2798 |
return rc |
| 2799 |
|
| 2800 |
|
| 2801 |
class RunWebKitTestsRepeatFailuresRedTree(RunWebKitTestsRedTree): |
| 2802 |
name = 'layout-tests-repeat-failures' |
| 2803 |
NUM_REPEATS_PER_TEST = 10 |
| 2804 |
EXIT_AFTER_FAILURES = None |
| 2805 |
MAX_SECONDS_STEP_RUN = 18000 # 5h |
| 2806 |
|
| 2807 |
def __init__(self, **kwargs): |
| 2808 |
super(RunWebKitTestsRepeatFailuresRedTree, self).__init__(maxTime=self.MAX_SECONDS_STEP_RUN, **kwargs) |
| 2809 |
|
| 2810 |
def setLayoutTestCommand(self): |
| 2811 |
super(RunWebKitTestsRepeatFailuresRedTree, self).setLayoutTestCommand() |
| 2812 |
first_results_failing_tests = set(self.getProperty('first_run_failures', [])) |
| 2813 |
self.setCommand(self.command + ['--repeat-each=%s' % self.NUM_REPEATS_PER_TEST] + sorted(first_results_failing_tests)) |
| 2814 |
|
| 2815 |
def evaluateCommand(self, cmd): |
| 2816 |
with_patch_repeat_failures_results_nonflaky_failures = set(self.getProperty('with_patch_repeat_failures_results_nonflaky_failures', [])) |
| 2817 |
with_patch_repeat_failures_results_flakies = set(self.getProperty('with_patch_repeat_failures_results_flakies', [])) |
| 2818 |
with_patch_repeat_failures_timedout = self.getProperty('with_patch_repeat_failures_timedout', False) |
| 2819 |
first_results_flaky_tests = set(self.getProperty('first_run_flakies', [])) |
| 2820 |
rc = self.evaluateResult(cmd) |
| 2821 |
self.setProperty('with_patch_repeat_failures_retcode', rc) |
| 2822 |
next_steps = [ArchiveTestResults(), UploadTestResults(identifier='repeat-failures'), ExtractTestResults(identifier='repeat-failures')] |
| 2823 |
if with_patch_repeat_failures_results_nonflaky_failures or with_patch_repeat_failures_timedout: |
| 2824 |
self.setProperty('patchFailedTests', True) |
| 2825 |
next_steps.extend([ValidatePatch(verifyBugClosed=False, addURLs=False), KillOldProcesses(), UnApplyPatchIfRequired(), CompileWebKitWithoutPatch(retry_build_on_failure=True), |
| 2826 |
ValidatePatch(verifyBugClosed=False, addURLs=False), RunWebKitTestsRepeatFailuresWithoutPatchRedTree()]) |
| 2827 |
else: |
| 2828 |
next_steps.append(AnalyzeLayoutTestsResultsRedTree()) |
| 2829 |
if next_steps: |
| 2830 |
self.build.addStepsAfterCurrentStep(next_steps) |
| 2831 |
return rc |
| 2832 |
|
| 2833 |
def commandComplete(self, cmd): |
| 2834 |
shell.Test.commandComplete(self, cmd) |
| 2835 |
logText = self.log_observer.getStdout() + self.log_observer.getStderr() |
| 2836 |
logTextJson = self.log_observer_json.getStdout() |
| 2837 |
with_patch_repeat_failures_results = LayoutTestFailures.results_from_string(logTextJson) |
| 2838 |
if with_patch_repeat_failures_results: |
| 2839 |
self.setProperty('with_patch_repeat_failures_results_exceed_failure_limit', with_patch_repeat_failures_results.did_exceed_test_failure_limit) |
| 2840 |
self.setProperty('with_patch_repeat_failures_results_nonflaky_failures', sorted(with_patch_repeat_failures_results.failing_tests)) |
| 2841 |
self.setProperty('with_patch_repeat_failures_results_flakies', sorted(with_patch_repeat_failures_results.flaky_tests)) |
| 2842 |
if with_patch_repeat_failures_results.failing_tests: |
| 2843 |
self._addToLog(self.test_failures_log_name, '\n'.join(with_patch_repeat_failures_results.failing_tests)) |
| 2844 |
command_timedout = self._did_command_timed_out(self.log_observer.getHeaders()) |
| 2845 |
self.setProperty('with_patch_repeat_failures_timedout', command_timedout) |
| 2846 |
self._parseRunWebKitTestsOutput(logText) |
| 2847 |
|
| 2848 |
def start(self): |
| 2849 |
# buildbot messages about timeout reached appear on the header stream of BufferLog |
| 2850 |
return super(RunWebKitTestsRepeatFailuresRedTree, self).start(BufferLogObserverClass=BufferLogHeaderObserver) |
| 2851 |
|
| 2852 |
|
| 2853 |
class RunWebKitTestsRepeatFailuresWithoutPatchRedTree(RunWebKitTestsRedTree): |
| 2854 |
name = 'layout-tests-repeat-failures-without-patch' |
| 2855 |
NUM_REPEATS_PER_TEST = 10 |
| 2856 |
EXIT_AFTER_FAILURES = None |
| 2857 |
MAX_SECONDS_STEP_RUN = 10800 # 3h |
| 2858 |
|
| 2859 |
def __init__(self, **kwargs): |
| 2860 |
super(RunWebKitTestsRepeatFailuresWithoutPatchRedTree, self).__init__(maxTime=self.MAX_SECONDS_STEP_RUN, **kwargs) |
| 2861 |
|
| 2862 |
def setLayoutTestCommand(self): |
| 2863 |
super(RunWebKitTestsRepeatFailuresWithoutPatchRedTree, self).setLayoutTestCommand() |
| 2864 |
with_patch_nonflaky_failures = set(self.getProperty('with_patch_repeat_failures_results_nonflaky_failures', [])) |
| 2865 |
first_run_failures = set(self.getProperty('first_run_failures', [])) |
| 2866 |
with_patch_repeat_failures_timedout = self.getProperty('with_patch_repeat_failures_timedout', False) |
| 2867 |
failures_to_repeat = first_run_failures if with_patch_repeat_failures_timedout else with_patch_nonflaky_failures |
| 2868 |
self.setProperty('xdebug_timeout_previous', with_patch_repeat_failures_timedout) |
| 2869 |
# Pass '--skipped=always' to ensure that any test passed via command line arguments |
| 2870 |
# is skipped anyways if is marked as such on the Expectation files or if is marked |
| 2871 |
# as failure (since we are passing also '--skip-failing-tests'). That way we ensure |
| 2872 |
# to report the case of a patch removing an expectation that still fails with it. |
| 2873 |
self.setCommand(self.command + ['--repeat-each=%s' % self.NUM_REPEATS_PER_TEST, '--skipped=always'] + sorted(failures_to_repeat)) |
| 2874 |
|
| 2875 |
def evaluateCommand(self, cmd): |
| 2876 |
rc = shell.Test.evaluateCommand(self, cmd) |
| 2877 |
self.build.addStepsAfterCurrentStep([ArchiveTestResults(), UploadTestResults(identifier='repeat-failures-without-patch'), ExtractTestResults(identifier='repeat-failures-without-patch'), AnalyzeLayoutTestsResultsRedTree()]) |
| 2878 |
return rc |
| 2879 |
|
| 2880 |
def commandComplete(self, cmd): |
| 2881 |
shell.Test.commandComplete(self, cmd) |
| 2882 |
logText = self.log_observer.getStdout() + self.log_observer.getStderr() |
| 2883 |
logTextJson = self.log_observer_json.getStdout() |
| 2884 |
without_patch_repeat_failures_results = LayoutTestFailures.results_from_string(logTextJson) |
| 2885 |
if without_patch_repeat_failures_results: |
| 2886 |
self.setProperty('without_patch_repeat_failures_results_exceed_failure_limit', without_patch_repeat_failures_results.did_exceed_test_failure_limit) |
| 2887 |
self.setProperty('without_patch_repeat_failures_results_nonflaky_failures', sorted(without_patch_repeat_failures_results.failing_tests)) |
| 2888 |
self.setProperty('without_patch_repeat_failures_results_flakies', sorted(without_patch_repeat_failures_results.flaky_tests)) |
| 2889 |
if without_patch_repeat_failures_results.failing_tests: |
| 2890 |
self._addToLog(self.test_failures_log_name, '\n'.join(without_patch_repeat_failures_results.failing_tests)) |
| 2891 |
command_timedout = self._did_command_timed_out(self.log_observer.getHeaders()) |
| 2892 |
self.setProperty('without_patch_repeat_failures_timedout', command_timedout) |
| 2893 |
self._parseRunWebKitTestsOutput(logText) |
| 2894 |
|
| 2895 |
def start(self): |
| 2896 |
# buildbot messages about timeout reached appear on the header stream of BufferLog |
| 2897 |
return super(RunWebKitTestsRepeatFailuresWithoutPatchRedTree, self).start(BufferLogObserverClass=BufferLogHeaderObserver) |
| 2898 |
|
| 2899 |
|
| 2900 |
class RunWebKitTestsWithoutPatchRedTree(RunWebKitTestsWithoutPatch): |
| 2901 |
EXIT_AFTER_FAILURES = 500 |
| 2902 |
|
| 2903 |
def evaluateCommand(self, cmd): |
| 2904 |
rc = shell.Test.evaluateCommand(self, cmd) |
| 2905 |
self.build.addStepsAfterCurrentStep([ArchiveTestResults(), UploadTestResults(identifier='clean-tree'), ExtractTestResults(identifier='clean-tree'), AnalyzeLayoutTestsResultsRedTree()]) |
| 2906 |
self.setProperty('clean_tree_run_status', rc) |
| 2907 |
return rc |
| 2908 |
|
| 2909 |
|
| 2910 |
class AnalyzeLayoutTestsResultsRedTree(AnalyzeLayoutTestsResults): |
| 2911 |
MAX_RETRY = 3 |
| 2912 |
|
| 2913 |
def report_success(self): |
| 2914 |
self.finished(SUCCESS) |
| 2915 |
self.build.results = SUCCESS |
| 2916 |
self.descriptionDone = 'Passed layout tests' |
| 2917 |
message = '' |
| 2918 |
self.setProperty('build_summary', message) |
| 2919 |
return defer.succeed(None) |
| 2920 |
|
| 2921 |
def report_warning(self, message): |
| 2922 |
self.finished(WARNINGS) |
| 2923 |
self.build.results = WARNINGS |
| 2924 |
self.descriptionDone = message |
| 2925 |
self.setProperty('build_summary', message) |
| 2926 |
return defer.succeed(None) |
| 2927 |
|
| 2928 |
def report_infrastructure_issue_and_maybe_retry_build(self, message): |
| 2929 |
retry_count = int(self.getProperty('retry_count', 0)) |
| 2930 |
if retry_count >= self.MAX_RETRY: |
| 2931 |
message += '\nReached the maximum number of retries ({}). Unable to determine if patch is bad or there is a pre-existent infrastructure issue.'.format(self.MAX_RETRY) |
| 2932 |
self.send_email_for_infrastructure_issue(message) |
| 2933 |
return self.report_warning(message) |
| 2934 |
message += "\nRetrying build [retry count is {} of {}]".format(retry_count, self.MAX_RETRY) |
| 2935 |
self.setProperty('retry_count', retry_count + 1) |
| 2936 |
self.send_email_for_infrastructure_issue(message) |
| 2937 |
return self.retry_build(message='Unexpected infrastructure issue: {}'.format(message)) |
| 2938 |
|
| 2939 |
def send_email_for_pre_existent_failures(self, test_names): |
| 2940 |
try: |
| 2941 |
builder_name = self.getProperty('buildername', '') |
| 2942 |
worker_name = self.getProperty('workername', '') |
| 2943 |
build_url = '{}#/builders/{}/builds/{}'.format(self.master.config.buildbotURL, self.build._builderid, self.build.number) |
| 2944 |
number_failures = len(test_names) |
| 2945 |
pluralSuffix = 's' if number_failures > 1 else '' |
| 2946 |
|
| 2947 |
email_subject = 'Info about {} pre-existent failure{} at {}'.format(number_failures, pluralSuffix, builder_name) |
| 2948 |
email_test = 'Info about pre-existent (non-flaky) test failure{} at EWS:\n'.format(pluralSuffix) |
| 2949 |
email_text = ' - Build : {}\n'.format(build_url) |
| 2950 |
email_text = ' - Builder : {}\n'.format(builder_name) |
| 2951 |
email_text = ' - Worker : {}\n'.format(worker_name) |
| 2952 |
for test_name in sorted(test_names): |
| 2953 |
history_url = '{}?suite=layout-tests&test={}'.format(RESULTS_DB_URL, test_name) |
| 2954 |
email_text += '\n- {} (<a href="{}">test history</a>)'.format(test_name, history_url) |
| 2955 |
send_email_to_bot_watchers(email_subject, email_text, builder_name, 'preexisting-{}'.format(test_name)) |
| 2956 |
except Exception as e: |
| 2957 |
print('Error in sending email for flaky failure: {}'.format(e)) |
| 2958 |
|
| 2959 |
def start(self): |
| 2960 |
# Run with patch, running the whole layout test suite |
| 2961 |
first_results_exceed_failure_limit = self.getProperty('first_results_exceed_failure_limit', False) |
| 2962 |
first_run_failures = set(self.getProperty('first_run_failures', [])) |
| 2963 |
first_run_flakies = set(self.getProperty('first_run_flakies', [])) |
| 2964 |
|
| 2965 |
# Run with patch, running first_run_failures 10 times each test |
| 2966 |
with_patch_repeat_failures_results_exceed_failure_limit = self.getProperty('with_patch_repeat_failures_results_exceed_failure_limit', False) |
| 2967 |
with_patch_repeat_failures_results_nonflaky_failures = set(self.getProperty('with_patch_repeat_failures_results_nonflaky_failures', [])) |
| 2968 |
with_patch_repeat_failures_results_flakies = set(self.getProperty('with_patch_repeat_failures_results_flakies', [])) |
| 2969 |
with_patch_repeat_failures_timedout = self.getProperty('with_patch_repeat_failures_timedout', False) |
| 2970 |
|
| 2971 |
# Run without patch, running with_patch_repeat_failures_results_nonflaky_failures 10 times each test |
| 2972 |
without_patch_repeat_failures_results_exceed_failure_limit = self.getProperty('without_patch_repeat_failures_results_exceed_failure_limit', False) |
| 2973 |
without_patch_repeat_failures_results_nonflaky_failures = set(self.getProperty('without_patch_repeat_failures_results_nonflaky_failures', [])) |
| 2974 |
without_patch_repeat_failures_results_flakies = set(self.getProperty('without_patch_repeat_failures_results_flakies', [])) |
| 2975 |
without_patch_repeat_failures_timedout = self.getProperty('without_patch_repeat_failures_timedout', False) |
| 2976 |
|
| 2977 |
# Check for unexpected situations and warn EWS bot watchers or author potentially retrying the build. |
| 2978 |
if (not first_run_failures) and (not first_run_flakies): |
| 2979 |
# If we've made it here, the first_run failed, which means there should be either test failures or flakies |
| 2980 |
# Otherwise there is some unexpected issue. |
| 2981 |
# The clean tree run that only happens when the first run gives error code without generating a list of failures or flakies |
| 2982 |
clean_tree_run_failures = set(self.getProperty('clean_tree_run_failures', [])) |
| 2983 |
clean_tree_run_flakies = set(self.getProperty('clean_tree_run_flakies', [])) |
| 2984 |
clean_tree_run_status = self.getProperty('clean_tree_run_status', FAILURE) |
| 2985 |
|
| 2986 |
# If the clean-tree run generated some results then we assume this patch broke the script run-webkit-tests or something like that |
| 2987 |
if (clean_tree_run_status in [SUCCESS, WARNINGS]) or clean_tree_run_failures or clean_tree_run_flakies: |
| 2988 |
return self.report_failure(set(), first_results_exceed_failure_limit) |
| 2989 |
return self.report_infrastructure_issue_and_maybe_retry_build('The layout-test run with patch generated no list of results but exited with error, and the clean_tree without patch run did the samething.') |
| 2990 |
|
| 2991 |
if with_patch_repeat_failures_results_exceed_failure_limit or without_patch_repeat_failures_results_exceed_failure_limit: |
| 2992 |
return self.report_infrastructure_issue_and_maybe_retry_build('One of the steps for retrying the failed tests has exited early, but this steps should run without "--exit-after-n-failures" switch, so they should not exit early.') |
| 2993 |
|
| 2994 |
if without_patch_repeat_failures_timedout: |
| 2995 |
return self.report_infrastructure_issue_and_maybe_retry_build('The step "layout-tests-repeat-failures-without-patch" was interrumped because it reached the timeout.') |
| 2996 |
|
| 2997 |
if with_patch_repeat_failures_timedout: |
| 2998 |
# The patch is causing the step 'layout-tests-repeat-failures-with-patch" to timeout (but the step 'layout-tests-repeat-failures-without-patch' doesn't timeout) |
| 2999 |
# Likely the patch is adding so many failures that is causing the timeout to be reached. There is no point in repeating this run (it would happen the same on next runs and consume lot of time) |
| 3000 |
# Mark the patch as bad and report the tests that failured on the first run (without the ones that passed on the repeat). |
| 3001 |
# This may cause some flaky tests to be reported as false positives (since the step to discard flakies with patch couldn't finish), but is the best we can do here. |
| 3002 |
likely_new_non_flaky_failures = first_run_failures - without_patch_repeat_failures_results_nonflaky_failures.union(without_patch_repeat_failures_results_flakies) |
| 3003 |
self.send_email_for_infrastructure_issue('The step "layout-tests-repeat-failures-with-patch" reached the timeout but the step "layout-tests-repeat-failures-without-patch" ended. Not trying to repeat this. Reporting {} failures from the first run.'.format(len(likely_new_non_flaky_failures))) |
| 3004 |
return self.report_failure(likely_new_non_flaky_failures, first_results_exceed_failure_limit) |
| 3005 |
|
| 3006 |
# The step with_patch_repeat_failures generated no list of failures or flakies, which should only happen when the return code of the step is SUCESS or WARNINGS. |
| 3007 |
if not with_patch_repeat_failures_results_nonflaky_failures and not with_patch_repeat_failures_results_flakies: |
| 3008 |
with_patch_repeat_failures_retcode = self.getProperty('with_patch_repeat_failures_retcode', FAILURE) |
| 3009 |
if with_patch_repeat_failures_retcode not in [SUCCESS, WARNINGS]: |
| 3010 |
return self.report_infrastructure_issue_and_maybe_retry_build('The step "layout-tests-repeat-failures" failed to generate any list of failures or flakies and returned an error code.') |
| 3011 |
|
| 3012 |
# Warn EWS bot watchers about flakies |
| 3013 |
all_flaky_failures = first_run_flakies.union(with_patch_repeat_failures_results_flakies).union(without_patch_repeat_failures_results_flakies) |
| 3014 |
for flaky_failure in all_flaky_failures: |
| 3015 |
step_names = [] |
| 3016 |
if flaky_failure in without_patch_repeat_failures_results_flakies: |
| 3017 |
step_names.append('layout-tests-repeat-failures-without-patch') |
| 3018 |
if flaky_failure in with_patch_repeat_failures_results_flakies: |
| 3019 |
step_names.append('layout-tests-repeat-failures (with patch)') |
| 3020 |
if flaky_failure in first_run_flakies: |
| 3021 |
step_names.append('layout-tests (with patch)') |
| 3022 |
step_names_str = '"{}"'.format('", "'.join(step_names)) |
| 3023 |
self.send_email_for_flaky_failure(flaky_failure, step_names_str) |
| 3024 |
|
| 3025 |
# Warn EWS bot watchers about pre-existent non-flaky failures (if any), but |
| 3026 |
# send only one e-mail with all the tests to avoid sending too much e-mails. |
| 3027 |
pre_existent_non_flaky_failures = without_patch_repeat_failures_results_nonflaky_failures - with_patch_repeat_failures_results_nonflaky_failures.union(all_flaky_failures) |
| 3028 |
if pre_existent_non_flaky_failures: |
| 3029 |
self.send_email_for_pre_existent_failures(pre_existent_non_flaky_failures) |
| 3030 |
|
| 3031 |
# Finally check if there are new consitent (non-flaky) failures caused by the patch and warn the patch author settting the status for the build. |
| 3032 |
new_non_flaky_failures = with_patch_repeat_failures_results_nonflaky_failures - without_patch_repeat_failures_results_nonflaky_failures.union(without_patch_repeat_failures_results_flakies) |
| 3033 |
if new_non_flaky_failures: |
| 3034 |
return self.report_failure(new_non_flaky_failures, first_results_exceed_failure_limit) |
| 3035 |
|
| 3036 |
return self.report_success() |
| 3037 |
|
| 3038 |
|
| 2715 |
class ArchiveBuiltProduct(shell.ShellCommand): |
3039 |
class ArchiveBuiltProduct(shell.ShellCommand): |
| 2716 |
command = ['python3', 'Tools/CISupport/built-product-archive', |
3040 |
command = ['python3', 'Tools/CISupport/built-product-archive', |
| 2717 |
WithProperties('--platform=%(fullPlatform)s'), WithProperties('--%(configuration)s'), 'archive'] |
3041 |
WithProperties('--platform=%(fullPlatform)s'), WithProperties('--%(configuration)s'), 'archive'] |