| 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 |
| 6 |
| 5 from google.appengine.ext import ndb | 7 from google.appengine.ext import ndb |
| 6 | 8 |
| 7 from common.git_repository import GitRepository | 9 from common.git_repository import GitRepository |
| 8 from common.http_client_appengine import HttpClientAppengine as HttpClient | 10 from common.http_client_appengine import HttpClientAppengine as HttpClient |
| 9 from common.pipeline_wrapper import BasePipeline | 11 from common.pipeline_wrapper import BasePipeline |
| 10 from model import analysis_status | 12 from model import analysis_status |
| 11 from model import result_status | 13 from model import result_status |
| 12 from model.wf_analysis import WfAnalysis | 14 from model.wf_analysis import WfAnalysis |
| 13 from model.wf_try_job import WfTryJob | 15 from model.wf_try_job import WfTryJob |
| 14 from model.wf_try_job_data import WfTryJobData | 16 from model.wf_try_job_data import WfTryJobData |
| (...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 160 | 162 |
| 161 if not report: | 163 if not report: |
| 162 return None | 164 return None |
| 163 | 165 |
| 164 if report.get('culprit'): | 166 if report.get('culprit'): |
| 165 return report.get('culprit') | 167 return report.get('culprit') |
| 166 | 168 |
| 167 return IdentifyTryJobCulpritPipeline._GetFailedRevisionFromResultsDict( | 169 return IdentifyTryJobCulpritPipeline._GetFailedRevisionFromResultsDict( |
| 168 report.get('result', {})) | 170 report.get('result', {})) |
| 169 | 171 |
| 170 def _FindCulpritForEachTestFailure(self, blame_list, result): | 172 @staticmethod |
| 171 # For test failures, the try job will run against every revision, | 173 def _GetCulpritsForTestsFromResultsDict(blame_list, test_results): |
| 172 # so we need to traverse the result dict in chronological order to identify | |
| 173 # the culprits for each failed step or test. | |
| 174 culprit_map = {} | 174 culprit_map = {} |
| 175 failed_revisions = [] | 175 failed_revisions = set() |
| 176 |
| 176 for revision in blame_list: | 177 for revision in blame_list: |
| 177 test_results = result['report'].get('result') | 178 if not test_results.get(revision): |
| 179 continue |
| 178 | 180 |
| 179 for step, test_result in test_results[revision].iteritems(): | 181 for step, test_result in test_results[revision].iteritems(): |
| 180 if (not test_result['valid'] or | 182 if (not test_result['valid'] or |
| 181 test_result['status'] != 'failed'): # pragma: no cover | 183 test_result['status'] != 'failed'): # pragma: no cover |
| 182 continue | 184 continue |
| 183 | 185 |
| 184 if revision not in failed_revisions: | 186 failed_revisions.add(revision) |
| 185 failed_revisions.append(revision) | |
| 186 | 187 |
| 187 if step not in culprit_map: | 188 if step not in culprit_map: |
| 188 culprit_map[step] = { | 189 culprit_map[step] = { |
| 189 'tests': {} | 190 'tests': {} |
| 190 } | 191 } |
| 191 | |
| 192 if (not test_result['failures'] and | 192 if (not test_result['failures'] and |
| 193 not culprit_map[step].get('revision')): | 193 not culprit_map[step].get('revision')): |
| 194 # Non swarming test failures, only have step level failure info. | 194 # Non swarming test failures, only have step level failure info. |
| 195 culprit_map[step]['revision'] = revision | 195 culprit_map[step]['revision'] = revision |
| 196 culprit_map[step]['repo_name'] = 'chromium' | |
| 197 | |
| 198 for failed_test in test_result['failures']: | 196 for failed_test in test_result['failures']: |
| 199 # Swarming tests, gets first failed revision for each test. | 197 # Swarming tests, gets first failed revision for each test. |
| 200 if failed_test not in culprit_map[step]['tests']: | 198 if failed_test not in culprit_map[step]['tests']: |
| 201 culprit_map[step]['tests'][failed_test] = { | 199 culprit_map[step]['tests'][failed_test] = { |
| 202 'revision': revision | 200 'revision': revision |
| 203 } | 201 } |
| 204 | 202 |
| 205 return culprit_map, failed_revisions | 203 return culprit_map, list(failed_revisions) |
| 204 |
| 205 def _FindCulpritForEachTestFailure(self, blame_list, result): |
| 206 # For test failures, we need to traverse the result dict in chronological |
| 207 # order to identify the culprits for each failed step or test. |
| 208 # The earliest revision that a test failed is the culprit. |
| 209 culprit_map = defaultdict(dict) |
| 210 failed_revisions = set() |
| 211 |
| 212 # Recipe should return culprits with the farmat as: |
| 213 # 'culprits': { |
| 214 # 'step1': { |
| 215 # 'test1': 'rev1', |
| 216 # 'test2': 'rev2', |
| 217 # ... |
| 218 # }, |
| 219 # ... |
| 220 # } |
| 221 if result['report'].get('culprits'): |
| 222 for step_name, tests in result['report']['culprits'].iteritems(): |
| 223 culprit_map[step_name]['tests'] = {} |
| 224 for test_name, revision in tests.iteritems(): |
| 225 culprit_map[step_name]['tests'][test_name] = { |
| 226 'revision': revision |
| 227 } |
| 228 failed_revisions.add(revision) |
| 229 return culprit_map, list(failed_revisions) |
| 230 |
| 231 return IdentifyTryJobCulpritPipeline._GetCulpritsForTestsFromResultsDict( |
| 232 blame_list, result['report'].get('result')) |
| 206 | 233 |
| 207 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): | 234 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): |
| 208 """Fills in commit_position and review url for each failed rev in map.""" | 235 """Fills in commit_position and review url for each failed rev in map.""" |
| 209 for step_culprit in culprit_map.values(): | 236 for step_culprit in culprit_map.values(): |
| 210 if step_culprit.get('revision'): | 237 if step_culprit.get('revision'): |
| 211 culprit = culprits[step_culprit['revision']] | 238 culprit = culprits[step_culprit['revision']] |
| 212 step_culprit['commit_position'] = culprit['commit_position'] | 239 step_culprit['commit_position'] = culprit['commit_position'] |
| 213 step_culprit['url'] = culprit['url'] | 240 step_culprit['url'] = culprit['url'] |
| 241 step_culprit['repo_name'] = culprit['repo_name'] |
| 214 for test_culprit in step_culprit.get('tests', {}).values(): | 242 for test_culprit in step_culprit.get('tests', {}).values(): |
| 215 test_revision = test_culprit['revision'] | 243 test_revision = test_culprit['revision'] |
| 216 test_culprit.update(culprits[test_revision]) | 244 test_culprit.update(culprits[test_revision]) |
| 217 | 245 |
| 218 def _GetCulpritDataForTest(self, culprit_map): | 246 def _GetCulpritDataForTest(self, culprit_map): |
| 219 """Gets culprit revision for each failure for try job metadata.""" | 247 """Gets culprit revision for each failure for try job metadata.""" |
| 220 culprit_data = {} | 248 culprit_data = {} |
| 221 for step, step_culprit in culprit_map.iteritems(): | 249 for step, step_culprit in culprit_map.iteritems(): |
| 222 if step_culprit['tests']: | 250 if step_culprit['tests']: |
| 223 culprit_data[step] = {} | 251 culprit_data[step] = {} |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 292 analysis.result_status = updated_result_status | 320 analysis.result_status = updated_result_status |
| 293 analysis.suspected_cls = updated_suspected_cls | 321 analysis.suspected_cls = updated_suspected_cls |
| 294 analysis.put() | 322 analysis.put() |
| 295 | 323 |
| 296 # Store try-job results. | 324 # Store try-job results. |
| 297 UpdateTryJobResult() | 325 UpdateTryJobResult() |
| 298 # Add try-job results to WfAnalysis. | 326 # Add try-job results to WfAnalysis. |
| 299 UpdateWfAnalysisWithTryJobResult() | 327 UpdateWfAnalysisWithTryJobResult() |
| 300 | 328 |
| 301 return result.get('culprit') if result else None | 329 return result.get('culprit') if result else None |
| OLD | NEW |