| 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_enums 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 change_log = GIT_REPO.GetChangeLog(failed_revision) |
| 25 if change_log: |
| 26 culprits[failed_revision] = { |
| 27 'revision': failed_revision, |
| 28 'commit_position': change_log.commit_position, |
| 29 'review_url': change_log.code_review_url |
| 30 } |
| 31 return culprits |
| 32 |
| 33 def _FindCulpritForEachTestFailure(self, blame_list, result): |
| 34 # For test failures, the try job will run against every revision, |
| 35 # so we need to traverse the result dict in order to identify the |
| 36 # culprits to each failed step or test. |
| 37 culprit_map = {} |
| 38 failed_revisions = [] |
| 39 for revision in blame_list: |
| 40 for step, step_result in result['result'][revision].iteritems(): |
| 41 if step_result['valid'] and step_result['status'] == 'failed': |
| 42 if revision not in failed_revisions: |
| 43 failed_revisions.append(revision) |
| 44 |
| 45 if step not in culprit_map: |
| 46 culprit_map[step] = {} |
| 47 culprit_map[step]['suspected_cls'] = { |
| 48 revision: {} |
| 49 } |
| 50 culprit_map[step]['suspected_cls'][revision]['revision'] = revision |
| 51 culprit_map[step]['tests'] = {} |
| 52 |
| 53 # Gets first failed revision for each test. |
| 54 for failed_test in step_result['failures']: |
| 55 if failed_test not in culprit_map[step]['tests']: |
| 56 culprit_map[step]['tests'][failed_test] = {} |
| 57 culprit_map[step]['tests'][failed_test]['revision'] = ( |
| 58 revision) |
| 59 if revision not in culprit_map[step]['suspected_cls']: |
| 60 # Different tests within the same step fail in different |
| 61 # revisions, all revisions should be culprits for the step. |
| 62 culprit_map[step]['suspected_cls'][revision] = {} |
| 63 culprit_map[step]['suspected_cls'][revision]['revision'] = ( |
| 64 revision) |
| 65 return culprit_map, failed_revisions |
| 66 |
| 67 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): |
| 68 """Fills in commit_position and review_url for each failed rev in map.""" |
| 69 for step_culprit in culprit_map.values(): |
| 70 for revision, culprit_info in step_culprit['suspected_cls'].iteritems(): |
| 71 culprit_info.update(culprits[revision]) |
| 72 for test_culprit in step_culprit.get('tests', {}).values(): |
| 73 test_revision = test_culprit['revision'] |
| 74 test_culprit.update(culprits[test_revision]) |
| 75 |
| 15 # Arguments number differs from overridden method - pylint: disable=W0221 | 76 # Arguments number differs from overridden method - pylint: disable=W0221 |
| 16 def run( | 77 def run( |
| 17 self, master_name, builder_name, build_number, try_job_id, | 78 self, master_name, builder_name, build_number, blame_list, try_job_type, |
| 18 compile_result): | 79 try_job_id, result): |
| 19 culprit = None | 80 """Identifies the information for failed revisions. |
| 20 | 81 |
| 21 if compile_result and len(compile_result.get('result', [])) > 0: | 82 Please refer to try_job_result_format.md for format check. |
| 22 # For compile failures, the try job will stop if one revision fails, so | 83 """ |
| 23 # the culprit will be the last revision in the result. | 84 culprits = None |
| 24 result_for_last_checked_revision = compile_result['result'][-1] | |
| 25 failed_revision = ( | |
| 26 result_for_last_checked_revision[0] if | |
| 27 result_for_last_checked_revision[1].lower() == 'failed' else None) | |
| 28 | 85 |
| 29 if failed_revision: | 86 if result and result.get('result'): |
| 30 git_repo = GitRepository( | 87 if try_job_type == TryJobType.type_compile: |
| 31 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) | 88 # For compile failures, the try job will stop if one revision fails, so |
| 32 change_log = git_repo.GetChangeLog(failed_revision) | 89 # the culprit will be the last revision in the result. |
| 33 if change_log: | 90 result_for_last_checked_revision = result['result'][-1] |
| 34 culprit = { | 91 failed_revisions = ( |
| 35 'revision': failed_revision, | 92 [result_for_last_checked_revision[0]] if |
| 36 'commit_position': change_log.commit_position, | 93 result_for_last_checked_revision[1].lower() == 'failed' else []) |
| 37 'review_url': change_log.code_review_url | 94 |
| 38 } | 95 culprits = self._GetCulpritInfo(failed_revisions) |
| 39 compile_result['culprit'] = culprit | 96 if culprits: |
| 97 result['culprit'] = culprits[failed_revisions[0]] |
| 98 else: # try_job_type is 'test'. |
| 99 culprit_map, failed_revisions = self._FindCulpritForEachTestFailure( |
| 100 blame_list, result) |
| 101 culprits = self._GetCulpritInfo(failed_revisions) |
| 102 if culprits: |
| 103 self._UpdateCulpritMapWithCulpritInfo(culprit_map, culprits) |
| 104 result['culprit'] = culprit_map |
| 40 | 105 |
| 41 # Store try job results. | 106 # Store try job results. |
| 42 try_job_result = WfTryJob.Get(master_name, builder_name, build_number) | 107 try_job_result = WfTryJob.Get(master_name, builder_name, build_number) |
| 43 if culprit: | 108 if culprits: |
| 44 if (try_job_result.compile_results and | 109 result_to_update = ( |
| 45 try_job_result.compile_results[-1]['try_job_id'] == try_job_id): | 110 try_job_result.compile_results if |
| 46 try_job_result.compile_results[-1].update(compile_result) | 111 try_job_type == TryJobType.type_compile else |
| 112 try_job_result.test_results) |
| 113 if (result_to_update and |
| 114 result_to_update[-1]['try_job_id'] == try_job_id): |
| 115 result_to_update[-1].update(result) |
| 47 else: # pragma: no cover | 116 else: # pragma: no cover |
| 48 try_job_result.compile_results.append(compile_result) | 117 result_to_update.append(result) |
| 49 | 118 |
| 50 try_job_result.status = wf_analysis_status.ANALYZED | 119 try_job_result.status = wf_analysis_status.ANALYZED |
| 51 try_job_result.put() | 120 try_job_result.put() |
| 52 | 121 |
| 53 return culprit | 122 return result.get('culprit', None) if result else None |
| OLD | NEW |