|
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().__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 735-740
class BugzillaMixin(object):
a/Tools/CISupport/ews-build/steps.py_sec2
|
| 735 |
return False |
748 |
return False |
| 736 |
return True |
749 |
return True |
| 737 |
|
750 |
|
|
|
751 |
def send_email_for_infrastructure_issue(self, infrastructure_issue_text): |
| 752 |
try: |
| 753 |
builder_name = self.getProperty('buildername', '') |
| 754 |
worker_name = self.getProperty('workername', '') |
| 755 |
build_url = '{}#/builders/{}/builds/{}'.format(self.master.config.buildbotURL, self.build._builderid, self.build.number) |
| 756 |
email_subject = 'Infrastructure issue at {}'.format(builder_name) |
| 757 |
email_text = 'The following infrastructure issue happened at:\n\n' |
| 758 |
email_text += ' - Build : {}\n'.format(build_url) |
| 759 |
email_text += ' - Builder : {}\n'.format(builder_name) |
| 760 |
email_text += ' - Worker : {}\n'.format(worker_name) |
| 761 |
email_text += ' - Issue: {}\n'.format(infrastructure_issue_text) |
| 762 |
send_email_to_bot_watchers(email_subject, email_text, builder_name, 'infrastructure-{}'.format(builder_name)) |
| 763 |
except Exception as e: |
| 764 |
print('Error in sending email for infrastructure issue: {}'.format(e)) |
| 765 |
|
| 738 |
def get_bugzilla_api_key(self): |
766 |
def get_bugzilla_api_key(self): |
| 739 |
try: |
767 |
try: |
| 740 |
passwords = json.load(open('passwords.json')) |
768 |
passwords = json.load(open('passwords.json')) |
|
Lines 1171-1176
class Trigger(trigger.Trigger):
a/Tools/CISupport/ews-build/steps.py_sec3
|
| 1171 |
properties_to_pass['ews_revision'] = properties.Property('got_revision') |
1199 |
properties_to_pass['ews_revision'] = properties.Property('got_revision') |
| 1172 |
if self.triggers: |
1200 |
if self.triggers: |
| 1173 |
properties_to_pass['triggers'] = self.triggers |
1201 |
properties_to_pass['triggers'] = self.triggers |
|
|
1202 |
properties_to_pass['retry_count'] = properties.Property('retry_count', default=0) |
| 1174 |
return properties_to_pass |
1203 |
return properties_to_pass |
| 1175 |
|
1204 |
|
| 1176 |
|
1205 |
|
|
Lines 2193-2199
class RunWebKitTests(shell.Test):
a/Tools/CISupport/ews-build/steps.py_sec4
|
| 2193 |
if patch_author in ['webkit-wpt-import-bot@igalia.com']: |
2222 |
if patch_author in ['webkit-wpt-import-bot@igalia.com']: |
| 2194 |
self.setCommand(self.command + ['imported/w3c/web-platform-tests']) |
2223 |
self.setCommand(self.command + ['imported/w3c/web-platform-tests']) |
| 2195 |
else: |
2224 |
else: |
| 2196 |
self.setCommand(self.command + ['--exit-after-n-failures', self.EXIT_AFTER_FAILURES, '--skip-failing-tests']) |
2225 |
if self.EXIT_AFTER_FAILURES is not None: |
|
|
2226 |
self.setCommand(self.command + ['--exit-after-n-failures', '{}'.format(self.EXIT_AFTER_FAILURES)]) |
| 2227 |
self.setCommand(self.command + ['--skip-failing-tests']) |
| 2197 |
|
2228 |
|
| 2198 |
if additionalArguments: |
2229 |
if additionalArguments: |
| 2199 |
self.setCommand(self.command + additionalArguments) |
2230 |
self.setCommand(self.command + additionalArguments) |
|
Lines 2201-2208
class RunWebKitTests(shell.Test):
a/Tools/CISupport/ews-build/steps.py_sec5
|
| 2201 |
if self.ENABLE_GUARD_MALLOC: |
2232 |
if self.ENABLE_GUARD_MALLOC: |
| 2202 |
self.setCommand(self.command + ['--guard-malloc']) |
2233 |
self.setCommand(self.command + ['--guard-malloc']) |
| 2203 |
|
2234 |
|
| 2204 |
def start(self): |
2235 |
def start(self, BufferLogObserverClass=logobserver.BufferLogObserver): |
| 2205 |
self.log_observer = logobserver.BufferLogObserver(wantStderr=True) |
2236 |
self.log_observer = BufferLogObserverClass(wantStderr=True) |
| 2206 |
self.addLogObserver('stdio', self.log_observer) |
2237 |
self.addLogObserver('stdio', self.log_observer) |
| 2207 |
self.log_observer_json = logobserver.BufferLogObserver() |
2238 |
self.log_observer_json = logobserver.BufferLogObserver() |
| 2208 |
self.addLogObserver('json', self.log_observer_json) |
2239 |
self.addLogObserver('json', self.log_observer_json) |
|
Lines 2264-2269
class RunWebKitTests(shell.Test):
a/Tools/CISupport/ews-build/steps.py_sec6
|
| 2264 |
if first_results: |
2295 |
if first_results: |
| 2265 |
self.setProperty('first_results_exceed_failure_limit', first_results.did_exceed_test_failure_limit) |
2296 |
self.setProperty('first_results_exceed_failure_limit', first_results.did_exceed_test_failure_limit) |
| 2266 |
self.setProperty('first_run_failures', sorted(first_results.failing_tests)) |
2297 |
self.setProperty('first_run_failures', sorted(first_results.failing_tests)) |
|
|
2298 |
self.setProperty('first_run_flakies', sorted(first_results.flaky_tests)) |
| 2267 |
if first_results.failing_tests: |
2299 |
if first_results.failing_tests: |
| 2268 |
self._addToLog(self.test_failures_log_name, '\n'.join(first_results.failing_tests)) |
2300 |
self._addToLog(self.test_failures_log_name, '\n'.join(first_results.failing_tests)) |
| 2269 |
self._parseRunWebKitTestsOutput(logText) |
2301 |
self._parseRunWebKitTestsOutput(logText) |
|
Lines 2375-2380
class ReRunWebKitTests(RunWebKitTests):
a/Tools/CISupport/ews-build/steps.py_sec7
|
| 2375 |
first_results_failing_tests = set(self.getProperty('first_run_failures', [])) |
2407 |
first_results_failing_tests = set(self.getProperty('first_run_failures', [])) |
| 2376 |
second_results_did_exceed_test_failure_limit = self.getProperty('second_results_exceed_failure_limit') |
2408 |
second_results_did_exceed_test_failure_limit = self.getProperty('second_results_exceed_failure_limit') |
| 2377 |
second_results_failing_tests = set(self.getProperty('second_run_failures', [])) |
2409 |
second_results_failing_tests = set(self.getProperty('second_run_failures', [])) |
|
|
2410 |
# FIXME: here it can be a good idea to also use the info from second_run_flakies and first_run_flakies |
| 2378 |
tests_that_consistently_failed = first_results_failing_tests.intersection(second_results_failing_tests) |
2411 |
tests_that_consistently_failed = first_results_failing_tests.intersection(second_results_failing_tests) |
| 2379 |
flaky_failures = first_results_failing_tests.union(second_results_failing_tests) - first_results_failing_tests.intersection(second_results_failing_tests) |
2412 |
flaky_failures = first_results_failing_tests.union(second_results_failing_tests) - first_results_failing_tests.intersection(second_results_failing_tests) |
| 2380 |
flaky_failures = sorted(list(flaky_failures))[:self.NUM_FAILURES_TO_DISPLAY] |
2413 |
flaky_failures = sorted(list(flaky_failures))[:self.NUM_FAILURES_TO_DISPLAY] |
|
Lines 2413-2418
class ReRunWebKitTests(RunWebKitTests):
a/Tools/CISupport/ews-build/steps.py_sec8
|
| 2413 |
if second_results: |
2446 |
if second_results: |
| 2414 |
self.setProperty('second_results_exceed_failure_limit', second_results.did_exceed_test_failure_limit) |
2447 |
self.setProperty('second_results_exceed_failure_limit', second_results.did_exceed_test_failure_limit) |
| 2415 |
self.setProperty('second_run_failures', sorted(second_results.failing_tests)) |
2448 |
self.setProperty('second_run_failures', sorted(second_results.failing_tests)) |
|
|
2449 |
self.setProperty('second_run_flakies', sorted(second_results.flaky_tests)) |
| 2416 |
if second_results.failing_tests: |
2450 |
if second_results.failing_tests: |
| 2417 |
self._addToLog(self.test_failures_log_name, '\n'.join(second_results.failing_tests)) |
2451 |
self._addToLog(self.test_failures_log_name, '\n'.join(second_results.failing_tests)) |
| 2418 |
self._parseRunWebKitTestsOutput(logText) |
2452 |
self._parseRunWebKitTestsOutput(logText) |
|
Lines 2452-2457
class RunWebKitTestsWithoutPatch(RunWebKitTests):
a/Tools/CISupport/ews-build/steps.py_sec9
|
| 2452 |
if clean_tree_results: |
2486 |
if clean_tree_results: |
| 2453 |
self.setProperty('clean_tree_results_exceed_failure_limit', clean_tree_results.did_exceed_test_failure_limit) |
2487 |
self.setProperty('clean_tree_results_exceed_failure_limit', clean_tree_results.did_exceed_test_failure_limit) |
| 2454 |
self.setProperty('clean_tree_run_failures', clean_tree_results.failing_tests) |
2488 |
self.setProperty('clean_tree_run_failures', clean_tree_results.failing_tests) |
|
|
2489 |
self.setProperty('clean_tree_run_flakies', sorted(clean_tree_results.flaky_tests)) |
| 2455 |
if clean_tree_results.failing_tests: |
2490 |
if clean_tree_results.failing_tests: |
| 2456 |
self._addToLog(self.test_failures_log_name, '\n'.join(clean_tree_results.failing_tests)) |
2491 |
self._addToLog(self.test_failures_log_name, '\n'.join(clean_tree_results.failing_tests)) |
| 2457 |
self._parseRunWebKitTestsOutput(logText) |
2492 |
self._parseRunWebKitTestsOutput(logText) |
|
Lines 2482-2499
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec10
|
| 2482 |
descriptionDone = ['analyze-layout-tests-results'] |
2517 |
descriptionDone = ['analyze-layout-tests-results'] |
| 2483 |
NUM_FAILURES_TO_DISPLAY = 10 |
2518 |
NUM_FAILURES_TO_DISPLAY = 10 |
| 2484 |
|
2519 |
|
| 2485 |
def report_failure(self, new_failures): |
2520 |
def report_failure(self, new_failures, exceed_failure_limit=False): |
| 2486 |
self.finished(FAILURE) |
2521 |
self.finished(FAILURE) |
| 2487 |
self.build.results = FAILURE |
2522 |
self.build.results = FAILURE |
| 2488 |
if not new_failures: |
2523 |
if not new_failures: |
| 2489 |
message = 'Found unexpected failure with patch' |
2524 |
message = 'Found unexpected failure with patch' |
| 2490 |
else: |
2525 |
else: |
| 2491 |
pluralSuffix = 's' if len(new_failures) > 1 else '' |
2526 |
pluralSuffix = 's' if len(new_failures) > 1 else '' |
|
|
2527 |
if exceed_failure_limit: |
| 2528 |
message = 'Failure limit exceed. At least found' |
| 2529 |
else: |
| 2530 |
message = 'Found' |
| 2492 |
new_failures_string = ', '.join(sorted(new_failures)[:self.NUM_FAILURES_TO_DISPLAY]) |
2531 |
new_failures_string = ', '.join(sorted(new_failures)[:self.NUM_FAILURES_TO_DISPLAY]) |
| 2493 |
message = 'Found {} new test failure{}: {}'.format(len(new_failures), pluralSuffix, new_failures_string) |
2532 |
message += ' {} new test failure{}: {}'.format(len(new_failures), pluralSuffix, new_failures_string) |
| 2494 |
if len(new_failures) > self.NUM_FAILURES_TO_DISPLAY: |
2533 |
if len(new_failures) > self.NUM_FAILURES_TO_DISPLAY: |
| 2495 |
message += ' ...' |
2534 |
message += ' ...' |
| 2496 |
self.send_email_for_new_test_failures(new_failures) |
2535 |
self.send_email_for_new_test_failures(new_failures, exceed_failure_limit) |
| 2497 |
self.descriptionDone = message |
2536 |
self.descriptionDone = message |
| 2498 |
self.setProperty('build_finish_summary', message) |
2537 |
self.setProperty('build_finish_summary', message) |
| 2499 |
|
2538 |
|
|
Lines 2550-2556
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec11
|
| 2550 |
def _results_failed_different_tests(self, first_results_failing_tests, second_results_failing_tests): |
2589 |
def _results_failed_different_tests(self, first_results_failing_tests, second_results_failing_tests): |
| 2551 |
return first_results_failing_tests != second_results_failing_tests |
2590 |
return first_results_failing_tests != second_results_failing_tests |
| 2552 |
|
2591 |
|
| 2553 |
def send_email_for_flaky_failure(self, test_name): |
2592 |
def send_email_for_flaky_failure(self, test_name, step_str=None): |
| 2554 |
try: |
2593 |
try: |
| 2555 |
builder_name = self.getProperty('buildername', '') |
2594 |
builder_name = self.getProperty('buildername', '') |
| 2556 |
worker_name = self.getProperty('workername', '') |
2595 |
worker_name = self.getProperty('workername', '') |
|
Lines 2559-2564
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec12
|
| 2559 |
|
2598 |
|
| 2560 |
email_subject = 'Flaky test: {}'.format(test_name) |
2599 |
email_subject = 'Flaky test: {}'.format(test_name) |
| 2561 |
email_text = 'Flaky test: {}\n\nBuild: {}\n\nBuilder: {}\n\nWorker: {}\n\nHistory: {}'.format(test_name, build_url, builder_name, worker_name, history_url) |
2600 |
email_text = 'Flaky test: {}\n\nBuild: {}\n\nBuilder: {}\n\nWorker: {}\n\nHistory: {}'.format(test_name, build_url, builder_name, worker_name, history_url) |
|
|
2601 |
if step_str: |
| 2602 |
email_text += '\nThis test was flaky on the steps: {}'.format(step_str) |
| 2562 |
send_email_to_bot_watchers(email_subject, email_text, builder_name, 'flaky-{}'.format(test_name)) |
2603 |
send_email_to_bot_watchers(email_subject, email_text, builder_name, 'flaky-{}'.format(test_name)) |
| 2563 |
except Exception as e: |
2604 |
except Exception as e: |
| 2564 |
print('Error in sending email for flaky failure: {}'.format(e)) |
2605 |
print('Error in sending email for flaky failure: {}'.format(e)) |
|
Lines 2576-2582
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec13
|
| 2576 |
except Exception as e: |
2617 |
except Exception as e: |
| 2577 |
print('Error in sending email for pre-existing failure: {}'.format(e)) |
2618 |
print('Error in sending email for pre-existing failure: {}'.format(e)) |
| 2578 |
|
2619 |
|
| 2579 |
def send_email_for_new_test_failures(self, test_names): |
2620 |
def send_email_for_new_test_failures(self, test_names, exceed_failure_limit=False): |
| 2580 |
try: |
2621 |
try: |
| 2581 |
patch_id = self.getProperty('patch_id', '') |
2622 |
patch_id = self.getProperty('patch_id', '') |
| 2582 |
if not self.should_send_email(patch_id): |
2623 |
if not self.should_send_email(patch_id): |
|
Lines 2598-2603
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec14
|
| 2598 |
email_text += ' while testing <a href="{}">Patch {}</a>'.format(Bugzilla.patch_url(patch_id), patch_id) |
2639 |
email_text += ' while testing <a href="{}">Patch {}</a>'.format(Bugzilla.patch_url(patch_id), patch_id) |
| 2599 |
email_text += ' for <a href="{}">Bug {}</a>.'.format(Bugzilla.bug_url(bug_id), bug_id) |
2640 |
email_text += ' for <a href="{}">Bug {}</a>.'.format(Bugzilla.bug_url(bug_id), bug_id) |
| 2600 |
email_text += '\n\nFull details are available at: {}\n\nPatch author: {}'.format(build_url, patch_author) |
2641 |
email_text += '\n\nFull details are available at: {}\n\nPatch author: {}'.format(build_url, patch_author) |
|
|
2642 |
if exceed_failure_limit: |
| 2643 |
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.' |
| 2601 |
email_text += '\n\nLayout test failure{}:\n{}'.format(pluralSuffix, test_names_string) |
2644 |
email_text += '\n\nLayout test failure{}:\n{}'.format(pluralSuffix, test_names_string) |
| 2602 |
email_text += '\n\nTo unsubscribe from these notifications or to provide any feedback please email aakash_jain@apple.com' |
2645 |
email_text += '\n\nTo unsubscribe from these notifications or to provide any feedback please email aakash_jain@apple.com' |
| 2603 |
self._addToLog('stdio', 'Sending email notification to {}'.format(patch_author)) |
2646 |
self._addToLog('stdio', 'Sending email notification to {}'.format(patch_author)) |
|
Lines 2624-2630
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec15
|
| 2624 |
clean_tree_run_status = self.getProperty('clean_tree_run_status', FAILURE) |
2667 |
clean_tree_run_status = self.getProperty('clean_tree_run_status', FAILURE) |
| 2625 |
if clean_tree_run_status == SUCCESS: |
2668 |
if clean_tree_run_status == SUCCESS: |
| 2626 |
return self.report_failure(set()) |
2669 |
return self.report_failure(set()) |
| 2627 |
# TODO: email EWS admins |
2670 |
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.') |
| 2628 |
return self.retry_build('Unexpected infrastructure issue, retrying build') |
2671 |
return self.retry_build('Unexpected infrastructure issue, retrying build') |
| 2629 |
|
2672 |
|
| 2630 |
if first_results_did_exceed_test_failure_limit and second_results_did_exceed_test_failure_limit: |
2673 |
if first_results_did_exceed_test_failure_limit and second_results_did_exceed_test_failure_limit: |
|
Lines 2651-2656
class AnalyzeLayoutTestsResults(buildstep.BuildStep, BugzillaMixin):
a/Tools/CISupport/ews-build/steps.py_sec16
|
| 2651 |
return self.report_failure(failures_introduced_by_patch) |
2694 |
return self.report_failure(failures_introduced_by_patch) |
| 2652 |
return self.retry_build() |
2695 |
return self.retry_build() |
| 2653 |
|
2696 |
|
|
|
2697 |
# FIXME: Here it could be a good idea to also use the info of results.flaky_tests from the runs |
| 2654 |
if self._results_failed_different_tests(first_results_failing_tests, second_results_failing_tests): |
2698 |
if self._results_failed_different_tests(first_results_failing_tests, second_results_failing_tests): |
| 2655 |
tests_that_only_failed_first = first_results_failing_tests.difference(second_results_failing_tests) |
2699 |
tests_that_only_failed_first = first_results_failing_tests.difference(second_results_failing_tests) |
| 2656 |
self._report_flaky_tests(tests_that_only_failed_first) |
2700 |
self._report_flaky_tests(tests_that_only_failed_first) |
|
Lines 2689-2694
class RunWebKit1Tests(RunWebKitTests):
a/Tools/CISupport/ews-build/steps.py_sec17
|
| 2689 |
return RunWebKitTests.start(self) |
2733 |
return RunWebKitTests.start(self) |
| 2690 |
|
2734 |
|
| 2691 |
|
2735 |
|
|
|
2736 |
# This is a specialized class designed to cope with a tree that is not always green. |
| 2737 |
# It tries hard to avoid reporting any false positive, so it will only report new |
| 2738 |
# consistent failures (fail always with the patch and pass always without it). |
| 2739 |
class RunWebKitTestsRedTree(RunWebKitTests): |
| 2740 |
EXIT_AFTER_FAILURES = 500 |
| 2741 |
|
| 2742 |
def _did_command_timed_out(self, logHeadersText): |
| 2743 |
timed_out_line_start = 'command timed out: {} seconds elapsed running'.format(self.MAX_SECONDS_STEP_RUN) |
| 2744 |
timed_out_line_end = 'attempting to kill' |
| 2745 |
for line in logHeadersText.splitlines(): |
| 2746 |
line = line.strip() |
| 2747 |
if line.startswith(timed_out_line_start) and line.endswith(timed_out_line_end): |
| 2748 |
return True |
| 2749 |
return False |
| 2750 |
|
| 2751 |
def evaluateCommand(self, cmd): |
| 2752 |
first_results_failing_tests = set(self.getProperty('first_run_failures', [])) |
| 2753 |
first_results_flaky_tests = set(self.getProperty('first_run_flakies', [])) |
| 2754 |
rc = self.evaluateResult(cmd) |
| 2755 |
next_steps = [ArchiveTestResults(), UploadTestResults(), ExtractTestResults()] |
| 2756 |
if first_results_failing_tests: |
| 2757 |
next_steps.extend([ValidatePatch(verifyBugClosed=False, addURLs=False), KillOldProcesses(), RunWebKitTestsRepeatFailuresRedTree()]) |
| 2758 |
elif first_results_flaky_tests: |
| 2759 |
next_steps.append(AnalyzeLayoutTestsResultsRedTree()) |
| 2760 |
elif rc == SUCCESS or rc == WARNINGS: |
| 2761 |
next_steps = None |
| 2762 |
message = 'Passed layout tests' |
| 2763 |
self.descriptionDone = message |
| 2764 |
self.build.results = SUCCESS |
| 2765 |
self.setProperty('build_summary', message) |
| 2766 |
else: |
| 2767 |
# We have a failure return code, but not a list of failed or flaky tests. |
| 2768 |
# So retry re-running the _whole_ layout tests without-patch to see if |
| 2769 |
# this unexpected failure was pre-existent. If the failure was not pre-existent, |
| 2770 |
# then we would not report a list of failed test, just a generic "unknown" failure. |
| 2771 |
self.setProperty('patchFailedTests', True) |
| 2772 |
next_steps.extend([UnApplyPatchIfRequired(), CompileWebKitWithoutPatch(retry_build_on_failure=True), ValidatePatch(verifyBugClosed=False, addURLs=False), RunWebKitTestsWithoutPatchRedTree()]) |
| 2773 |
if next_steps: |
| 2774 |
self.build.addStepsAfterCurrentStep(next_steps) |
| 2775 |
return rc |
| 2776 |
|
| 2777 |
|
| 2778 |
class RunWebKitTestsRepeatFailuresRedTree(RunWebKitTestsRedTree): |
| 2779 |
name = 'layout-tests-repeat-failures' |
| 2780 |
NUM_REPEATS_PER_TEST = 10 |
| 2781 |
EXIT_AFTER_FAILURES = None |
| 2782 |
MAX_SECONDS_STEP_RUN = 18000 # 5h |
| 2783 |
|
| 2784 |
def __init__(self, **kwargs): |
| 2785 |
super().__init__(maxTime=self.MAX_SECONDS_STEP_RUN, **kwargs) |
| 2786 |
|
| 2787 |
def setLayoutTestCommand(self): |
| 2788 |
super().setLayoutTestCommand() |
| 2789 |
first_results_failing_tests = set(self.getProperty('first_run_failures', [])) |
| 2790 |
self.setCommand(self.command + ['--repeat-each=%s' % self.NUM_REPEATS_PER_TEST] + sorted(first_results_failing_tests)) |
| 2791 |
|
| 2792 |
def evaluateCommand(self, cmd): |
| 2793 |
with_patch_repeat_failures_results_nonflaky_failures = set(self.getProperty('with_patch_repeat_failures_results_nonflaky_failures', [])) |
| 2794 |
with_patch_repeat_failures_results_flakies = set(self.getProperty('with_patch_repeat_failures_results_flakies', [])) |
| 2795 |
with_patch_repeat_failures_timedout = self.getProperty('with_patch_repeat_failures_timedout', False) |
| 2796 |
first_results_flaky_tests = set(self.getProperty('first_run_flakies', [])) |
| 2797 |
rc = self.evaluateResult(cmd) |
| 2798 |
self.setProperty('with_patch_repeat_failures_retcode', rc) |
| 2799 |
next_steps = [ArchiveTestResults(), UploadTestResults(identifier='repeat-failures'), ExtractTestResults(identifier='repeat-failures')] |
| 2800 |
if with_patch_repeat_failures_results_nonflaky_failures or with_patch_repeat_failures_timedout: |
| 2801 |
self.setProperty('patchFailedTests', True) |
| 2802 |
next_steps.extend([ValidatePatch(verifyBugClosed=False, addURLs=False), KillOldProcesses(), UnApplyPatchIfRequired(), CompileWebKitWithoutPatch(retry_build_on_failure=True), |
| 2803 |
ValidatePatch(verifyBugClosed=False, addURLs=False), RunWebKitTestsRepeatFailuresWithoutPatchRedTree()]) |
| 2804 |
else: |
| 2805 |
next_steps.append(AnalyzeLayoutTestsResultsRedTree()) |
| 2806 |
if next_steps: |
| 2807 |
self.build.addStepsAfterCurrentStep(next_steps) |
| 2808 |
return rc |
| 2809 |
|
| 2810 |
def commandComplete(self, cmd): |
| 2811 |
shell.Test.commandComplete(self, cmd) |
| 2812 |
logText = self.log_observer.getStdout() + self.log_observer.getStderr() |
| 2813 |
logTextJson = self.log_observer_json.getStdout() |
| 2814 |
with_patch_repeat_failures_results = LayoutTestFailures.results_from_string(logTextJson) |
| 2815 |
if with_patch_repeat_failures_results: |
| 2816 |
self.setProperty('with_patch_repeat_failures_results_exceed_failure_limit', with_patch_repeat_failures_results.did_exceed_test_failure_limit) |
| 2817 |
self.setProperty('with_patch_repeat_failures_results_nonflaky_failures', sorted(with_patch_repeat_failures_results.failing_tests)) |
| 2818 |
self.setProperty('with_patch_repeat_failures_results_flakies', sorted(with_patch_repeat_failures_results.flaky_tests)) |
| 2819 |
if with_patch_repeat_failures_results.failing_tests: |
| 2820 |
self._addToLog(self.test_failures_log_name, '\n'.join(with_patch_repeat_failures_results.failing_tests)) |
| 2821 |
command_timedout = self._did_command_timed_out(self.log_observer.getHeaders()) |
| 2822 |
self.setProperty('with_patch_repeat_failures_timedout', command_timedout) |
| 2823 |
self._parseRunWebKitTestsOutput(logText) |
| 2824 |
|
| 2825 |
def start(self): |
| 2826 |
# buildbot messages about timeout reached appear on the header stream of BufferLog |
| 2827 |
return super().start(BufferLogObserverClass=BufferLogHeaderObserver) |
| 2828 |
|
| 2829 |
|
| 2830 |
class RunWebKitTestsRepeatFailuresWithoutPatchRedTree(RunWebKitTestsRedTree): |
| 2831 |
name = 'layout-tests-repeat-failures-without-patch' |
| 2832 |
NUM_REPEATS_PER_TEST = 10 |
| 2833 |
EXIT_AFTER_FAILURES = None |
| 2834 |
MAX_SECONDS_STEP_RUN = 10800 # 3h |
| 2835 |
|
| 2836 |
def __init__(self, **kwargs): |
| 2837 |
super().__init__(maxTime=self.MAX_SECONDS_STEP_RUN, **kwargs) |
| 2838 |
|
| 2839 |
def setLayoutTestCommand(self): |
| 2840 |
super().setLayoutTestCommand() |
| 2841 |
with_patch_nonflaky_failures = set(self.getProperty('with_patch_repeat_failures_results_nonflaky_failures', [])) |
| 2842 |
first_run_failures = set(self.getProperty('first_run_failures', [])) |
| 2843 |
with_patch_repeat_failures_timedout = self.getProperty('with_patch_repeat_failures_timedout', False) |
| 2844 |
failures_to_repeat = first_run_failures if with_patch_repeat_failures_timedout else with_patch_nonflaky_failures |
| 2845 |
# Pass '--skipped=always' to ensure that any test passed via command line arguments |
| 2846 |
# is skipped anyways if is marked as such on the Expectation files or if is marked |
| 2847 |
# as failure (since we are passing also '--skip-failing-tests'). That way we ensure |
| 2848 |
# to report the case of a patch removing an expectation that still fails with it. |
| 2849 |
self.setCommand(self.command + ['--repeat-each=%s' % self.NUM_REPEATS_PER_TEST, '--skipped=always'] + sorted(failures_to_repeat)) |
| 2850 |
|
| 2851 |
def evaluateCommand(self, cmd): |
| 2852 |
rc = self.evaluateResult(cmd) |
| 2853 |
self.setProperty('without_patch_repeat_failures_retcode', rc) |
| 2854 |
self.build.addStepsAfterCurrentStep([ArchiveTestResults(), UploadTestResults(identifier='repeat-failures-without-patch'), ExtractTestResults(identifier='repeat-failures-without-patch'), AnalyzeLayoutTestsResultsRedTree()]) |
| 2855 |
return rc |
| 2856 |
|
| 2857 |
def commandComplete(self, cmd): |
| 2858 |
shell.Test.commandComplete(self, cmd) |
| 2859 |
logText = self.log_observer.getStdout() + self.log_observer.getStderr() |
| 2860 |
logTextJson = self.log_observer_json.getStdout() |
| 2861 |
without_patch_repeat_failures_results = LayoutTestFailures.results_from_string(logTextJson) |
| 2862 |
if without_patch_repeat_failures_results: |
| 2863 |
self.setProperty('without_patch_repeat_failures_results_exceed_failure_limit', without_patch_repeat_failures_results.did_exceed_test_failure_limit) |
| 2864 |
self.setProperty('without_patch_repeat_failures_results_nonflaky_failures', sorted(without_patch_repeat_failures_results.failing_tests)) |
| 2865 |
self.setProperty('without_patch_repeat_failures_results_flakies', sorted(without_patch_repeat_failures_results.flaky_tests)) |
| 2866 |
if without_patch_repeat_failures_results.failing_tests: |
| 2867 |
self._addToLog(self.test_failures_log_name, '\n'.join(without_patch_repeat_failures_results.failing_tests)) |
| 2868 |
command_timedout = self._did_command_timed_out(self.log_observer.getHeaders()) |
| 2869 |
self.setProperty('without_patch_repeat_failures_timedout', command_timedout) |
| 2870 |
self._parseRunWebKitTestsOutput(logText) |
| 2871 |
|
| 2872 |
def start(self): |
| 2873 |
# buildbot messages about timeout reached appear on the header stream of BufferLog |
| 2874 |
return super().start(BufferLogObserverClass=BufferLogHeaderObserver) |
| 2875 |
|
| 2876 |
|
| 2877 |
class RunWebKitTestsWithoutPatchRedTree(RunWebKitTestsWithoutPatch): |
| 2878 |
EXIT_AFTER_FAILURES = 500 |
| 2879 |
|
| 2880 |
def evaluateCommand(self, cmd): |
| 2881 |
rc = shell.Test.evaluateCommand(self, cmd) |
| 2882 |
self.build.addStepsAfterCurrentStep([ArchiveTestResults(), UploadTestResults(identifier='clean-tree'), ExtractTestResults(identifier='clean-tree'), AnalyzeLayoutTestsResultsRedTree()]) |
| 2883 |
self.setProperty('clean_tree_run_status', rc) |
| 2884 |
return rc |
| 2885 |
|
| 2886 |
|
| 2887 |
class AnalyzeLayoutTestsResultsRedTree(AnalyzeLayoutTestsResults): |
| 2888 |
MAX_RETRY = 3 |
| 2889 |
|
| 2890 |
def report_success(self): |
| 2891 |
self.finished(SUCCESS) |
| 2892 |
self.build.results = SUCCESS |
| 2893 |
self.descriptionDone = 'Passed layout tests' |
| 2894 |
message = '' |
| 2895 |
self.setProperty('build_summary', message) |
| 2896 |
return defer.succeed(None) |
| 2897 |
|
| 2898 |
def report_warning(self, message): |
| 2899 |
self.finished(WARNINGS) |
| 2900 |
self.build.results = WARNINGS |
| 2901 |
self.descriptionDone = message |
| 2902 |
self.setProperty('build_summary', message) |
| 2903 |
return defer.succeed(None) |
| 2904 |
|
| 2905 |
def report_infrastructure_issue_and_maybe_retry_build(self, message): |
| 2906 |
retry_count = int(self.getProperty('retry_count', 0)) |
| 2907 |
if retry_count >= self.MAX_RETRY: |
| 2908 |
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) |
| 2909 |
self.send_email_for_infrastructure_issue(message) |
| 2910 |
return self.report_warning(message) |
| 2911 |
message += "\nRetrying build [retry count is {} of {}]".format(retry_count, self.MAX_RETRY) |
| 2912 |
self.setProperty('retry_count', retry_count + 1) |
| 2913 |
self.send_email_for_infrastructure_issue(message) |
| 2914 |
return self.retry_build(message='Unexpected infrastructure issue: {}'.format(message)) |
| 2915 |
|
| 2916 |
def send_email_for_pre_existent_failures(self, test_names): |
| 2917 |
try: |
| 2918 |
builder_name = self.getProperty('buildername', '') |
| 2919 |
worker_name = self.getProperty('workername', '') |
| 2920 |
build_url = '{}#/builders/{}/builds/{}'.format(self.master.config.buildbotURL, self.build._builderid, self.build.number) |
| 2921 |
number_failures = len(test_names) |
| 2922 |
pluralSuffix = 's' if number_failures > 1 else '' |
| 2923 |
|
| 2924 |
email_subject = 'Info about {} pre-existent failure{} at {}'.format(number_failures, pluralSuffix, builder_name) |
| 2925 |
email_test = 'Info about pre-existent (non-flaky) test failure{} at EWS:\n'.format(pluralSuffix) |
| 2926 |
email_text = ' - Build : {}\n'.format(build_url) |
| 2927 |
email_text = ' - Builder : {}\n'.format(builder_name) |
| 2928 |
email_text = ' - Worker : {}\n'.format(worker_name) |
| 2929 |
for test_name in sorted(test_names): |
| 2930 |
history_url = '{}?suite=layout-tests&test={}'.format(RESULTS_DB_URL, test_name) |
| 2931 |
email_text += '\n- {} (<a href="{}">test history</a>)'.format(test_name, history_url) |
| 2932 |
send_email_to_bot_watchers(email_subject, email_text, builder_name, 'preexisting-{}'.format(test_name)) |
| 2933 |
except Exception as e: |
| 2934 |
print('Error in sending email for flaky failure: {}'.format(e)) |
| 2935 |
|
| 2936 |
def start(self): |
| 2937 |
# Run with patch, running the whole layout test suite |
| 2938 |
first_results_exceed_failure_limit = self.getProperty('first_results_exceed_failure_limit', False) |
| 2939 |
first_run_failures = set(self.getProperty('first_run_failures', [])) |
| 2940 |
first_run_flakies = set(self.getProperty('first_run_flakies', [])) |
| 2941 |
|
| 2942 |
# Run with patch, running first_run_failures 10 times each test |
| 2943 |
with_patch_repeat_failures_results_exceed_failure_limit = self.getProperty('with_patch_repeat_failures_results_exceed_failure_limit', False) |
| 2944 |
with_patch_repeat_failures_results_nonflaky_failures = set(self.getProperty('with_patch_repeat_failures_results_nonflaky_failures', [])) |
| 2945 |
with_patch_repeat_failures_results_flakies = set(self.getProperty('with_patch_repeat_failures_results_flakies', [])) |
| 2946 |
with_patch_repeat_failures_timedout = self.getProperty('with_patch_repeat_failures_timedout', False) |
| 2947 |
|
| 2948 |
# Run without patch, running with_patch_repeat_failures_results_nonflaky_failures 10 times each test |
| 2949 |
without_patch_repeat_failures_results_exceed_failure_limit = self.getProperty('without_patch_repeat_failures_results_exceed_failure_limit', False) |
| 2950 |
without_patch_repeat_failures_results_nonflaky_failures = set(self.getProperty('without_patch_repeat_failures_results_nonflaky_failures', [])) |
| 2951 |
without_patch_repeat_failures_results_flakies = set(self.getProperty('without_patch_repeat_failures_results_flakies', [])) |
| 2952 |
without_patch_repeat_failures_timedout = self.getProperty('without_patch_repeat_failures_timedout', False) |
| 2953 |
|
| 2954 |
# Check for unexpected situations and warn EWS bot watchers or author potentially retrying the build. |
| 2955 |
if (not first_run_failures) and (not first_run_flakies): |
| 2956 |
# If we've made it here, the first_run failed, which means there should be either test failures or flakies |
| 2957 |
# Otherwise there is some unexpected issue. |
| 2958 |
# The clean tree run that only happens when the first run gives error code without generating a list of failures or flakies |
| 2959 |
clean_tree_run_failures = set(self.getProperty('clean_tree_run_failures', [])) |
| 2960 |
clean_tree_run_flakies = set(self.getProperty('clean_tree_run_flakies', [])) |
| 2961 |
clean_tree_run_status = self.getProperty('clean_tree_run_status', FAILURE) |
| 2962 |
|
| 2963 |
# If we are not on the last retry, then try to retry the whole testing with the hope it was a random infrastructure error |
| 2964 |
retry_count = int(self.getProperty('retry_count', 0)) |
| 2965 |
if retry_count < self.MAX_RETRY: |
| 2966 |
return self.report_infrastructure_issue_and_maybe_retry_build('The layout-test run with patch generated no list of results and exited with error, retrying with the hope it was a random infrastructure error.') |
| 2967 |
# Otherwise report and error or a warning (since we already gave it enough retries for the issue to not be caused by a random infrastructure error) |
| 2968 |
# If the clean-tree run generated some results then we assume this patch broke the script run-webkit-tests or something like that |
| 2969 |
if (clean_tree_run_status in [SUCCESS, WARNINGS]) or clean_tree_run_failures or clean_tree_run_flakies: |
| 2970 |
return self.report_failure(set(), first_results_exceed_failure_limit) |
| 2971 |
# This will end the testing as retry_count will be now self.MAX_RETRY and a warning will be reported. |
| 2972 |
return self.report_infrastructure_issue_and_maybe_retry_build('The layout-test run with patch generated no list of results and exited with error, and the clean_tree without patch run did the same thing.') |
| 2973 |
|
| 2974 |
if with_patch_repeat_failures_results_exceed_failure_limit or without_patch_repeat_failures_results_exceed_failure_limit: |
| 2975 |
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.') |
| 2976 |
|
| 2977 |
if without_patch_repeat_failures_timedout: |
| 2978 |
return self.report_infrastructure_issue_and_maybe_retry_build('The step "layout-tests-repeat-failures-without-patch" was interrumped because it reached the timeout.') |
| 2979 |
|
| 2980 |
if with_patch_repeat_failures_timedout: |
| 2981 |
# 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) |
| 2982 |
# 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) |
| 2983 |
# Mark the patch as bad and report the tests that failured on the first run (without the ones that passed on the repeat). |
| 2984 |
# 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. |
| 2985 |
likely_new_non_flaky_failures = first_run_failures - without_patch_repeat_failures_results_nonflaky_failures.union(without_patch_repeat_failures_results_flakies) |
| 2986 |
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))) |
| 2987 |
return self.report_failure(likely_new_non_flaky_failures, first_results_exceed_failure_limit) |
| 2988 |
|
| 2989 |
# 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. |
| 2990 |
if not with_patch_repeat_failures_results_nonflaky_failures and not with_patch_repeat_failures_results_flakies: |
| 2991 |
with_patch_repeat_failures_retcode = self.getProperty('with_patch_repeat_failures_retcode', FAILURE) |
| 2992 |
if with_patch_repeat_failures_retcode not in [SUCCESS, WARNINGS]: |
| 2993 |
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.') |
| 2994 |
|
| 2995 |
# Check the same for the step without_patch_repeat_failures |
| 2996 |
if not without_patch_repeat_failures_results_nonflaky_failures and not without_patch_repeat_failures_results_flakies: |
| 2997 |
without_patch_repeat_failures_retcode = self.getProperty('without_patch_repeat_failures_retcode', FAILURE) |
| 2998 |
if without_patch_repeat_failures_retcode not in [SUCCESS, WARNINGS]: |
| 2999 |
return self.report_infrastructure_issue_and_maybe_retry_build('The step "layout-tests-repeat-failures-without-patch" failed to generate any list of failures or flakies and returned an error code.') |
| 3000 |
|
| 3001 |
# Warn EWS bot watchers about flakies |
| 3002 |
all_flaky_failures = first_run_flakies.union(with_patch_repeat_failures_results_flakies).union(without_patch_repeat_failures_results_flakies) |
| 3003 |
for flaky_failure in all_flaky_failures: |
| 3004 |
step_names = [] |
| 3005 |
if flaky_failure in without_patch_repeat_failures_results_flakies: |
| 3006 |
step_names.append('layout-tests-repeat-failures-without-patch') |
| 3007 |
if flaky_failure in with_patch_repeat_failures_results_flakies: |
| 3008 |
step_names.append('layout-tests-repeat-failures (with patch)') |
| 3009 |
if flaky_failure in first_run_flakies: |
| 3010 |
step_names.append('layout-tests (with patch)') |
| 3011 |
step_names_str = '"{}"'.format('", "'.join(step_names)) |
| 3012 |
self.send_email_for_flaky_failure(flaky_failure, step_names_str) |
| 3013 |
|
| 3014 |
# Warn EWS bot watchers about pre-existent non-flaky failures (if any), but |
| 3015 |
# send only one e-mail with all the tests to avoid sending too much e-mails. |
| 3016 |
pre_existent_non_flaky_failures = without_patch_repeat_failures_results_nonflaky_failures - with_patch_repeat_failures_results_nonflaky_failures.union(all_flaky_failures) |
| 3017 |
if pre_existent_non_flaky_failures: |
| 3018 |
self.send_email_for_pre_existent_failures(pre_existent_non_flaky_failures) |
| 3019 |
|
| 3020 |
# 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. |
| 3021 |
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) |
| 3022 |
if new_non_flaky_failures: |
| 3023 |
return self.report_failure(new_non_flaky_failures, first_results_exceed_failure_limit) |
| 3024 |
|
| 3025 |
return self.report_success() |
| 3026 |
|
| 3027 |
|
| 2692 |
class ArchiveBuiltProduct(shell.ShellCommand): |
3028 |
class ArchiveBuiltProduct(shell.ShellCommand): |
| 2693 |
command = ['python3', 'Tools/CISupport/built-product-archive', |
3029 |
command = ['python3', 'Tools/CISupport/built-product-archive', |
| 2694 |
WithProperties('--platform=%(fullPlatform)s'), WithProperties('--%(configuration)s'), 'archive'] |
3030 |
WithProperties('--platform=%(fullPlatform)s'), WithProperties('--%(configuration)s'), 'archive'] |