| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import logging | 5 import logging |
| 6 | 6 |
| 7 from google.appengine.api import modules | 7 from google.appengine.api import modules |
| 8 from google.appengine.ext import ndb | 8 from google.appengine.ext import ndb |
| 9 | 9 |
| 10 from model import wf_analysis_status | 10 from model import wf_analysis_status |
| 11 from model.wf_try_job import WfTryJob | 11 from model.wf_try_job import WfTryJob |
| 12 from waterfall.try_job_enums import TryJobType |
| 12 from waterfall import try_job_pipeline | 13 from waterfall import try_job_pipeline |
| 13 from waterfall import waterfall_config | 14 from waterfall import waterfall_config |
| 14 | 15 |
| 16 |
| 15 # TODO(chanli): Need to figure out why try-job-queue doesn't work. | 17 # TODO(chanli): Need to figure out why try-job-queue doesn't work. |
| 16 TRY_JOB_PIPELINE_QUEUE_NAME = 'build-failure-analysis-queue' | 18 TRY_JOB_PIPELINE_QUEUE_NAME = 'build-failure-analysis-queue' |
| 17 | 19 |
| 18 | 20 |
| 19 def _CheckFailureForTryJobKey(master_name, builder_name, build_number, | 21 def _CheckFailureForTryJobKey( |
| 20 failure_result_map, step_name, failure): | 22 master_name, builder_name, build_number, |
| 23 failure_result_map, failed_step_or_test, failure): |
| 21 """Compares the current_failure and first_failure for each failed_step/test. | 24 """Compares the current_failure and first_failure for each failed_step/test. |
| 22 | 25 |
| 23 If equal, a new try_job needs to start; | 26 If equal, a new try_job needs to start; |
| 24 If not, apply the key of the first_failure's try_job to this failure. | 27 If not, apply the key of the first_failure's try_job to this failure. |
| 25 """ | 28 """ |
| 26 # TODO(chanli): Need to compare failures across builders | 29 # TODO(chanli): Need to compare failures across builders |
| 27 # after the grouping of failures is implemented. | 30 # after the grouping of failures is implemented. |
| 28 new_try_job_key = '%s/%s/%s' % (master_name, builder_name, build_number) | 31 # TODO(chanli): Need to handle cases where first failure is actually |
| 32 # more than 20 builds back. The implementation should not be here, |
| 33 # but need to be taken care of. |
| 29 if not failure.get('last_pass'): | 34 if not failure.get('last_pass'): |
| 30 # Bail out since cannot figure out the good_revision. | 35 # Bail out since cannot figure out the good_revision. |
| 31 return False, None | 36 return False, None |
| 32 | 37 |
| 33 if failure['current_failure'] == failure['first_failure']: | 38 if failure['current_failure'] == failure['first_failure']: |
| 34 failure_result_map[step_name] = new_try_job_key | 39 failure_result_map[failed_step_or_test] = '%s/%s/%s' % ( |
| 35 logging.info('First-time failure') | 40 master_name, builder_name, build_number) |
| 36 return True, failure['last_pass'] # A new try_job is needed. | 41 return True, failure['last_pass'] # A new try_job is needed. |
| 37 else: | 42 else: |
| 38 # TODO(chanli): Need to handle cases where first failure is actually | 43 failure_result_map[failed_step_or_test] = '%s/%s/%s' % ( |
| 39 # more than 20 builds back. The implementation should not be here, | |
| 40 # but need to be taken care of. | |
| 41 try_job_key = '%s/%s/%s' % ( | |
| 42 master_name, builder_name, failure['first_failure']) | 44 master_name, builder_name, failure['first_failure']) |
| 43 failure_result_map[step_name] = try_job_key | 45 return False, None |
| 44 logging.info('Not first-time failure') | 46 |
| 45 return False, failure['last_pass'] | 47 |
| 48 def _CheckIfNeedNewTryJobForTestFailure( |
| 49 failure_level, master_name, builder_name, build_number, |
| 50 failure_result_map, failures): |
| 51 """Traverses failed steps or tests to check if a new try job is needed.""" |
| 52 need_new_try_job = False |
| 53 last_pass = build_number |
| 54 targeted_tests = {} if failure_level == 'step' else [] |
| 55 |
| 56 for failure_name, failure in failures.iteritems(): |
| 57 if failure_level == 'step': |
| 58 targeted_tests[failure_name] = [] |
| 59 else: |
| 60 targeted_tests.append(failure_name) |
| 61 |
| 62 if 'tests' in failure: |
| 63 failure_result_map[failure_name] = {} |
| 64 targeted_tests[failure_name], failure_need_try_job, failure_last_pass =( |
| 65 _CheckIfNeedNewTryJobForTestFailure( |
| 66 'test', master_name, builder_name, build_number, |
| 67 failure_result_map[failure_name], failure['tests'])) |
| 68 if not failure_need_try_job: |
| 69 targeted_tests.pop(failure_name) |
| 70 else: |
| 71 failure_need_try_job, failure_last_pass = _CheckFailureForTryJobKey( |
| 72 master_name, builder_name, build_number, |
| 73 failure_result_map, failure_name, failure) |
| 74 if not failure_need_try_job: |
| 75 if failure_level == 'step': |
| 76 targeted_tests.pop(failure_name) |
| 77 else: |
| 78 targeted_tests.remove(failure_name) |
| 79 |
| 80 need_new_try_job = need_new_try_job or failure_need_try_job |
| 81 last_pass = (failure_last_pass if failure_last_pass and |
| 82 failure_last_pass < last_pass else last_pass) |
| 83 |
| 84 return targeted_tests, need_new_try_job, last_pass |
| 46 | 85 |
| 47 | 86 |
| 48 @ndb.transactional | 87 @ndb.transactional |
| 49 def _NeedANewTryJob( | 88 def _NeedANewTryJob( |
| 50 master_name, builder_name, build_number, failed_steps): | 89 master_name, builder_name, build_number, failed_steps, failure_result_map): |
| 51 """Checks if a new try_job is needed.""" | 90 """Checks if a new try_job is needed.""" |
| 52 need_new_try_job = False | 91 need_new_try_job = False |
| 53 failure_result_map = {} | 92 last_pass = build_number |
| 54 last_pass = None | |
| 55 | 93 |
| 56 for step_name, step in failed_steps.iteritems(): | 94 if 'compile' in failed_steps: |
| 57 # TODO(chanli): support test failures when the recipe is ready. | 95 try_job_type = TryJobType.type_compile |
| 58 if step_name == 'compile': | 96 targeted_tests = None |
| 59 need_new_try_job, last_pass = _CheckFailureForTryJobKey( | 97 need_new_try_job, last_pass = _CheckFailureForTryJobKey( |
| 60 master_name, builder_name, build_number, | 98 master_name, builder_name, build_number, |
| 61 failure_result_map, step_name, step) | 99 failure_result_map, TryJobType.type_compile, failed_steps['compile']) |
| 100 else: |
| 101 try_job_type = TryJobType.type_test |
| 102 targeted_tests, need_new_try_job, last_pass =( |
| 103 _CheckIfNeedNewTryJobForTestFailure( |
| 104 'step', master_name, builder_name, build_number, failure_result_map, |
| 105 failed_steps)) |
| 62 | 106 |
| 63 if need_new_try_job: | 107 if need_new_try_job: |
| 64 try_job = WfTryJob.Get( | 108 try_job = WfTryJob.Get( |
| 65 master_name, builder_name, build_number) | 109 master_name, builder_name, build_number) |
| 66 | 110 |
| 67 if try_job: | 111 if try_job: |
| 68 if try_job.failed: | 112 if try_job.failed: |
| 69 try_job.status = wf_analysis_status.PENDING | 113 try_job.status = wf_analysis_status.PENDING |
| 70 try_job.put() | 114 try_job.put() |
| 71 else: | 115 else: |
| 72 need_new_try_job = False | 116 need_new_try_job = False |
| 73 break | 117 else: |
| 74 else: | 118 try_job = WfTryJob.Create( |
| 75 try_job = WfTryJob.Create( | 119 master_name, builder_name, build_number) |
| 76 master_name, builder_name, build_number) | 120 try_job.put() |
| 77 try_job.put() | |
| 78 break | |
| 79 | 121 |
| 80 return need_new_try_job, failure_result_map, last_pass | 122 return need_new_try_job, last_pass, try_job_type, targeted_tests |
| 81 | 123 |
| 82 | 124 |
| 83 def _GetFailedTargetsFromSignals(signals): | 125 def _GetFailedTargetsFromSignals(signals): |
| 84 compile_targets = [] | 126 compile_targets = [] |
| 85 | 127 |
| 86 if not signals or 'compile' not in signals: | 128 if not signals or 'compile' not in signals: |
| 87 return compile_targets | 129 return compile_targets |
| 88 | 130 |
| 89 for source_target in signals['compile']['failed_targets']: | 131 for source_target in signals['compile']['failed_targets']: |
| 90 # Link failures have only targets but no source. TODO(lijeffrey): | 132 # Link failures have only targets but no source. TODO(lijeffrey): |
| (...skipping 11 matching lines...) Expand all Loading... |
| 102 build_number = failure_info['build_number'] | 144 build_number = failure_info['build_number'] |
| 103 failed_steps = failure_info.get('failed_steps', []) | 145 failed_steps = failure_info.get('failed_steps', []) |
| 104 builds = failure_info.get('builds', {}) | 146 builds = failure_info.get('builds', {}) |
| 105 | 147 |
| 106 tryserver_mastername, tryserver_buildername = ( | 148 tryserver_mastername, tryserver_buildername = ( |
| 107 waterfall_config.GetTrybotForWaterfallBuilder(master_name, builder_name)) | 149 waterfall_config.GetTrybotForWaterfallBuilder(master_name, builder_name)) |
| 108 if not tryserver_mastername or not tryserver_buildername: | 150 if not tryserver_mastername or not tryserver_buildername: |
| 109 logging.info('%s, %s is not supported yet.', master_name, builder_name) | 151 logging.info('%s, %s is not supported yet.', master_name, builder_name) |
| 110 return {} | 152 return {} |
| 111 | 153 |
| 112 need_new_try_job, failure_result_map, last_pass = _NeedANewTryJob( | 154 failure_result_map = {} |
| 113 master_name, builder_name, build_number, failed_steps) | 155 need_new_try_job, last_pass, try_job_type, targeted_tests = ( |
| 156 _NeedANewTryJob(master_name, builder_name, build_number, |
| 157 failed_steps, failure_result_map)) |
| 114 | 158 |
| 115 if need_new_try_job: | 159 if need_new_try_job: |
| 116 compile_targets = _GetFailedTargetsFromSignals(signals) | 160 compile_targets = (_GetFailedTargetsFromSignals(signals) |
| 161 if try_job_type == TryJobType.type_compile else None) |
| 117 | 162 |
| 118 new_try_job_pipeline = try_job_pipeline.TryJobPipeline( | 163 new_try_job_pipeline = try_job_pipeline.TryJobPipeline( |
| 119 master_name, builder_name, build_number, | 164 master_name, builder_name, build_number, |
| 120 builds[str(last_pass)]['chromium_revision'], | 165 builds[str(last_pass)]['chromium_revision'], |
| 121 builds[str(build_number)]['chromium_revision'], | 166 builds[str(build_number)]['chromium_revision'], |
| 122 compile_targets) | 167 builds[str(build_number)]['blame_list'], |
| 168 try_job_type, compile_targets, targeted_tests) |
| 123 | 169 |
| 124 new_try_job_pipeline.target = ( | 170 new_try_job_pipeline.target = ( |
| 125 '%s.build-failure-analysis' % modules.get_current_version_name()) | 171 '%s.build-failure-analysis' % modules.get_current_version_name()) |
| 126 new_try_job_pipeline.start(queue_name=TRY_JOB_PIPELINE_QUEUE_NAME) | 172 new_try_job_pipeline.start(queue_name=TRY_JOB_PIPELINE_QUEUE_NAME) |
| 127 logging.info('Try-job was scheduled for build %s, %s, %s: %s', | 173 logging.info('Try-job was scheduled for build %s, %s, %s: %s', |
| 128 master_name, builder_name, build_number, | 174 master_name, builder_name, build_number, |
| 129 new_try_job_pipeline.pipeline_status_path) | 175 new_try_job_pipeline.pipeline_status_path) |
| 130 | 176 |
| 131 return failure_result_map | 177 return failure_result_map |
| OLD | NEW |