Chromium Code Reviews| 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 from common.git_repository import GitRepository | 5 from common.git_repository import GitRepository |
| 6 from common.http_client_appengine import HttpClientAppengine as HttpClient | 6 from common.http_client_appengine import HttpClientAppengine as HttpClient |
| 7 from model import wf_analysis_status | 7 from model import wf_analysis_status |
| 8 from model.wf_try_job import WfTryJob | 8 from model.wf_try_job import WfTryJob |
| 9 from pipeline_wrapper import BasePipeline | 9 from pipeline_wrapper import BasePipeline |
| 10 from waterfall.try_job_type import TryJobType | |
| 11 | |
| 12 | |
| 13 GIT_REPO = GitRepository( | |
| 14 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) | |
| 10 | 15 |
| 11 | 16 |
| 12 class IdentifyTryJobCulpritPipeline(BasePipeline): | 17 class IdentifyTryJobCulpritPipeline(BasePipeline): |
| 13 """A pipeline to identify culprit CL info based on try job compile results.""" | 18 """A pipeline to identify culprit CL info based on try job compile results.""" |
| 14 | 19 |
| 20 def _GetCulpritInfo(self, failed_revisions): | |
| 21 """Gets commit_positions and review_urls for revisions.""" | |
| 22 culprits = {} | |
| 23 for failed_revision in failed_revisions: | |
| 24 culprits[failed_revision] = { | |
| 25 'revision': failed_revision | |
| 26 } | |
| 27 change_log = GIT_REPO.GetChangeLog(failed_revision) | |
| 28 if change_log: | |
| 29 culprits[failed_revision]['commit_position'] = ( | |
| 30 change_log.commit_position) | |
| 31 culprits[failed_revision]['review_url'] = change_log.code_review_url | |
| 32 | |
| 33 return culprits | |
| 34 | |
| 15 @staticmethod | 35 @staticmethod |
| 16 def _GetFailedRevisionFromResultsDict(results_dict): | 36 def _GetFailedRevisionFromResultsDict(results_dict): |
| 17 """Finds the failed revision from the given dict of revisions. | 37 """Finds the failed revision from the given dict of revisions. |
| 18 | 38 |
| 19 Args: | 39 Args: |
| 20 results_dict: (dict) A dict that maps revisions to their results. For | 40 results_dict: (dict) A dict that maps revisions to their results. For |
| 21 example: | 41 example: |
| 22 | 42 |
| 23 { | 43 { |
| 24 'rev1': 'passed', | 44 'rev1': 'passed', |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 94 result_for_last_checked_revision[0] if | 114 result_for_last_checked_revision[0] if |
| 95 result_for_last_checked_revision[1].lower() == 'failed' else None) | 115 result_for_last_checked_revision[1].lower() == 'failed' else None) |
| 96 else: | 116 else: |
| 97 revision_results = report.get('result', {}) | 117 revision_results = report.get('result', {}) |
| 98 failed_revision = ( | 118 failed_revision = ( |
| 99 IdentifyTryJobCulpritPipeline._GetFailedRevisionFromResultsDict( | 119 IdentifyTryJobCulpritPipeline._GetFailedRevisionFromResultsDict( |
| 100 revision_results)) | 120 revision_results)) |
| 101 | 121 |
| 102 return failed_revision | 122 return failed_revision |
| 103 | 123 |
| 104 @staticmethod | 124 def _FindCulpritForEachTestFailure(self, blame_list, result): |
| 105 def _GetCulpritFromFailedRevision(failed_revision): | 125 # For test failures, the try job will run against every revision, |
| 106 """Returns a culprit (dict) using failed_revision, or None.""" | 126 # so we need to traverse the result dict in chronological order to identify |
| 107 if not failed_revision: | 127 # the culprits for each failed step or test. |
| 108 return None | 128 culprit_map = {} |
| 129 failed_revisions = [] | |
| 130 for revision in blame_list: | |
| 131 for step, step_result in result['report'][revision].iteritems(): | |
| 132 if step_result['valid'] and step_result['status'] == 'failed': | |
|
qyearsley
2016/02/03 18:55:14
Optional change if you want to use one less level
chanli
2016/02/03 23:44:13
Done.
| |
| 133 if revision not in failed_revisions: | |
| 134 failed_revisions.append(revision) | |
| 109 | 135 |
| 110 git_repo = GitRepository( | 136 if step not in culprit_map: |
| 111 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) | 137 culprit_map[step] = { |
| 112 change_log = git_repo.GetChangeLog(failed_revision) | 138 'tests': {} |
| 139 } | |
| 113 | 140 |
| 114 if not change_log: | 141 if (not step_result['failures'] and |
| 115 return None | 142 not culprit_map[step].get('revision')): |
| 143 # Non swarming test failures, only have step level failure info. | |
| 144 culprit_map[step]['revision'] = revision | |
| 116 | 145 |
| 117 return { | 146 for failed_test in step_result['failures']: |
| 118 'revision': failed_revision, | 147 # Swarming tests, gets first failed revision for each test. |
| 119 'commit_position': change_log.commit_position, | 148 if failed_test not in culprit_map[step]['tests']: |
| 120 'review_url': change_log.code_review_url | 149 culprit_map[step]['tests'][failed_test] = { |
| 121 } | 150 'revision': revision |
| 151 } | |
| 152 return culprit_map, failed_revisions | |
| 153 | |
| 154 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): | |
| 155 """Fills in commit_position and review_url for each failed rev in map.""" | |
| 156 for step_culprit in culprit_map.values(): | |
| 157 if step_culprit.get('revision'): | |
| 158 culprit = culprits[step_culprit['revision']] | |
| 159 step_culprit['commit_position'] = culprit['commit_position'] | |
| 160 step_culprit['review_url'] = culprit['review_url'] | |
| 161 for test_culprit in step_culprit.get('tests', {}).values(): | |
| 162 test_revision = test_culprit['revision'] | |
| 163 test_culprit.update(culprits[test_revision]) | |
| 122 | 164 |
| 123 # Arguments number differs from overridden method - pylint: disable=W0221 | 165 # Arguments number differs from overridden method - pylint: disable=W0221 |
| 124 def run(self, master_name, builder_name, build_number, try_job_id, | 166 def run( |
| 125 compile_result): | 167 self, master_name, builder_name, build_number, blame_list, try_job_type, |
| 126 culprit = None | 168 try_job_id, result): |
| 127 failed_revision = self._GetFailedRevisionFromCompileResult(compile_result) | 169 """Identifies the information for failed revisions. |
| 128 culprit = self._GetCulpritFromFailedRevision(failed_revision) | 170 |
| 171 Please refer to try_job_result_format.md for format check. | |
| 172 """ | |
| 173 culprits = None | |
| 174 if result and result.get('report'): | |
| 175 if try_job_type == TryJobType.COMPILE: | |
| 176 # For compile failures, the try job will stop if one revision fails, so | |
| 177 # the culprit will be the last revision in the result. | |
| 178 failed_revision = self._GetFailedRevisionFromCompileResult( | |
| 179 result) | |
| 180 failed_revisions = [failed_revision] if failed_revision else [] | |
| 181 culprits = self._GetCulpritInfo(failed_revisions) | |
| 182 if culprits: | |
| 183 result['culprit'] = culprits[failed_revision] | |
| 184 else: # try_job_type is 'test'. | |
| 185 culprit_map, failed_revisions = self._FindCulpritForEachTestFailure( | |
| 186 blame_list, result) | |
| 187 culprits = self._GetCulpritInfo(failed_revisions) | |
| 188 if culprits: | |
| 189 self._UpdateCulpritMapWithCulpritInfo(culprit_map, culprits) | |
| 190 result['culprit'] = culprit_map | |
| 129 | 191 |
| 130 # Store try job results. | 192 # Store try job results. |
| 131 try_job_result = WfTryJob.Get(master_name, builder_name, build_number) | 193 try_job_result = WfTryJob.Get(master_name, builder_name, build_number) |
| 132 if culprit: | 194 if culprits: |
| 133 compile_result['culprit'] = culprit | 195 result_to_update = ( |
| 134 if (try_job_result.compile_results and | 196 try_job_result.compile_results if |
| 135 try_job_result.compile_results[-1]['try_job_id'] == try_job_id): | 197 try_job_type == TryJobType.COMPILE else |
| 136 try_job_result.compile_results[-1].update(compile_result) | 198 try_job_result.test_results) |
| 199 if (result_to_update and | |
| 200 result_to_update[-1]['try_job_id'] == try_job_id): | |
| 201 result_to_update[-1].update(result) | |
| 202 | |
| 137 else: # pragma: no cover | 203 else: # pragma: no cover |
| 138 try_job_result.compile_results.append(compile_result) | 204 result_to_update.append(result) |
| 139 | 205 |
| 140 try_job_result.status = wf_analysis_status.ANALYZED | 206 try_job_result.status = wf_analysis_status.ANALYZED |
| 141 try_job_result.put() | 207 try_job_result.put() |
| 142 | 208 |
| 143 return culprit | 209 return result.get('culprit', None) if result else None |
|
qyearsley
2016/02/03 18:55:14
Equivalent to `result.get('culprit') if result els
chanli
2016/02/03 23:44:13
Done.
| |
| OLD | NEW |