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 | 10 |
| 11 | 11 |
| 12 GIT_REPO = GitRepository( | |
| 13 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) | |
|
stgao
2016/01/26 00:51:40
It seems in quite a few places in the code base, w
chanli
2016/01/27 18:49:55
Acknowledged.
| |
| 14 | |
| 15 | |
| 12 class IdentifyTryJobCulpritPipeline(BasePipeline): | 16 class IdentifyTryJobCulpritPipeline(BasePipeline): |
| 13 """A pipeline to identify culprit CL info based on try job compile results.""" | 17 """A pipeline to identify culprit CL info based on try job compile results.""" |
| 14 | 18 |
| 19 def _GetCulpritInfo(self, failed_revisions): | |
| 20 """Gets commit_positions and review_urls for revisions.""" | |
| 21 culprits = {} | |
| 22 for failed_revision in failed_revisions: | |
| 23 change_log = GIT_REPO.GetChangeLog(failed_revision) | |
| 24 if change_log: | |
| 25 culprits[failed_revision] = { | |
| 26 'revision': failed_revision, | |
| 27 'commit_position': change_log.commit_position, | |
| 28 'review_url': change_log.code_review_url | |
| 29 } | |
| 30 return culprits | |
| 31 | |
| 32 def _FindCulpritForEachTestFailure(self, blame_list, result): | |
| 33 # For test failures, the try job will run against every revision, | |
|
stgao
2016/01/26 00:51:40
Just as a side note: This is the case for now. But
chanli
2016/01/27 18:49:55
Acknowledged. I'll keep it in mind and make the ch
| |
| 34 # so we need to traverse the result dict in order to identify the | |
| 35 # culprits to each failed step or test. | |
| 36 culprit_map = {} | |
| 37 failed_revisions = [] | |
| 38 for revision in blame_list: | |
| 39 for step, step_result in result['result'][revision].iteritems(): | |
|
stgao
2016/01/26 00:51:40
What if the revision is not tested yet due to infr
chanli
2016/01/27 18:49:55
If there were any infra failure or something else,
| |
| 40 if step_result['valid'] and step_result['status'] == 'failed': | |
| 41 if revision not in failed_revisions: | |
| 42 failed_revisions.append(revision) | |
| 43 | |
| 44 if step not in culprit_map: | |
| 45 culprit_map[step] = {} | |
| 46 culprit_map[step]['suspected_cls'] = { | |
| 47 revision: {} | |
| 48 } | |
| 49 culprit_map[step]['suspected_cls'][revision]['revision'] = revision | |
| 50 culprit_map[step]['tests'] = {} | |
| 51 | |
| 52 # Gets first failed revision for each test. | |
| 53 for failed_test in step_result['failures']: | |
| 54 if failed_test not in culprit_map[step]['tests']: | |
| 55 culprit_map[step]['tests'][failed_test] = {} | |
|
stgao
2016/01/26 00:51:40
Under the failed_test, is there a reason to have a
chanli
2016/01/27 18:49:55
Just like the example below, we will record the cu
| |
| 56 culprit_map[step]['tests'][failed_test]['revision'] = ( | |
| 57 revision) | |
| 58 if revision not in culprit_map[step]['suspected_cls']: | |
| 59 # Different tests within the same step fail in different | |
| 60 # revisions, all revisions should be culprits for the step. | |
| 61 culprit_map[step]['suspected_cls'][revision] = {} | |
| 62 culprit_map[step]['suspected_cls'][revision]['revision'] = ( | |
| 63 revision) | |
| 64 return culprit_map, failed_revisions | |
| 65 | |
| 66 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): | |
| 67 """Fills in commit_position and review_url for each failed rev in map.""" | |
| 68 for step_culprit in culprit_map.values(): | |
| 69 for revision, culprit_info in step_culprit['suspected_cls'].iteritems(): | |
| 70 culprit_info.update(culprits[revision]) | |
| 71 for test_culprit in step_culprit.get('tests', {}).values(): | |
| 72 test_revision = test_culprit['revision'] | |
| 73 test_culprit.update(culprits[test_revision]) | |
| 74 | |
| 15 # Arguments number differs from overridden method - pylint: disable=W0221 | 75 # Arguments number differs from overridden method - pylint: disable=W0221 |
| 16 def run( | 76 def run( |
| 17 self, master_name, builder_name, build_number, try_job_id, | 77 self, master_name, builder_name, build_number, blame_list, try_job_type, |
| 18 compile_result): | 78 try_job_id, result): |
| 19 culprit = None | 79 """Identifies the information for failed revisions. |
| 20 | 80 |
| 21 if compile_result and len(compile_result.get('result', [])) > 0: | 81 The format for final try-job result for compile failures is: |
| 22 # For compile failures, the try job will stop if one revision fails, so | 82 { |
| 23 # the culprit will be the last revision in the result. | 83 'result': [ |
| 24 result_for_last_checked_revision = compile_result['result'][-1] | 84 ['rev1', 'passed'], |
| 25 failed_revision = ( | 85 ['rev2', 'failed'] |
| 26 result_for_last_checked_revision[0] if | 86 ], |
| 27 result_for_last_checked_revision[1].lower() == 'failed' else None) | 87 'url': 'url', |
| 28 | 88 'try_job_id': '1', |
| 29 if failed_revision: | 89 'culprit': { |
| 30 git_repo = GitRepository( | 90 'revision': 'rev2', |
| 31 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) | 91 'commit_position': '2', |
| 32 change_log = git_repo.GetChangeLog(failed_revision) | 92 'review_url': 'url_2' |
| 33 if change_log: | 93 } |
| 34 culprit = { | 94 } |
| 35 'revision': failed_revision, | 95 |
| 36 'commit_position': change_log.commit_position, | 96 The format for final try-job result for test failures is: |
|
stgao
2016/01/26 00:51:40
What do you think of adding a file recipe_result_f
chanli
2016/01/27 18:49:55
Done.
| |
| 37 'review_url': change_log.code_review_url | 97 { |
| 38 } | 98 'result': { |
| 39 compile_result['culprit'] = culprit | 99 'rev1': { |
| 100 'a_test': { | |
| 101 'status': 'failed', | |
| 102 'valid': True, | |
| 103 'failures': ['a_test1'] | |
| 104 }, | |
| 105 'b_test': { | |
| 106 'status': 'failed', | |
| 107 'valid': True, | |
| 108 'failures': ['b_test1'] | |
| 109 }, | |
| 110 'c_test': { | |
| 111 'status': 'passed', | |
| 112 'valid': True | |
| 113 } | |
| 114 }, | |
| 115 'rev2': { | |
| 116 'a_test': { | |
| 117 'status': 'failed', | |
| 118 'valid': True, | |
| 119 'failures': ['a_test1', 'a_test2'] | |
| 120 }, | |
| 121 'b_test': { | |
| 122 'status': 'passed', | |
| 123 'valid': True | |
| 124 }, | |
| 125 'c_test': { | |
| 126 'status': 'failed', | |
| 127 'valid': True, | |
| 128 'failures': [] | |
| 129 } | |
| 130 } | |
| 131 }, | |
| 132 'url': 'url', | |
| 133 'try_job_id': '1', | |
| 134 'culprit': { | |
| 135 'a_test': { | |
| 136 'suspected_cls': { | |
| 137 'rev1': { | |
| 138 'revision': 'rev1', | |
| 139 'commit_position': '1', | |
| 140 'review_url': 'url_1' | |
| 141 }, | |
| 142 'rev2': { | |
| 143 'revision': 'rev2', | |
| 144 'commit_position': '2', | |
| 145 'review_url': 'url_2' | |
| 146 } | |
| 147 }, | |
| 148 'tests': { | |
| 149 'a_test1': { | |
| 150 'revision': 'rev1', | |
| 151 'commit_position': '1', | |
| 152 'review_url': 'url_1' | |
| 153 }, | |
| 154 'a_test2': { | |
| 155 'revision': 'rev2', | |
| 156 'commit_position': '2', | |
| 157 'review_url': 'url_2' | |
| 158 } | |
| 159 } | |
| 160 }, | |
| 161 'b_test': { | |
| 162 'suspected_cls': { | |
| 163 'rev1': { | |
| 164 'revision': 'rev1', | |
| 165 'commit_position': '1', | |
| 166 'review_url': 'url_1' | |
| 167 } | |
| 168 }, | |
| 169 'tests': { | |
| 170 'b_test1': { | |
| 171 'revision': 'rev1', | |
| 172 'commit_position': '1', | |
| 173 'review_url': 'url_1' | |
| 174 } | |
| 175 } | |
| 176 }, | |
| 177 'c_test': { | |
| 178 'suspected_cls': { | |
| 179 'rev2': { | |
| 180 'revision': 'rev2', | |
| 181 'commit_position': '2', | |
| 182 'review_url': 'url_2', | |
| 183 } | |
| 184 }, | |
| 185 'tests': {} | |
| 186 } | |
| 187 } | |
| 188 } | |
| 189 """ | |
| 190 culprits = None | |
| 191 | |
| 192 if result and result.get('result'): | |
| 193 if try_job_type == 'compile': | |
| 194 # For compile failures, the try job will stop if one revision fails, so | |
| 195 # the culprit will be the last revision in the result. | |
| 196 result_for_last_checked_revision = result['result'][-1] | |
| 197 failed_revisions = ( | |
| 198 [result_for_last_checked_revision[0]] if | |
| 199 result_for_last_checked_revision[1].lower() == 'failed' else []) | |
| 200 | |
| 201 culprits = self._GetCulpritInfo(failed_revisions) | |
| 202 if culprits: | |
| 203 result['culprit'] = culprits[failed_revisions[0]] | |
|
stgao
2016/01/26 00:51:40
Unrelated to this CL: In the else case, it means t
chanli
2016/01/27 18:49:55
Acknowledged. I'll file a bug for this as well.
| |
| 204 else: # try_job_type is 'test'. | |
| 205 culprit_map, failed_revisions = self._FindCulpritForEachTestFailure( | |
| 206 blame_list, result) | |
| 207 culprits = self._GetCulpritInfo(failed_revisions) | |
| 208 if culprits: | |
| 209 self._UpdateCulpritMapWithCulpritInfo(culprit_map, culprits) | |
| 210 result['culprit'] = culprit_map | |
| 40 | 211 |
| 41 # Store try job results. | 212 # Store try job results. |
| 42 try_job_result = WfTryJob.Get(master_name, builder_name, build_number) | 213 try_job_result = WfTryJob.Get(master_name, builder_name, build_number) |
| 43 if culprit: | 214 if culprits: |
| 44 if (try_job_result.compile_results and | 215 result_needs_update = ( |
|
stgao
2016/01/26 00:51:40
nit: naming.
chanli
2016/01/27 18:49:55
Done.
| |
| 45 try_job_result.compile_results[-1]['try_job_id'] == try_job_id): | 216 try_job_result.compile_results if |
| 46 try_job_result.compile_results[-1].update(compile_result) | 217 try_job_type == 'compile' else try_job_result.test_results) |
| 218 if (result_needs_update and | |
| 219 result_needs_update[-1]['try_job_id'] == try_job_id): | |
| 220 result_needs_update[-1].update(result) | |
| 47 else: # pragma: no cover | 221 else: # pragma: no cover |
| 48 try_job_result.compile_results.append(compile_result) | 222 result_needs_update.append(result) |
| 49 | 223 |
| 50 try_job_result.status = wf_analysis_status.ANALYZED | 224 try_job_result.status = wf_analysis_status.ANALYZED |
| 51 try_job_result.put() | 225 try_job_result.put() |
| 52 | 226 |
| 53 return culprit | 227 return result.get('culprit', None) if result else None |
| OLD | NEW |