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 |
| 18 from model.wf_suspected_cl import WfSuspectedCL | |
| 17 from model.wf_try_job import WfTryJob | 19 from model.wf_try_job import WfTryJob |
| 18 from model.wf_try_job_data import WfTryJobData | 20 from model.wf_try_job_data import WfTryJobData |
| 19 from waterfall.send_notification_for_culprit_pipeline import ( | 21 from waterfall.send_notification_for_culprit_pipeline import ( |
| 20 SendNotificationForCulpritPipeline) | 22 SendNotificationForCulpritPipeline) |
| 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 |
| (...skipping 17 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 _GetTryJobSuspectedCLs(result): |
| 57 """Returns all suspected CLs found by the try job. | |
| 58 | |
| 59 Args: | |
| 60 result: A result dict containing the culprit from the results of | |
| 61 this try job. | |
| 62 | |
| 63 Returns: | |
| 64 A list of suspected CLs found by this try job. | |
| 65 """ | |
| 66 suspected_cls = [] | |
| 67 if not result or not result.get('culprit'): | |
| 68 return suspected_cls | |
| 69 | |
| 70 culprit = result.get('culprit') | |
| 71 compile_cl_info = culprit.get('compile') | |
| 72 | |
| 73 if compile_cl_info: | |
| 74 # Suspected CL is from compile failure. | |
| 75 suspected_cls.append(compile_cl_info) | |
| 76 return suspected_cls | |
| 77 | |
| 78 # Suspected CLs are from test failures. | |
| 79 suspected_cl_revisions = [] | |
|
lijeffrey
2016/08/10 08:12:39
Move the test suspected_cls logic to a separate fu
chanli
2016/08/11 23:39:36
Done.
| |
| 80 for results in culprit.itervalues(): | |
| 81 for test_cl_info in results['tests'].values(): | |
|
lijeffrey
2016/08/10 08:12:39
nit: if using itervalues() above, why not use it h
chanli
2016/08/11 23:39:36
Done.
| |
| 82 revision = test_cl_info['revision'] | |
| 83 if revision not in suspected_cl_revisions: | |
| 84 suspected_cl_revisions.append(revision) | |
| 85 suspected_cls.append(test_cl_info) | |
| 86 | |
| 87 return suspected_cls | |
| 88 | |
| 89 | |
| 90 def _GetSuspectedCLs(analysis, try_job_suspected_cls): | |
| 55 """Returns a list of suspected CLs. | 91 """Returns a list of suspected CLs. |
| 56 | 92 |
| 57 Args: | 93 Args: |
| 58 analysis: The WfAnalysis entity corresponding to this try job. | 94 analysis: The WfAnalysis entity corresponding to this try job. |
| 59 result: A result dict containing the culprit from the results of | 95 is_compile_try_job: True if the try job is for compile failure, |
| 60 this try job. | 96 otherwise False. |
| 97 try_job_suspected_cls: A list of suspected CLs found by the try job. | |
| 61 | 98 |
| 62 Returns: | 99 Returns: |
| 63 A combined list of suspected CLs from those already in analysis and those | 100 A combined list of suspected CLs from those already in analysis and those |
| 64 found by this try job. | 101 found by this try job. |
| 65 """ | 102 """ |
| 66 suspected_cls = analysis.suspected_cls[:] if analysis.suspected_cls else [] | 103 suspected_cls = analysis.suspected_cls[:] if analysis.suspected_cls else [] |
| 67 suspected_cl_revisions = [cl['revision'] for cl in suspected_cls] | 104 suspected_cl_revisions = [cl['revision'] for cl in suspected_cls] |
| 68 culprit = result.get('culprit') | |
| 69 compile_cl_info = culprit.get('compile') | |
| 70 | 105 |
| 71 if compile_cl_info: | 106 for try_job_suspected_cl in try_job_suspected_cls: |
| 72 # Suspected CL is from compile failure. | 107 if try_job_suspected_cl['revision'] not in suspected_cl_revisions: |
| 73 revision = compile_cl_info.get('revision') | 108 suspected_cl_revisions.append(try_job_suspected_cl['revision']) |
| 74 if revision not in suspected_cl_revisions: | 109 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 | 110 |
| 100 return suspected_cls | 111 return suspected_cls |
| 101 | 112 |
| 102 | 113 |
| 103 def _GetFailedRevisionFromResultsDict(results_dict): | 114 def _GetFailedRevisionFromResultsDict(results_dict): |
| 104 """Finds the failed revision from the given dict of revisions. | 115 """Finds the failed revision from the given dict of revisions. |
| 105 | 116 |
| 106 Args: | 117 Args: |
| 107 results_dict: (dict) A dict that maps revisions to their results. For | 118 results_dict: (dict) A dict that maps revisions to their results. For |
| 108 example: | 119 example: |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 161 if (not test_result['valid'] or | 172 if (not test_result['valid'] or |
| 162 test_result['status'] != 'failed'): # pragma: no cover | 173 test_result['status'] != 'failed'): # pragma: no cover |
| 163 continue | 174 continue |
| 164 | 175 |
| 165 failed_revisions.add(revision) | 176 failed_revisions.add(revision) |
| 166 | 177 |
| 167 if step not in culprit_map: | 178 if step not in culprit_map: |
| 168 culprit_map[step] = { | 179 culprit_map[step] = { |
| 169 'tests': {} | 180 'tests': {} |
| 170 } | 181 } |
| 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']: | 182 for failed_test in test_result['failures']: |
| 176 # Swarming tests, gets first failed revision for each test. | 183 # Swarming tests, gets first failed revision for each test. |
| 177 if failed_test not in culprit_map[step]['tests']: | 184 if failed_test not in culprit_map[step]['tests']: |
| 178 culprit_map[step]['tests'][failed_test] = { | 185 culprit_map[step]['tests'][failed_test] = { |
| 179 'revision': revision | 186 'revision': revision |
| 180 } | 187 } |
| 181 | 188 |
| 182 return culprit_map, list(failed_revisions) | 189 return culprit_map, list(failed_revisions) |
| 183 | 190 |
| 184 | 191 |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 241 } | 248 } |
| 242 failed_revisions.add(revision) | 249 failed_revisions.add(revision) |
| 243 return culprit_map, list(failed_revisions) | 250 return culprit_map, list(failed_revisions) |
| 244 | 251 |
| 245 return _GetCulpritsForTestsFromResultsDict( | 252 return _GetCulpritsForTestsFromResultsDict( |
| 246 blame_list, result['report'].get('result')) | 253 blame_list, result['report'].get('result')) |
| 247 | 254 |
| 248 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): | 255 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): |
| 249 """Fills in commit_position and review url for each failed rev in map.""" | 256 """Fills in commit_position and review url for each failed rev in map.""" |
| 250 for step_culprit in culprit_map.values(): | 257 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(): | 258 for test_culprit in step_culprit.get('tests', {}).values(): |
| 257 test_revision = test_culprit['revision'] | 259 test_revision = test_culprit['revision'] |
| 258 test_culprit.update(culprits[test_revision]) | 260 test_culprit.update(culprits[test_revision]) |
| 259 | 261 |
| 260 def _GetCulpritDataForTest(self, culprit_map): | 262 def _GetCulpritDataForTest(self, culprit_map): |
| 261 """Gets culprit revision for each failure for try job metadata.""" | 263 """Gets culprit revision for each failure for try job metadata.""" |
| 262 culprit_data = {} | 264 culprit_data = {} |
| 263 for step, step_culprit in culprit_map.iteritems(): | 265 for step, step_culprit in culprit_map.iteritems(): |
| 264 if step_culprit['tests']: | 266 culprit_data[step] = {} |
| 265 culprit_data[step] = {} | 267 for test, test_culprit in step_culprit['tests'].iteritems(): |
| 266 for test, test_culprit in step_culprit['tests'].iteritems(): | 268 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 | 269 return culprit_data |
| 271 | 270 |
| 272 # Arguments number differs from overridden method - pylint: disable=W0221 | 271 # Arguments number differs from overridden method - pylint: disable=W0221 |
| 273 def run( | 272 def run( |
| 274 self, master_name, builder_name, build_number, blame_list, try_job_type, | 273 self, master_name, builder_name, build_number, blame_list, try_job_type, |
| 275 try_job_id, result): | 274 try_job_id, result): |
| 276 """Identifies the information for failed revisions. | 275 """Identifies the information for failed revisions. |
| 277 | 276 |
| 278 Please refer to try_job_result_format.md for format check. | 277 Please refer to try_job_result_format.md for format check. |
| 279 """ | 278 """ |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 311 try_job_result.test_results) | 310 try_job_result.test_results) |
| 312 if (result_to_update and | 311 if (result_to_update and |
| 313 result_to_update[-1]['try_job_id'] == try_job_id): | 312 result_to_update[-1]['try_job_id'] == try_job_id): |
| 314 result_to_update[-1].update(result) | 313 result_to_update[-1].update(result) |
| 315 else: # pragma: no cover | 314 else: # pragma: no cover |
| 316 result_to_update.append(result) | 315 result_to_update.append(result) |
| 317 try_job_result.status = analysis_status.COMPLETED | 316 try_job_result.status = analysis_status.COMPLETED |
| 318 try_job_result.put() | 317 try_job_result.put() |
| 319 | 318 |
| 320 @ndb.transactional | 319 @ndb.transactional |
| 321 def UpdateWfAnalysisWithTryJobResult(): | 320 def UpdateWfAnalysisWithTryJobResult(try_job_suspected_cls): |
| 322 if not culprits: | 321 if not culprits: |
| 323 return | 322 return |
| 324 | |
| 325 # Update analysis result and suspected CLs with results of this try job if | 323 # Update analysis result and suspected CLs with results of this try job if |
| 326 # culprits were found. | 324 # culprits were found. |
| 327 analysis = WfAnalysis.Get(master_name, builder_name, build_number) | 325 analysis = WfAnalysis.Get(master_name, builder_name, build_number) |
| 328 updated_result_status = _GetResultAnalysisStatus(analysis, result) | 326 updated_result_status = _GetResultAnalysisStatus(analysis, result) |
| 329 updated_suspected_cls = _GetSuspectedCLs(analysis, result) | 327 updated_suspected_cls = _GetSuspectedCLs(analysis, try_job_suspected_cls) |
| 330 | |
| 331 if (analysis.result_status != updated_result_status or | 328 if (analysis.result_status != updated_result_status or |
| 332 analysis.suspected_cls != updated_suspected_cls): | 329 analysis.suspected_cls != updated_suspected_cls): |
| 333 analysis.result_status = updated_result_status | 330 analysis.result_status = updated_result_status |
| 334 analysis.suspected_cls = updated_suspected_cls | 331 analysis.suspected_cls = updated_suspected_cls |
| 335 analysis.put() | 332 analysis.put() |
| 336 | 333 |
| 334 @ndb.transactional | |
| 335 def CreateOrUpdateSuspectedCL(repo_name, revision, commit_position): | |
|
lijeffrey
2016/08/10 08:12:39
this looks almost the same as _SaveSuspectedCL in
lijeffrey
2016/08/10 08:12:39
nit: Why not just name this UpdateSuspectedCL?
chanli
2016/08/11 23:39:36
Done.
chanli
2016/08/11 23:39:36
Done.
| |
| 336 suspected_cl = WfSuspectedCL.Get(repo_name, revision) | |
|
lijeffrey
2016/08/10 08:12:39
suspected_cl = WfSuspectedCl.Get(...) or WfSuspect
chanli
2016/08/11 23:39:36
Done.
| |
| 337 if not suspected_cl: | |
| 338 suspected_cl = WfSuspectedCL.Create( | |
| 339 repo_name, revision, commit_position) | |
| 340 | |
| 341 if suspected_cl.approach == analysis_approach_type.HEURISTIC: | |
| 342 suspected_cl.approach = analysis_approach_type.BOTH | |
| 343 elif suspected_cl.approach is None: #pragma: no cover | |
| 344 suspected_cl.approach = analysis_approach_type.TRY_JOB | |
| 345 | |
| 346 suspected_cl.failure_type = suspected_cl.failure_type or try_job_type | |
| 347 | |
| 348 build_info = [master_name, builder_name, build_number] | |
| 349 if build_info not in suspected_cl.builds: | |
| 350 suspected_cl.builds.append(build_info) | |
| 351 | |
| 352 suspected_cl.put() | |
| 353 | |
| 354 def CreateOrUpdateSuspectedCLs(try_job_suspected_cls): | |
| 355 if not culprits: | |
| 356 return | |
| 357 | |
| 358 # Creates or updates each suspected_cl. | |
| 359 for culprit in try_job_suspected_cls: | |
| 360 CreateOrUpdateSuspectedCL( | |
| 361 culprit['repo_name'], culprit['revision'], | |
| 362 culprit.get('commit_position')) | |
| 363 | |
| 364 try_job_suspected_cls = _GetTryJobSuspectedCLs(result) | |
| 337 # Store try-job results. | 365 # Store try-job results. |
| 338 UpdateTryJobResult() | 366 UpdateTryJobResult() |
| 339 # Add try-job results to WfAnalysis. | 367 # Add try-job results to WfAnalysis. |
| 340 UpdateWfAnalysisWithTryJobResult() | 368 UpdateWfAnalysisWithTryJobResult(try_job_suspected_cls) |
| 369 # Updates suspected_cl | |
| 370 CreateOrUpdateSuspectedCLs(try_job_suspected_cls) | |
| 341 | 371 |
| 342 _NotifyCulprits(master_name, builder_name, build_number, culprits) | 372 _NotifyCulprits(master_name, builder_name, build_number, culprits) |
| 343 return result.get('culprit') if result else None | 373 return result.get('culprit') if result else None |
| OLD | NEW |