| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 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 copy |
| 6 from datetime import datetime | 7 from datetime import datetime |
| 7 | 8 |
| 8 from google.appengine.api import users | 9 from google.appengine.api import users |
| 9 | 10 |
| 10 from common import constants | 11 from common import constants |
| 11 from common.base_handler import BaseHandler | 12 from common.base_handler import BaseHandler |
| 12 from common.base_handler import Permission | 13 from common.base_handler import Permission |
| 13 from common.waterfall import failure_type | 14 from common.waterfall import failure_type |
| 14 from handlers import handlers_util | 15 from handlers import handlers_util |
| 15 from handlers import result_status | 16 from handlers import result_status |
| 16 from handlers.result_status import NO_TRY_JOB_REASON_MAP | 17 from handlers.result_status import NO_TRY_JOB_REASON_MAP |
| 17 from model import analysis_status | 18 from model import analysis_status |
| 19 from model.base_build_model import BaseBuildModel |
| 20 from model.result_status import RESULT_STATUS_TO_DESCRIPTION |
| 21 from model.suspected_cl_status import CL_STATUS_TO_DESCRIPTION |
| 18 from model.wf_analysis import WfAnalysis | 22 from model.wf_analysis import WfAnalysis |
| 23 from model.wf_suspected_cl import WfSuspectedCL |
| 19 from model.wf_try_job import WfTryJob | 24 from model.wf_try_job import WfTryJob |
| 20 from model.result_status import RESULT_STATUS_TO_DESCRIPTION | |
| 21 from waterfall import build_failure_analysis_pipelines | 25 from waterfall import build_failure_analysis_pipelines |
| 22 from waterfall import buildbot | 26 from waterfall import buildbot |
| 23 from waterfall import waterfall_config | 27 from waterfall import waterfall_config |
| 24 | 28 |
| 25 | 29 |
| 26 NON_SWARMING = object() | 30 NON_SWARMING = object() |
| 27 | 31 |
| 28 | 32 |
| 29 def _FormatDatetime(dt): | 33 def _FormatDatetime(dt): |
| 30 if not dt: | 34 if not dt: |
| 31 return None | 35 return None |
| 32 else: | 36 else: |
| 33 return dt.strftime('%Y-%m-%d %H:%M:%S UTC') | 37 return dt.strftime('%Y-%m-%d %H:%M:%S UTC') |
| 34 | 38 |
| 35 | 39 |
| 40 def _GetCLDict(analysis, cl_info): |
| 41 if not cl_info: |
| 42 return {} |
| 43 |
| 44 cl_keys = cl_info.split('/') |
| 45 repo_name = cl_keys[0] |
| 46 revision = cl_keys[1] |
| 47 for cl in analysis.suspected_cls: |
| 48 if cl['repo_name'] == repo_name and cl['revision'] == revision: |
| 49 return cl |
| 50 return {} |
| 51 |
| 52 |
| 36 def _GetTriageHistory(analysis): | 53 def _GetTriageHistory(analysis): |
| 37 if (not users.is_current_user_admin() or | 54 if (not users.is_current_user_admin() or |
| 38 not analysis.completed or | 55 not analysis.completed or |
| 39 not analysis.triage_history): | 56 not analysis.triage_history): |
| 40 return None | 57 return None |
| 41 | 58 |
| 42 triage_history = [] | 59 triage_history = [] |
| 43 for triage_record in analysis.triage_history: | 60 for triage_record in analysis.triage_history: |
| 61 |
| 44 triage_history.append({ | 62 triage_history.append({ |
| 45 'triage_time': _FormatDatetime( | 63 'triage_time': _FormatDatetime( |
| 46 datetime.utcfromtimestamp(triage_record['triage_timestamp'])), | 64 datetime.utcfromtimestamp(triage_record['triage_timestamp'])), |
| 47 'user_name': triage_record['user_name'], | 65 'user_name': triage_record['user_name'], |
| 48 'result_status': RESULT_STATUS_TO_DESCRIPTION.get( | 66 'triaged_cl': _GetCLDict(analysis, triage_record.get('triaged_cl')), |
| 49 triage_record['result_status']), | 67 'result_status': ( |
| 68 RESULT_STATUS_TO_DESCRIPTION.get(triage_record.get('result_status')) |
| 69 or CL_STATUS_TO_DESCRIPTION.get(triage_record.get('cl_status'))), |
| 50 'version': triage_record.get('version'), | 70 'version': triage_record.get('version'), |
| 51 }) | 71 }) |
| 52 | 72 |
| 53 return triage_history | 73 return triage_history |
| 54 | 74 |
| 55 | 75 |
| 56 def _GetOrganizedAnalysisResultBySuspectedCL(analysis_result): | 76 def _GetOrganizedAnalysisResultBySuspectedCL(analysis_result): |
| 57 """Group tests it they have the same suspected CLs.""" | 77 """Group tests it they have the same suspected CLs.""" |
| 58 organized_results = defaultdict(list) | 78 organized_results = defaultdict(list) |
| 59 | 79 |
| (...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 295 compile_failure = None | 315 compile_failure = None |
| 296 for failure in analysis.result.get('failures', []): | 316 for failure in analysis.result.get('failures', []): |
| 297 if failure['step_name'] == constants.COMPILE_STEP_NAME: | 317 if failure['step_name'] == constants.COMPILE_STEP_NAME: |
| 298 compile_failure = failure | 318 compile_failure = failure |
| 299 if compile_failure: # pragma: no branch. | 319 if compile_failure: # pragma: no branch. |
| 300 data['first_failure'] = compile_failure['first_failure'] | 320 data['first_failure'] = compile_failure['first_failure'] |
| 301 data['last_pass'] = compile_failure['last_pass'] | 321 data['last_pass'] = compile_failure['last_pass'] |
| 302 data['suspected_cls_by_heuristic'] = compile_failure['suspected_cls'] | 322 data['suspected_cls_by_heuristic'] = compile_failure['suspected_cls'] |
| 303 | 323 |
| 304 | 324 |
| 325 def _GetAllSuspectedCLsAndCheckStatus( |
| 326 master_name, builder_name, build_number, analysis): |
| 327 build_key = BaseBuildModel.CreateBuildId( |
| 328 master_name, builder_name, build_number) |
| 329 suspected_cls = copy.deepcopy(analysis.suspected_cls) |
| 330 if not suspected_cls: |
| 331 return [] |
| 332 |
| 333 for cl in suspected_cls: |
| 334 cl['status'] = None |
| 335 suspected_cl = WfSuspectedCL.Get(cl['repo_name'], cl['revision']) |
| 336 # TODO(chanli): Write a script to convert legacy data before enable |
| 337 # this change. |
| 338 if not suspected_cl: |
| 339 continue |
| 340 |
| 341 build = suspected_cl.builds.get(build_key) |
| 342 if build: |
| 343 cl['status'] = build['status'] |
| 344 |
| 345 return suspected_cls |
| 346 |
| 347 |
| 305 class BuildFailure(BaseHandler): | 348 class BuildFailure(BaseHandler): |
| 306 PERMISSION_LEVEL = Permission.ANYONE | 349 PERMISSION_LEVEL = Permission.ANYONE |
| 307 | 350 |
| 308 def _ShowDebugInfo(self): | 351 def _ShowDebugInfo(self): |
| 309 # Show debug info only if the app is run locally during development, if the | 352 # Show debug info only if the app is run locally during development, if the |
| 310 # currently logged-in user is an admin, or if it is explicitly requested | 353 # currently logged-in user is an admin, or if it is explicitly requested |
| 311 # with parameter 'debug=1'. | 354 # with parameter 'debug=1'. |
| 312 return users.is_current_user_admin() or self.request.get('debug') == '1' | 355 return users.is_current_user_admin() or self.request.get('debug') == '1' |
| 313 | 356 |
| 314 def _ShowTriageHelpButton(self): | 357 def _ShowTriageHelpButton(self): |
| (...skipping 18 matching lines...) Expand all Loading... |
| 333 'triage_history': _GetTriageHistory(analysis), | 376 'triage_history': _GetTriageHistory(analysis), |
| 334 'show_triage_help_button': self._ShowTriageHelpButton(), | 377 'show_triage_help_button': self._ShowTriageHelpButton(), |
| 335 'triage_reference_analysis_master_name': | 378 'triage_reference_analysis_master_name': |
| 336 analysis.triage_reference_analysis_master_name, | 379 analysis.triage_reference_analysis_master_name, |
| 337 'triage_reference_analysis_builder_name': | 380 'triage_reference_analysis_builder_name': |
| 338 analysis.triage_reference_analysis_builder_name, | 381 analysis.triage_reference_analysis_builder_name, |
| 339 'triage_reference_analysis_build_number': | 382 'triage_reference_analysis_build_number': |
| 340 analysis.triage_reference_analysis_build_number | 383 analysis.triage_reference_analysis_build_number |
| 341 } | 384 } |
| 342 | 385 |
| 343 def _PrepareDataForCompileFailure(self, analysis): | 386 def _PrepareDataForCompileFailure(self, analysis, data): |
| 344 data = self._PrepareCommonDataForFailure(analysis) | |
| 345 | 387 |
| 346 # Check result from heuristic analysis. | 388 # Check result from heuristic analysis. |
| 347 _PopulateHeuristicDataForCompileFailure(analysis, data) | 389 _PopulateHeuristicDataForCompileFailure(analysis, data) |
| 348 # Check result from try job. | 390 # Check result from try job. |
| 349 data['try_job'] = _PrepareTryJobDataForCompileFailure(analysis) | 391 data['try_job'] = _PrepareTryJobDataForCompileFailure(analysis) |
| 350 | 392 |
| 351 return data | 393 def _PrepareDataForTestFailures(self, analysis, build_info, data, |
| 394 show_debug_info=False): |
| 352 | 395 |
| 353 def _PrepareDataForTestFailures(self, analysis, build_info, | |
| 354 show_debug_info=False): | |
| 355 data = self._PrepareCommonDataForFailure(analysis) | |
| 356 data['status_message_map'] = result_status.STATUS_MESSAGE_MAP | 396 data['status_message_map'] = result_status.STATUS_MESSAGE_MAP |
| 357 | 397 |
| 358 organized_results = _GetOrganizedAnalysisResultBySuspectedCL( | 398 organized_results = _GetOrganizedAnalysisResultBySuspectedCL( |
| 359 analysis.result) | 399 analysis.result) |
| 360 analysis_result = _GetAnalysisResultWithTryJobInfo( | 400 analysis_result = _GetAnalysisResultWithTryJobInfo( |
| 361 show_debug_info, organized_results, *build_info) | 401 show_debug_info, organized_results, *build_info) |
| 362 | 402 |
| 363 data['analysis_result'] = analysis_result | 403 data['analysis_result'] = analysis_result |
| 364 return data | 404 return data |
| 365 | 405 |
| (...skipping 29 matching lines...) Expand all Loading... |
| 395 build_completed = (users.is_current_user_admin() and | 435 build_completed = (users.is_current_user_admin() and |
| 396 self.request.get('build_completed') == '1') | 436 self.request.get('build_completed') == '1') |
| 397 force_try_job = (users.is_current_user_admin() and | 437 force_try_job = (users.is_current_user_admin() and |
| 398 self.request.get('force_try_job') == '1') | 438 self.request.get('force_try_job') == '1') |
| 399 analysis = build_failure_analysis_pipelines.ScheduleAnalysisIfNeeded( | 439 analysis = build_failure_analysis_pipelines.ScheduleAnalysisIfNeeded( |
| 400 master_name, builder_name, build_number, | 440 master_name, builder_name, build_number, |
| 401 build_completed=build_completed, force=force, | 441 build_completed=build_completed, force=force, |
| 402 force_try_job=force_try_job, | 442 force_try_job=force_try_job, |
| 403 queue_name=constants.WATERFALL_ANALYSIS_QUEUE) | 443 queue_name=constants.WATERFALL_ANALYSIS_QUEUE) |
| 404 | 444 |
| 445 data = self._PrepareCommonDataForFailure(analysis) |
| 446 data['suspected_cls'] = _GetAllSuspectedCLsAndCheckStatus( |
| 447 master_name, builder_name, build_number, analysis) |
| 448 |
| 405 if analysis.failure_type == failure_type.COMPILE: | 449 if analysis.failure_type == failure_type.COMPILE: |
| 450 self._PrepareDataForCompileFailure(analysis, data) |
| 406 return { | 451 return { |
| 407 'template': 'waterfall/compile_failure.html', | 452 'template': 'waterfall/compile_failure.html', |
| 408 'data': self._PrepareDataForCompileFailure(analysis), | 453 'data': data |
| 409 } | 454 } |
| 410 else: | 455 else: |
| 456 self._PrepareDataForTestFailures( |
| 457 analysis, build_info, data, self._ShowDebugInfo()) |
| 411 return { | 458 return { |
| 412 'template': 'waterfall/test_failure.html', | 459 'template': 'waterfall/test_failure.html', |
| 413 'data': self._PrepareDataForTestFailures(analysis, build_info, | 460 'data': data |
| 414 self._ShowDebugInfo()), | |
| 415 } | 461 } |
| 416 | 462 |
| 417 def HandlePost(self): # pragma: no cover | 463 def HandlePost(self): # pragma: no cover |
| 418 return self.HandleGet() | 464 return self.HandleGet() |
| OLD | NEW |