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) | |
|
chanli
2016/09/12 20:56:15
Have deleted this part.
| |
| 91 | |
| 92 | |
| 93 def _GetSuspectedCLs(analysis, culprits): | |
| 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, |
| 60 this try job. | 99 otherwise False. |
| 100 culprits: 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 revision, try_job_suspected_cl in culprits.iteritems(): |
| 72 # Suspected CL is from compile failure. | |
| 73 revision = compile_cl_info.get('revision') | |
| 74 if revision not in suspected_cl_revisions: | 110 if revision not in suspected_cl_revisions: |
| 75 suspected_cl_revisions.append(revision) | 111 suspected_cl_revisions.append(revision) |
| 76 suspected_cls.append(compile_cl_info) | 112 suspected_cls.append(try_job_suspected_cl) |
| 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 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 234 send_notification_right_now) | 244 send_notification_right_now) |
| 235 elif compile_suspected_cl: | 245 elif compile_suspected_cl: |
| 236 # A special case where try job didn't find any suspected cls, but | 246 # A special case where try job didn't find any suspected cls, but |
| 237 # heuristic found a suspected_cl. | 247 # heuristic found a suspected_cl. |
| 238 _StartSendNotificationPipeline( | 248 _StartSendNotificationPipeline( |
| 239 master_name, builder_name, build_number, | 249 master_name, builder_name, build_number, |
| 240 compile_suspected_cl['repo_name'], compile_suspected_cl['revision'], | 250 compile_suspected_cl['repo_name'], compile_suspected_cl['revision'], |
| 241 send_notification_right_now=True) | 251 send_notification_right_now=True) |
| 242 | 252 |
| 243 | 253 |
| 254 def _GetTestFailureCausedByCL(result): | |
| 255 if not result: | |
| 256 return None | |
| 257 | |
| 258 failures = {} | |
| 259 for step_name, step_result in result.iteritems(): | |
| 260 if step_result['status'] == 'failed': | |
| 261 failures[step_name] = step_result['failures'] | |
| 262 | |
| 263 return failures | |
| 264 | |
| 265 | |
| 244 class IdentifyTryJobCulpritPipeline(BasePipeline): | 266 class IdentifyTryJobCulpritPipeline(BasePipeline): |
| 245 """A pipeline to identify culprit CL info based on try job compile results.""" | 267 """A pipeline to identify culprit CL info based on try job compile results.""" |
| 246 | 268 |
| 247 def _GetCulpritInfo(self, failed_revisions): | 269 def _GetCulpritInfo(self, failed_revisions): |
| 248 """Gets commit_positions and review urls for revisions.""" | 270 """Gets commit_positions and review urls for revisions.""" |
| 249 culprits = {} | 271 culprits = {} |
| 250 # TODO(lijeffrey): remove hard-coded 'chromium' when DEPS file parsing is | 272 # TODO(lijeffrey): remove hard-coded 'chromium' when DEPS file parsing is |
| 251 # supported. | 273 # supported. |
| 252 for failed_revision in failed_revisions: | 274 for failed_revision in failed_revisions: |
| 253 culprits[failed_revision] = { | 275 culprits[failed_revision] = { |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 288 } | 310 } |
| 289 failed_revisions.add(revision) | 311 failed_revisions.add(revision) |
| 290 return culprit_map, list(failed_revisions) | 312 return culprit_map, list(failed_revisions) |
| 291 | 313 |
| 292 return _GetCulpritsForTestsFromResultsDict( | 314 return _GetCulpritsForTestsFromResultsDict( |
| 293 blame_list, result['report'].get('result')) | 315 blame_list, result['report'].get('result')) |
| 294 | 316 |
| 295 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): | 317 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): |
| 296 """Fills in commit_position and review url for each failed rev in map.""" | 318 """Fills in commit_position and review url for each failed rev in map.""" |
| 297 for step_culprit in culprit_map.values(): | 319 for step_culprit in culprit_map.values(): |
| 298 if step_culprit.get('revision'): | |
| 299 culprit = culprits[step_culprit['revision']] | |
| 300 step_culprit['commit_position'] = culprit['commit_position'] | |
| 301 step_culprit['url'] = culprit['url'] | |
| 302 step_culprit['repo_name'] = culprit['repo_name'] | |
| 303 for test_culprit in step_culprit.get('tests', {}).values(): | 320 for test_culprit in step_culprit.get('tests', {}).values(): |
| 304 test_revision = test_culprit['revision'] | 321 test_revision = test_culprit['revision'] |
| 305 test_culprit.update(culprits[test_revision]) | 322 test_culprit.update(culprits[test_revision]) |
| 306 | 323 |
| 307 def _GetCulpritDataForTest(self, culprit_map): | 324 def _GetCulpritDataForTest(self, culprit_map): |
| 308 """Gets culprit revision for each failure for try job metadata.""" | 325 """Gets culprit revision for each failure for try job metadata.""" |
| 309 culprit_data = {} | 326 culprit_data = {} |
| 310 for step, step_culprit in culprit_map.iteritems(): | 327 for step, step_culprit in culprit_map.iteritems(): |
| 311 if step_culprit['tests']: | 328 culprit_data[step] = {} |
| 312 culprit_data[step] = {} | 329 for test, test_culprit in step_culprit['tests'].iteritems(): |
| 313 for test, test_culprit in step_culprit['tests'].iteritems(): | 330 culprit_data[step][test] = test_culprit['revision'] |
| 314 culprit_data[step][test] = test_culprit['revision'] | |
| 315 else: | |
| 316 culprit_data[step] = step_culprit['revision'] | |
| 317 return culprit_data | 331 return culprit_data |
| 318 | 332 |
| 319 # Arguments number differs from overridden method - pylint: disable=W0221 | 333 # Arguments number differs from overridden method - pylint: disable=W0221 |
| 320 def run( | 334 def run( |
| 321 self, master_name, builder_name, build_number, blame_list, try_job_type, | 335 self, master_name, builder_name, build_number, blame_list, try_job_type, |
| 322 try_job_id, result): | 336 try_job_id, result): |
| 323 """Identifies the information for failed revisions. | 337 """Identifies the information for failed revisions. |
| 324 | 338 |
| 325 Please refer to try_job_result_format.md for format check. | 339 Please refer to try_job_result_format.md for format check. |
| 326 """ | 340 """ |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 362 else: # pragma: no cover | 376 else: # pragma: no cover |
| 363 result_to_update.append(result) | 377 result_to_update.append(result) |
| 364 try_job_result.status = analysis_status.COMPLETED | 378 try_job_result.status = analysis_status.COMPLETED |
| 365 try_job_result.put() | 379 try_job_result.put() |
| 366 | 380 |
| 367 @ndb.transactional | 381 @ndb.transactional |
| 368 def UpdateWfAnalysisWithTryJobResult(): | 382 def UpdateWfAnalysisWithTryJobResult(): |
| 369 if not culprits: | 383 if not culprits: |
| 370 return | 384 return |
| 371 | 385 |
| 386 | |
| 387 print culprits | |
|
chanli
2016/09/12 20:56:15
Have removed unnecessary lines.
| |
| 388 | |
| 372 analysis = WfAnalysis.Get(master_name, builder_name, build_number) | 389 analysis = WfAnalysis.Get(master_name, builder_name, build_number) |
| 373 # Update analysis result and suspected CLs with results of this try job if | 390 # Update analysis result and suspected CLs with results of this try job if |
| 374 # culprits were found. | 391 # culprits were found. |
| 375 updated_result_status = _GetResultAnalysisStatus(analysis, result) | 392 updated_result_status = _GetResultAnalysisStatus(analysis, result) |
| 376 updated_suspected_cls = _GetSuspectedCLs(analysis, result) | 393 updated_suspected_cls = _GetSuspectedCLs(analysis, culprits) |
| 377 | |
| 378 if (analysis.result_status != updated_result_status or | 394 if (analysis.result_status != updated_result_status or |
| 379 analysis.suspected_cls != updated_suspected_cls): | 395 analysis.suspected_cls != updated_suspected_cls): |
| 380 analysis.result_status = updated_result_status | 396 analysis.result_status = updated_result_status |
| 381 analysis.suspected_cls = updated_suspected_cls | 397 analysis.suspected_cls = updated_suspected_cls |
| 382 analysis.put() | 398 analysis.put() |
| 383 | 399 |
| 400 def UpdateSuspectedCLs(): | |
| 401 if not culprits: | |
| 402 return | |
| 403 | |
| 404 # Creates or updates each suspected_cl. | |
| 405 for culprit in culprits.values(): | |
| 406 revision = culprit['revision'] | |
| 407 if try_job_type == failure_type.COMPILE: | |
| 408 failures = {'compile': []} | |
| 409 else: | |
| 410 failures = _GetTestFailureCausedByCL( | |
| 411 result.get('report', {}).get('result', {}).get(revision)) | |
| 412 | |
| 413 suspected_cl_util.UpdateSuspectedCL( | |
| 414 culprit['repo_name'], revision, culprit.get('commit_position'), | |
| 415 analysis_approach_type.TRY_JOB, master_name, builder_name, | |
| 416 build_number, try_job_type, failures, None) | |
| 417 | |
| 418 # try_job_suspected_cls = _GetTryJobSuspectedCLs(result) | |
| 384 # Store try-job results. | 419 # Store try-job results. |
| 385 UpdateTryJobResult() | 420 UpdateTryJobResult() |
| 386 | 421 |
| 387 # Saves cls found by heuristic approach for later use. | 422 # Saves cls found by heuristic approach for later use. |
| 388 # This part must be before UpdateWfAnalysisWithTryJobResult(). | 423 # This part must be before UpdateWfAnalysisWithTryJobResult(). |
| 389 analysis = WfAnalysis.Get(master_name, builder_name, build_number) | 424 analysis = WfAnalysis.Get(master_name, builder_name, build_number) |
| 390 heuristic_cls = _GetHeuristicSuspectedCLs(analysis) | 425 heuristic_cls = _GetHeuristicSuspectedCLs(analysis) |
| 391 compile_suspected_cl = ( | 426 compile_suspected_cl = ( |
| 392 _GetSuspectedCLFoundByHeuristicForCompile(analysis) | 427 _GetSuspectedCLFoundByHeuristicForCompile(analysis) |
| 393 if try_job_type == failure_type.COMPILE else None) | 428 if try_job_type == failure_type.COMPILE else None) |
| 394 | 429 |
| 395 # Add try-job results to WfAnalysis. | 430 # Add try-job results to WfAnalysis. |
| 396 UpdateWfAnalysisWithTryJobResult() | 431 UpdateWfAnalysisWithTryJobResult() |
| 432 # Updates suspected_cl | |
| 433 UpdateSuspectedCLs() | |
| 397 | 434 |
| 398 _NotifyCulprits(master_name, builder_name, build_number, culprits, | 435 _NotifyCulprits(master_name, builder_name, build_number, culprits, |
| 399 heuristic_cls, compile_suspected_cl) | 436 heuristic_cls, compile_suspected_cl) |
| 400 return result.get('culprit') if result else None | 437 return result.get('culprit') if result else None |
| OLD | NEW |