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 collections import defaultdict | 5 from collections import defaultdict |
| 6 import logging | 6 import logging |
| 7 | 7 |
| 8 from google.appengine.ext import ndb | 8 from google.appengine.ext import ndb |
| 9 | 9 |
| 10 from common.git_repository import GitRepository | 10 from common.git_repository import GitRepository |
| 11 from common.http_client_appengine import HttpClientAppengine as HttpClient | 11 from common.http_client_appengine import HttpClientAppengine as HttpClient |
| 12 from common.pipeline_wrapper import BasePipeline | 12 from common.pipeline_wrapper import BasePipeline |
| 13 from common.waterfall import failure_type | 13 from common.waterfall import failure_type |
| 14 from model import analysis_approach_type | |
| 14 from model import analysis_status | 15 from model import analysis_status |
| 15 from model import result_status | 16 from model import result_status |
| 16 from model.wf_analysis import WfAnalysis | 17 from model.wf_analysis import WfAnalysis |
| 17 from model.wf_try_job import WfTryJob | 18 from model.wf_try_job import WfTryJob |
| 18 from model.wf_try_job_data import WfTryJobData | 19 from model.wf_try_job_data import WfTryJobData |
| 19 from waterfall.send_notification_for_culprit_pipeline import ( | 20 from waterfall.send_notification_for_culprit_pipeline import ( |
| 20 SendNotificationForCulpritPipeline) | 21 SendNotificationForCulpritPipeline) |
| 22 from waterfall import suspected_cl_util | |
| 21 | 23 |
| 22 | 24 |
| 23 GIT_REPO = GitRepository( | 25 GIT_REPO = GitRepository( |
| 24 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) | 26 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) |
| 25 | 27 |
| 26 | 28 |
| 27 def _GetResultAnalysisStatus(analysis, result): | 29 def _GetResultAnalysisStatus(analysis, result): |
| 28 """Returns the analysis status based on existing status and try job result. | 30 """Returns the analysis status based on existing status and try job result. |
| 29 | 31 |
| 30 Args: | 32 Args: |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 44 if (try_job_found_culprit and | 46 if (try_job_found_culprit and |
| 45 (old_result_status is None or | 47 (old_result_status is None or |
| 46 old_result_status == result_status.NOT_FOUND_UNTRIAGED or | 48 old_result_status == result_status.NOT_FOUND_UNTRIAGED or |
| 47 old_result_status == result_status.NOT_FOUND_INCORRECT or | 49 old_result_status == result_status.NOT_FOUND_INCORRECT or |
| 48 old_result_status == result_status.NOT_FOUND_CORRECT)): | 50 old_result_status == result_status.NOT_FOUND_CORRECT)): |
| 49 return result_status.FOUND_UNTRIAGED | 51 return result_status.FOUND_UNTRIAGED |
| 50 | 52 |
| 51 return old_result_status | 53 return old_result_status |
| 52 | 54 |
| 53 | 55 |
| 54 def _GetSuspectedCLs(analysis, result): | 56 def _GetTestTryJobSuspectedCLs(culprit): |
| 57 # Suspected CLs are from test failures. | |
| 58 suspected_cl_revisions = [] | |
| 59 suspected_cls = [] | |
| 60 for results in culprit.itervalues(): | |
| 61 for test_cl_info in results['tests'].itervalues(): | |
| 62 revision = test_cl_info['revision'] | |
| 63 if revision not in suspected_cl_revisions: | |
| 64 suspected_cl_revisions.append(revision) | |
| 65 suspected_cls.append(test_cl_info) | |
| 66 | |
| 67 return suspected_cls | |
| 68 | |
| 69 | |
| 70 def _GetTryJobSuspectedCLs(result): | |
| 71 """Returns all suspected CLs found by the try job. | |
| 72 | |
| 73 Args: | |
| 74 result: A result dict containing the culprit from the results of | |
| 75 this try job. | |
| 76 | |
| 77 Returns: | |
| 78 A list of suspected CLs found by this try job. | |
| 79 """ | |
| 80 if not result or not result.get('culprit'): | |
| 81 return [] | |
| 82 | |
| 83 culprit = result.get('culprit') | |
| 84 compile_cl_info = culprit.get('compile') | |
| 85 | |
| 86 if compile_cl_info: | |
| 87 # Suspected CL is from compile failure. | |
| 88 return [compile_cl_info] | |
| 89 | |
| 90 return _GetTestTryJobSuspectedCLs(culprit) | |
| 91 | |
| 92 | |
| 93 def _GetSuspectedCLs(analysis, try_job_suspected_cls): | |
| 55 """Returns a list of suspected CLs. | 94 """Returns a list of suspected CLs. |
| 56 | 95 |
| 57 Args: | 96 Args: |
| 58 analysis: The WfAnalysis entity corresponding to this try job. | 97 analysis: The WfAnalysis entity corresponding to this try job. |
| 59 result: A result dict containing the culprit from the results of | 98 is_compile_try_job: True if the try job is for compile failure, |
|
lijeffrey
2016/08/17 19:31:11
where is this arg?
chanli
2016/09/12 20:56:15
Done.
| |
| 60 this try job. | 99 otherwise False. |
| 100 try_job_suspected_cls: A list of suspected CLs found by the try job. | |
| 61 | 101 |
| 62 Returns: | 102 Returns: |
| 63 A combined list of suspected CLs from those already in analysis and those | 103 A combined list of suspected CLs from those already in analysis and those |
| 64 found by this try job. | 104 found by this try job. |
| 65 """ | 105 """ |
| 66 suspected_cls = analysis.suspected_cls[:] if analysis.suspected_cls else [] | 106 suspected_cls = analysis.suspected_cls[:] if analysis.suspected_cls else [] |
| 67 suspected_cl_revisions = [cl['revision'] for cl in suspected_cls] | 107 suspected_cl_revisions = [cl['revision'] for cl in suspected_cls] |
| 68 culprit = result.get('culprit') | |
| 69 compile_cl_info = culprit.get('compile') | |
| 70 | 108 |
| 71 if compile_cl_info: | 109 for try_job_suspected_cl in try_job_suspected_cls: |
| 72 # Suspected CL is from compile failure. | 110 if try_job_suspected_cl['revision'] not in suspected_cl_revisions: |
| 73 revision = compile_cl_info.get('revision') | 111 suspected_cl_revisions.append(try_job_suspected_cl['revision']) |
| 74 if revision not in suspected_cl_revisions: | 112 suspected_cls.append(try_job_suspected_cl) |
| 75 suspected_cl_revisions.append(revision) | |
| 76 suspected_cls.append(compile_cl_info) | |
| 77 return suspected_cls | |
| 78 | |
| 79 # Suspected CLs are from test failures. | |
| 80 for results in culprit.itervalues(): | |
| 81 if results.get('revision'): | |
| 82 # Non swarming test failures, only have step level failure info. | |
| 83 revision = results.get('revision') | |
| 84 cl_info = { | |
| 85 'url': results.get('url'), | |
| 86 'repo_name': results.get('repo_name'), | |
| 87 'revision': results.get('revision'), | |
| 88 'commit_position': results.get('commit_position') | |
| 89 } | |
| 90 if revision not in suspected_cl_revisions: | |
| 91 suspected_cl_revisions.append(revision) | |
| 92 suspected_cls.append(cl_info) | |
| 93 else: | |
| 94 for test_cl_info in results['tests'].values(): | |
| 95 revision = test_cl_info.get('revision') | |
| 96 if revision not in suspected_cl_revisions: | |
| 97 suspected_cl_revisions.append(revision) | |
| 98 suspected_cls.append(test_cl_info) | |
| 99 | 113 |
| 100 return suspected_cls | 114 return suspected_cls |
| 101 | 115 |
| 102 | 116 |
| 103 def _GetFailedRevisionFromResultsDict(results_dict): | 117 def _GetFailedRevisionFromResultsDict(results_dict): |
| 104 """Finds the failed revision from the given dict of revisions. | 118 """Finds the failed revision from the given dict of revisions. |
| 105 | 119 |
| 106 Args: | 120 Args: |
| 107 results_dict: (dict) A dict that maps revisions to their results. For | 121 results_dict: (dict) A dict that maps revisions to their results. For |
| 108 example: | 122 example: |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 161 if (not test_result['valid'] or | 175 if (not test_result['valid'] or |
| 162 test_result['status'] != 'failed'): # pragma: no cover | 176 test_result['status'] != 'failed'): # pragma: no cover |
| 163 continue | 177 continue |
| 164 | 178 |
| 165 failed_revisions.add(revision) | 179 failed_revisions.add(revision) |
| 166 | 180 |
| 167 if step not in culprit_map: | 181 if step not in culprit_map: |
| 168 culprit_map[step] = { | 182 culprit_map[step] = { |
| 169 'tests': {} | 183 'tests': {} |
| 170 } | 184 } |
| 171 if (not test_result['failures'] and | |
| 172 not culprit_map[step].get('revision')): | |
| 173 # Non swarming test failures, only have step level failure info. | |
| 174 culprit_map[step]['revision'] = revision | |
| 175 for failed_test in test_result['failures']: | 185 for failed_test in test_result['failures']: |
| 176 # Swarming tests, gets first failed revision for each test. | 186 # Swarming tests, gets first failed revision for each test. |
| 177 if failed_test not in culprit_map[step]['tests']: | 187 if failed_test not in culprit_map[step]['tests']: |
| 178 culprit_map[step]['tests'][failed_test] = { | 188 culprit_map[step]['tests'][failed_test] = { |
| 179 'revision': revision | 189 'revision': revision |
| 180 } | 190 } |
| 181 | 191 |
| 182 return culprit_map, list(failed_revisions) | 192 return culprit_map, list(failed_revisions) |
| 183 | 193 |
| 184 | 194 |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 241 } | 251 } |
| 242 failed_revisions.add(revision) | 252 failed_revisions.add(revision) |
| 243 return culprit_map, list(failed_revisions) | 253 return culprit_map, list(failed_revisions) |
| 244 | 254 |
| 245 return _GetCulpritsForTestsFromResultsDict( | 255 return _GetCulpritsForTestsFromResultsDict( |
| 246 blame_list, result['report'].get('result')) | 256 blame_list, result['report'].get('result')) |
| 247 | 257 |
| 248 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): | 258 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): |
| 249 """Fills in commit_position and review url for each failed rev in map.""" | 259 """Fills in commit_position and review url for each failed rev in map.""" |
| 250 for step_culprit in culprit_map.values(): | 260 for step_culprit in culprit_map.values(): |
| 251 if step_culprit.get('revision'): | |
| 252 culprit = culprits[step_culprit['revision']] | |
| 253 step_culprit['commit_position'] = culprit['commit_position'] | |
| 254 step_culprit['url'] = culprit['url'] | |
| 255 step_culprit['repo_name'] = culprit['repo_name'] | |
| 256 for test_culprit in step_culprit.get('tests', {}).values(): | 261 for test_culprit in step_culprit.get('tests', {}).values(): |
| 257 test_revision = test_culprit['revision'] | 262 test_revision = test_culprit['revision'] |
| 258 test_culprit.update(culprits[test_revision]) | 263 test_culprit.update(culprits[test_revision]) |
| 259 | 264 |
| 260 def _GetCulpritDataForTest(self, culprit_map): | 265 def _GetCulpritDataForTest(self, culprit_map): |
| 261 """Gets culprit revision for each failure for try job metadata.""" | 266 """Gets culprit revision for each failure for try job metadata.""" |
| 262 culprit_data = {} | 267 culprit_data = {} |
| 263 for step, step_culprit in culprit_map.iteritems(): | 268 for step, step_culprit in culprit_map.iteritems(): |
| 264 if step_culprit['tests']: | 269 culprit_data[step] = {} |
| 265 culprit_data[step] = {} | 270 for test, test_culprit in step_culprit['tests'].iteritems(): |
| 266 for test, test_culprit in step_culprit['tests'].iteritems(): | 271 culprit_data[step][test] = test_culprit['revision'] |
| 267 culprit_data[step][test] = test_culprit['revision'] | |
| 268 else: | |
| 269 culprit_data[step] = step_culprit['revision'] | |
| 270 return culprit_data | 272 return culprit_data |
| 271 | 273 |
| 272 # Arguments number differs from overridden method - pylint: disable=W0221 | 274 # Arguments number differs from overridden method - pylint: disable=W0221 |
| 273 def run( | 275 def run( |
| 274 self, master_name, builder_name, build_number, blame_list, try_job_type, | 276 self, master_name, builder_name, build_number, blame_list, try_job_type, |
| 275 try_job_id, result): | 277 try_job_id, result): |
| 276 """Identifies the information for failed revisions. | 278 """Identifies the information for failed revisions. |
| 277 | 279 |
| 278 Please refer to try_job_result_format.md for format check. | 280 Please refer to try_job_result_format.md for format check. |
| 279 """ | 281 """ |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 311 try_job_result.test_results) | 313 try_job_result.test_results) |
| 312 if (result_to_update and | 314 if (result_to_update and |
| 313 result_to_update[-1]['try_job_id'] == try_job_id): | 315 result_to_update[-1]['try_job_id'] == try_job_id): |
| 314 result_to_update[-1].update(result) | 316 result_to_update[-1].update(result) |
| 315 else: # pragma: no cover | 317 else: # pragma: no cover |
| 316 result_to_update.append(result) | 318 result_to_update.append(result) |
| 317 try_job_result.status = analysis_status.COMPLETED | 319 try_job_result.status = analysis_status.COMPLETED |
| 318 try_job_result.put() | 320 try_job_result.put() |
| 319 | 321 |
| 320 @ndb.transactional | 322 @ndb.transactional |
| 321 def UpdateWfAnalysisWithTryJobResult(): | 323 def UpdateWfAnalysisWithTryJobResult(try_job_suspected_cls): |
| 322 if not culprits: | 324 if not culprits: |
| 323 return | 325 return |
| 324 | |
| 325 # Update analysis result and suspected CLs with results of this try job if | 326 # Update analysis result and suspected CLs with results of this try job if |
| 326 # culprits were found. | 327 # culprits were found. |
| 327 analysis = WfAnalysis.Get(master_name, builder_name, build_number) | 328 analysis = WfAnalysis.Get(master_name, builder_name, build_number) |
| 328 updated_result_status = _GetResultAnalysisStatus(analysis, result) | 329 updated_result_status = _GetResultAnalysisStatus(analysis, result) |
| 329 updated_suspected_cls = _GetSuspectedCLs(analysis, result) | 330 updated_suspected_cls = _GetSuspectedCLs(analysis, try_job_suspected_cls) |
| 330 | |
| 331 if (analysis.result_status != updated_result_status or | 331 if (analysis.result_status != updated_result_status or |
| 332 analysis.suspected_cls != updated_suspected_cls): | 332 analysis.suspected_cls != updated_suspected_cls): |
| 333 analysis.result_status = updated_result_status | 333 analysis.result_status = updated_result_status |
| 334 analysis.suspected_cls = updated_suspected_cls | 334 analysis.suspected_cls = updated_suspected_cls |
| 335 analysis.put() | 335 analysis.put() |
| 336 | 336 |
| 337 def UpdateSuspectedCLs(try_job_suspected_cls): | |
| 338 if not culprits: | |
| 339 return | |
| 340 | |
| 341 # Creates or updates each suspected_cl. | |
| 342 for culprit in try_job_suspected_cls: | |
| 343 suspected_cl_util.UpdateSuspectedCL( | |
| 344 analysis_approach_type.TRY_JOB, master_name, builder_name, | |
| 345 build_number, try_job_type, | |
| 346 culprit['repo_name'], culprit['revision'], | |
| 347 culprit.get('commit_position')) | |
| 348 | |
| 349 try_job_suspected_cls = _GetTryJobSuspectedCLs(result) | |
| 337 # Store try-job results. | 350 # Store try-job results. |
| 338 UpdateTryJobResult() | 351 UpdateTryJobResult() |
| 339 # Add try-job results to WfAnalysis. | 352 # Add try-job results to WfAnalysis. |
| 340 UpdateWfAnalysisWithTryJobResult() | 353 UpdateWfAnalysisWithTryJobResult(try_job_suspected_cls) |
| 354 # Updates suspected_cl | |
|
lijeffrey
2016/08/17 19:31:11
nit: comment ends with .
chanli
2016/09/12 20:56:15
Done.
| |
| 355 UpdateSuspectedCLs(try_job_suspected_cls) | |
| 341 | 356 |
| 342 _NotifyCulprits(master_name, builder_name, build_number, culprits) | 357 _NotifyCulprits(master_name, builder_name, build_number, culprits) |
| 343 return result.get('culprit') if result else None | 358 return result.get('culprit') if result else None |
| OLD | NEW |