Chromium Code Reviews| Index: appengine/findit/waterfall/identify_try_job_culprit_pipeline.py |
| diff --git a/appengine/findit/waterfall/identify_try_job_culprit_pipeline.py b/appengine/findit/waterfall/identify_try_job_culprit_pipeline.py |
| index 568b5a8cb0060d04f9970ba4bcdc90e94e9322d3..83d8fbb4282df3a6bea19390581c6e30df2c7a39 100644 |
| --- a/appengine/findit/waterfall/identify_try_job_culprit_pipeline.py |
| +++ b/appengine/findit/waterfall/identify_try_job_culprit_pipeline.py |
| @@ -7,11 +7,31 @@ from common.http_client_appengine import HttpClientAppengine as HttpClient |
| from model import wf_analysis_status |
| from model.wf_try_job import WfTryJob |
| from pipeline_wrapper import BasePipeline |
| +from waterfall.try_job_type import TryJobType |
| + |
| + |
| +GIT_REPO = GitRepository( |
| + 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) |
| class IdentifyTryJobCulpritPipeline(BasePipeline): |
| """A pipeline to identify culprit CL info based on try job compile results.""" |
| + def _GetCulpritInfo(self, failed_revisions): |
| + """Gets commit_positions and review_urls for revisions.""" |
| + culprits = {} |
| + for failed_revision in failed_revisions: |
| + culprits[failed_revision] = { |
| + 'revision': failed_revision |
| + } |
| + change_log = GIT_REPO.GetChangeLog(failed_revision) |
| + if change_log: |
| + culprits[failed_revision]['commit_position'] = ( |
| + change_log.commit_position) |
| + culprits[failed_revision]['review_url'] = change_log.code_review_url |
| + |
| + return culprits |
| + |
| @staticmethod |
| def _GetFailedRevisionFromResultsDict(results_dict): |
| """Finds the failed revision from the given dict of revisions. |
| @@ -101,43 +121,89 @@ class IdentifyTryJobCulpritPipeline(BasePipeline): |
| return failed_revision |
| - @staticmethod |
| - def _GetCulpritFromFailedRevision(failed_revision): |
| - """Returns a culprit (dict) using failed_revision, or None.""" |
| - if not failed_revision: |
| - return None |
| - |
| - git_repo = GitRepository( |
| - 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) |
| - change_log = git_repo.GetChangeLog(failed_revision) |
| - |
| - if not change_log: |
| - return None |
| - |
| - return { |
| - 'revision': failed_revision, |
| - 'commit_position': change_log.commit_position, |
| - 'review_url': change_log.code_review_url |
| - } |
| + def _FindCulpritForEachTestFailure(self, blame_list, result): |
| + # For test failures, the try job will run against every revision, |
| + # so we need to traverse the result dict in chronological order to identify |
| + # the culprits for each failed step or test. |
| + culprit_map = {} |
| + failed_revisions = [] |
| + for revision in blame_list: |
| + for step, step_result in result['report'][revision].iteritems(): |
| + if step_result['valid'] and step_result['status'] == 'failed': |
|
qyearsley
2016/02/03 18:55:14
Optional change if you want to use one less level
chanli
2016/02/03 23:44:13
Done.
|
| + if revision not in failed_revisions: |
| + failed_revisions.append(revision) |
| + |
| + if step not in culprit_map: |
| + culprit_map[step] = { |
| + 'tests': {} |
| + } |
| + |
| + if (not step_result['failures'] and |
| + not culprit_map[step].get('revision')): |
| + # Non swarming test failures, only have step level failure info. |
| + culprit_map[step]['revision'] = revision |
| + |
| + for failed_test in step_result['failures']: |
| + # Swarming tests, gets first failed revision for each test. |
| + if failed_test not in culprit_map[step]['tests']: |
| + culprit_map[step]['tests'][failed_test] = { |
| + 'revision': revision |
| + } |
| + return culprit_map, failed_revisions |
| + |
| + def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): |
| + """Fills in commit_position and review_url for each failed rev in map.""" |
| + for step_culprit in culprit_map.values(): |
| + if step_culprit.get('revision'): |
| + culprit = culprits[step_culprit['revision']] |
| + step_culprit['commit_position'] = culprit['commit_position'] |
| + step_culprit['review_url'] = culprit['review_url'] |
| + for test_culprit in step_culprit.get('tests', {}).values(): |
| + test_revision = test_culprit['revision'] |
| + test_culprit.update(culprits[test_revision]) |
| # Arguments number differs from overridden method - pylint: disable=W0221 |
| - def run(self, master_name, builder_name, build_number, try_job_id, |
| - compile_result): |
| - culprit = None |
| - failed_revision = self._GetFailedRevisionFromCompileResult(compile_result) |
| - culprit = self._GetCulpritFromFailedRevision(failed_revision) |
| + def run( |
| + self, master_name, builder_name, build_number, blame_list, try_job_type, |
| + try_job_id, result): |
| + """Identifies the information for failed revisions. |
| + |
| + Please refer to try_job_result_format.md for format check. |
| + """ |
| + culprits = None |
| + if result and result.get('report'): |
| + if try_job_type == TryJobType.COMPILE: |
| + # For compile failures, the try job will stop if one revision fails, so |
| + # the culprit will be the last revision in the result. |
| + failed_revision = self._GetFailedRevisionFromCompileResult( |
| + result) |
| + failed_revisions = [failed_revision] if failed_revision else [] |
| + culprits = self._GetCulpritInfo(failed_revisions) |
| + if culprits: |
| + result['culprit'] = culprits[failed_revision] |
| + else: # try_job_type is 'test'. |
| + culprit_map, failed_revisions = self._FindCulpritForEachTestFailure( |
| + blame_list, result) |
| + culprits = self._GetCulpritInfo(failed_revisions) |
| + if culprits: |
| + self._UpdateCulpritMapWithCulpritInfo(culprit_map, culprits) |
| + result['culprit'] = culprit_map |
| # Store try job results. |
| try_job_result = WfTryJob.Get(master_name, builder_name, build_number) |
| - if culprit: |
| - compile_result['culprit'] = culprit |
| - if (try_job_result.compile_results and |
| - try_job_result.compile_results[-1]['try_job_id'] == try_job_id): |
| - try_job_result.compile_results[-1].update(compile_result) |
| + if culprits: |
| + result_to_update = ( |
| + try_job_result.compile_results if |
| + try_job_type == TryJobType.COMPILE else |
| + try_job_result.test_results) |
| + if (result_to_update and |
| + result_to_update[-1]['try_job_id'] == try_job_id): |
| + result_to_update[-1].update(result) |
| + |
| else: # pragma: no cover |
| - try_job_result.compile_results.append(compile_result) |
| + result_to_update.append(result) |
| try_job_result.status = wf_analysis_status.ANALYZED |
| try_job_result.put() |
| - return culprit |
| + return result.get('culprit', None) if result else None |
|
qyearsley
2016/02/03 18:55:14
Equivalent to `result.get('culprit') if result els
chanli
2016/02/03 23:44:13
Done.
|